From 9e93251397788b73728afcf44372127de7b26332 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Wed, 22 Jan 2014 16:55:27 +0100 Subject: [PATCH 01/72] utils: Provide a CALLBACK macro, similar to METHOD, but for void* callbacks Using the same mechanism as the METHOD macro, the CALLBACK macro defines a hybrid function signature. It strictly uses a weak void* for the first function parameter, in contrast to the dynamic METHOD object "this" type. --- src/libstrongswan/utils/utils.h | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/libstrongswan/utils/utils.h b/src/libstrongswan/utils/utils.h index 75b31ec1b..53bfceda6 100644 --- a/src/libstrongswan/utils/utils.h +++ b/src/libstrongswan/utils/utils.h @@ -203,6 +203,19 @@ static inline bool memeq(const void *x, const void *y, size_t len) static typeof(name) *_##name = (typeof(name)*)name; \ static ret name(this, ##__VA_ARGS__) +/** + * Callback declaration/definition macro, allowing casted first parameter. + * + * This is very similar to METHOD, but instead of casting the first parameter + * to a public interface, it uses a void*. This allows type safe definition + * of a callback function, while using the real type for the first parameter. + */ +#define CALLBACK(name, ret, param1, ...) \ + static ret _cb_##name(union {void *_generic; param1;} \ + __attribute__((transparent_union)), ##__VA_ARGS__); \ + static typeof(_cb_##name) *name = (typeof(_cb_##name)*)_cb_##name; \ + static ret _cb_##name(param1, ##__VA_ARGS__) + /** * Architecture independent bitfield definition helpers (at least with GCC). * From 874e212f71b86a483445036096b29f0dd81b3ed3 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Tue, 11 Feb 2014 15:41:49 +0100 Subject: [PATCH 02/72] processor: Flush pending jobs during cancel(), not destroy During shutdown, cancel queued jobs earlier to avoid having cleanup functions accessing infrastructure not available anymore, for example watcher. --- src/libstrongswan/processing/processor.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/libstrongswan/processing/processor.c b/src/libstrongswan/processing/processor.c index 012b169e3..27e5ab5f6 100644 --- a/src/libstrongswan/processing/processor.c +++ b/src/libstrongswan/processing/processor.c @@ -467,6 +467,8 @@ METHOD(processor_t, cancel, void, { enumerator_t *enumerator; worker_thread_t *worker; + job_t *job; + int i; this->mutex->lock(this->mutex); this->desired_threads = 0; @@ -496,6 +498,14 @@ METHOD(processor_t, cancel, void, worker->thread->join(worker->thread); free(worker); } + for (i = 0; i < JOB_PRIO_MAX; i++) + { + while (this->jobs[i]->remove_first(this->jobs[i], + (void**)&job) == SUCCESS) + { + job->destroy(job); + } + } this->mutex->unlock(this->mutex); } @@ -510,7 +520,7 @@ METHOD(processor_t, destroy, void, this->mutex->destroy(this->mutex); for (i = 0; i < JOB_PRIO_MAX; i++) { - this->jobs[i]->destroy_offset(this->jobs[i], offsetof(job_t, destroy)); + this->jobs[i]->destroy(this->jobs[i]); } this->threads->destroy(this->threads); free(this); From d16d5a245f0b313bed187462e8b06dd9391e2982 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Tue, 18 Feb 2014 17:54:34 +0100 Subject: [PATCH 03/72] watcher: Avoid queueing multiple watcher callbacks at the same time While we don't add FDs with an active callback to the watched FDSET, we still can get notifications for callbacks active due the asynchronous processing of the same. To avoid queue multiple callbacks, we check for queued callbacks before activating new ones. --- src/libstrongswan/processing/watcher.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/libstrongswan/processing/watcher.c b/src/libstrongswan/processing/watcher.c index cc3c3a788..a0b5529b3 100644 --- a/src/libstrongswan/processing/watcher.c +++ b/src/libstrongswan/processing/watcher.c @@ -238,6 +238,7 @@ static job_requeue_t watch(private_watcher_t *this) entry_t *entry; fd_set rd, wr, ex; int maxfd = 0, res; + bool rebuild = FALSE; FD_ZERO(&rd); FD_ZERO(&wr); @@ -282,7 +283,7 @@ static job_requeue_t watch(private_watcher_t *this) enumerator->destroy(enumerator); this->mutex->unlock(this->mutex); - while (TRUE) + while (!rebuild) { char buf[1]; bool old; @@ -308,6 +309,11 @@ static job_requeue_t watch(private_watcher_t *this) enumerator = this->fds->create_enumerator(this->fds); while (enumerator->enumerate(enumerator, &entry)) { + if (entry->in_callback) + { + rebuild = TRUE; + break; + } if (FD_ISSET(entry->fd, &rd) && (entry->events & WATCHER_READ)) { DBG2(DBG_JOB, "watched FD %d ready to read", entry->fd); @@ -347,6 +353,7 @@ static job_requeue_t watch(private_watcher_t *this) return JOB_REQUEUE_DIRECT; } } + return JOB_REQUEUE_DIRECT; } METHOD(watcher_t, add, void, From d2b07d3187c38327cf949345d6255706d3477645 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Mon, 10 Mar 2014 10:48:27 +0100 Subject: [PATCH 04/72] watcher: Don't wait for running callback once watcher thread cancelled During shutdown, waiting for callbacks might never complete, as queued callbacks might not get executed under certain conditions. Not the clean fix, but works good enough for now. Seen on Windows in vici tests. --- src/libstrongswan/processing/watcher.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/libstrongswan/processing/watcher.c b/src/libstrongswan/processing/watcher.c index a0b5529b3..09905646c 100644 --- a/src/libstrongswan/processing/watcher.c +++ b/src/libstrongswan/processing/watcher.c @@ -49,6 +49,11 @@ struct private_watcher_t { */ bool pending; + /** + * Is watcher running? + */ + bool running; + /** * Lock to access FD list */ @@ -225,6 +230,7 @@ static void activate_all(private_watcher_t *this) entry->in_callback = 0; } enumerator->destroy(enumerator); + this->running = FALSE; this->condvar->broadcast(this->condvar); this->mutex->unlock(this->mutex); } @@ -373,6 +379,7 @@ METHOD(watcher_t, add, void, this->fds->insert_last(this->fds, entry); if (this->fds->get_count(this->fds) == 1) { + this->running = TRUE; lib->processor->queue_job(lib->processor, (job_t*)callback_job_create_with_prio((void*)watch, this, NULL, (callback_job_cancel_t)return_false, JOB_PRIO_CRITICAL)); @@ -400,7 +407,7 @@ METHOD(watcher_t, remove_, void, { if (entry->fd == fd) { - if (entry->in_callback) + if (this->running && entry->in_callback) { is_in_callback = TRUE; break; From f8223f5f7e33c072e43cdb54198ec2e466107558 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Wed, 19 Feb 2014 14:21:10 +0100 Subject: [PATCH 05/72] mem-cred: Replace existing equal private keys during add_key() --- src/libstrongswan/credentials/sets/mem_cred.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/libstrongswan/credentials/sets/mem_cred.c b/src/libstrongswan/credentials/sets/mem_cred.c index b8da3f620..def017255 100644 --- a/src/libstrongswan/credentials/sets/mem_cred.c +++ b/src/libstrongswan/credentials/sets/mem_cred.c @@ -307,8 +307,25 @@ METHOD(credential_set_t, create_private_enumerator, enumerator_t*, METHOD(mem_cred_t, add_key, void, private_mem_cred_t *this, private_key_t *key) { + enumerator_t *enumerator; + private_key_t *current; + this->lock->write_lock(this->lock); + + enumerator = this->keys->create_enumerator(this->keys); + while (enumerator->enumerate(enumerator, ¤t)) + { + if (current->equals(current, key)) + { + this->keys->remove_at(this->keys, enumerator); + current->destroy(current); + break; + } + } + enumerator->destroy(enumerator); + this->keys->insert_first(this->keys, key); + this->lock->unlock(this->lock); } From d4368689a0e0836723bf0efd6d3d330247ffe21e Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Wed, 19 Feb 2014 14:29:42 +0100 Subject: [PATCH 06/72] mem-cred: Replace existing equal shared keys during add_shared() --- src/libstrongswan/credentials/sets/mem_cred.c | 59 ++++++++++++++++++- 1 file changed, 56 insertions(+), 3 deletions(-) diff --git a/src/libstrongswan/credentials/sets/mem_cred.c b/src/libstrongswan/credentials/sets/mem_cred.c index def017255..d8f568d36 100644 --- a/src/libstrongswan/credentials/sets/mem_cred.c +++ b/src/libstrongswan/credentials/sets/mem_cred.c @@ -350,6 +350,44 @@ static void shared_entry_destroy(shared_entry_t *entry) free(entry); } +/** + * Check if two shared key entries equal + */ +static bool shared_entry_equals(shared_entry_t *a, shared_entry_t *b) +{ + enumerator_t *e1, *e2; + identification_t *id1, *id2; + bool equals = TRUE; + + if (a->shared->get_type(a->shared) != b->shared->get_type(b->shared)) + { + return FALSE; + } + if (!chunk_equals(a->shared->get_key(a->shared), + b->shared->get_key(b->shared))) + { + return FALSE; + } + if (a->owners->get_count(a->owners) != b->owners->get_count(b->owners)) + { + return FALSE; + } + e1 = a->owners->create_enumerator(a->owners); + e2 = b->owners->create_enumerator(b->owners); + while (e1->enumerate(e1, &id1) && e2->enumerate(e2, &id2)) + { + if (!id1->equals(id1, id2)) + { + equals = FALSE; + break; + } + } + e1->destroy(e1); + e2->destroy(e2); + + return equals; +} + /** * Data for the shared_key enumerator */ @@ -452,15 +490,30 @@ METHOD(credential_set_t, create_shared_enumerator, enumerator_t*, METHOD(mem_cred_t, add_shared_list, void, private_mem_cred_t *this, shared_key_t *shared, linked_list_t* owners) { - shared_entry_t *entry; + shared_entry_t *current, *new; + enumerator_t *enumerator; - INIT(entry, + INIT(new, .shared = shared, .owners = owners, ); this->lock->write_lock(this->lock); - this->shared->insert_first(this->shared, entry); + + enumerator = this->shared->create_enumerator(this->shared); + while (enumerator->enumerate(enumerator, ¤t)) + { + if (shared_entry_equals(current, new)) + { + this->shared->remove_at(this->shared, enumerator); + shared_entry_destroy(current); + break; + } + } + enumerator->destroy(enumerator); + + this->shared->insert_first(this->shared, new); + this->lock->unlock(this->lock); } From 6770cfe34aea20ce970dbc35fad4bd4685a2b58d Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Mon, 20 Jan 2014 10:59:21 +0100 Subject: [PATCH 07/72] vici: Add a plugin stub for the "Versatile IKE Control Interface" plugin --- configure.ac | 4 ++ src/libcharon/Makefile.am | 7 ++ src/libcharon/plugins/vici/Makefile.am | 19 ++++++ src/libcharon/plugins/vici/vici_plugin.c | 84 ++++++++++++++++++++++++ src/libcharon/plugins/vici/vici_plugin.h | 42 ++++++++++++ 5 files changed, 156 insertions(+) create mode 100644 src/libcharon/plugins/vici/Makefile.am create mode 100644 src/libcharon/plugins/vici/vici_plugin.c create mode 100644 src/libcharon/plugins/vici/vici_plugin.h diff --git a/configure.ac b/configure.ac index 1bd68d66d..6dbcccf37 100644 --- a/configure.ac +++ b/configure.ac @@ -210,6 +210,7 @@ ARG_DISBL_SET([stroke], [disable charons stroke configuration backend.]) ARG_ENABL_SET([smp], [enable SMP configuration and control interface. Requires libxml.]) ARG_ENABL_SET([sql], [enable SQL database configuration backend.]) ARG_ENABL_SET([uci], [enable OpenWRT UCI configuration plugin.]) +ARG_ENABL_SET([vici], [enable strongSwan IKE generic IPC interface plugin.]) # attribute provider/consumer plugins ARG_ENABL_SET([android-dns], [enable Android specific DNS handler.]) ARG_DISBL_SET([attr], [disable strongswan.conf based configuration attribute plugin.]) @@ -1131,6 +1132,7 @@ ADD_PLUGIN([socket-default], [c charon nm cmd]) ADD_PLUGIN([socket-dynamic], [c charon cmd]) ADD_PLUGIN([farp], [c charon]) ADD_PLUGIN([stroke], [c charon]) +ADD_PLUGIN([vici], [c charon]) ADD_PLUGIN([smp], [c charon]) ADD_PLUGIN([sql], [c charon]) ADD_PLUGIN([updown], [c charon]) @@ -1261,6 +1263,7 @@ AM_CONDITIONAL(USE_NTRU, test x$ntru = xtrue) # charon plugins # ---------------- AM_CONDITIONAL(USE_STROKE, test x$stroke = xtrue) +AM_CONDITIONAL(USE_VICI, test x$vici = xtrue) AM_CONDITIONAL(USE_MEDSRV, test x$medsrv = xtrue) AM_CONDITIONAL(USE_MEDCLI, test x$medcli = xtrue) AM_CONDITIONAL(USE_UCI, test x$uci = xtrue) @@ -1578,6 +1581,7 @@ AC_CONFIG_FILES([ src/libcharon/plugins/android_log/Makefile src/libcharon/plugins/maemo/Makefile src/libcharon/plugins/stroke/Makefile + src/libcharon/plugins/vici/Makefile src/libcharon/plugins/updown/Makefile src/libcharon/plugins/dhcp/Makefile src/libcharon/plugins/unit_tester/Makefile diff --git a/src/libcharon/Makefile.am b/src/libcharon/Makefile.am index df58eaa10..3e7a96103 100644 --- a/src/libcharon/Makefile.am +++ b/src/libcharon/Makefile.am @@ -202,6 +202,13 @@ if MONOLITHIC endif endif +if USE_VICI + SUBDIRS += plugins/vici +if MONOLITHIC + libcharon_la_LIBADD += plugins/vici/libstrongswan-vici.la +endif +endif + if USE_SMP SUBDIRS += plugins/smp if MONOLITHIC diff --git a/src/libcharon/plugins/vici/Makefile.am b/src/libcharon/plugins/vici/Makefile.am new file mode 100644 index 000000000..93de45add --- /dev/null +++ b/src/libcharon/plugins/vici/Makefile.am @@ -0,0 +1,19 @@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/libstrongswan \ + -I$(top_srcdir)/src/libhydra \ + -I$(top_srcdir)/src/libcharon \ + -DIPSEC_PIDDIR=\"${piddir}\" + +AM_CFLAGS = \ + -rdynamic + +if MONOLITHIC +noinst_LTLIBRARIES = libstrongswan-vici.la +else +plugin_LTLIBRARIES = libstrongswan-vici.la +endif + +libstrongswan_vici_la_SOURCES = \ + vici_plugin.h vici_plugin.c + +libstrongswan_vici_la_LDFLAGS = -module -avoid-version diff --git a/src/libcharon/plugins/vici/vici_plugin.c b/src/libcharon/plugins/vici/vici_plugin.c new file mode 100644 index 000000000..cbe3b15d4 --- /dev/null +++ b/src/libcharon/plugins/vici/vici_plugin.c @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2014 Martin Willi + * Copyright (C) 2014 revosec 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 . + * + * 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 "vici_plugin.h" + +#include + +typedef struct private_vici_plugin_t private_vici_plugin_t; + +/** + * Private members of vici_plugin_t + */ +struct private_vici_plugin_t { + + /** + * public functions + */ + vici_plugin_t public; +}; + +METHOD(plugin_t, get_name, char*, + private_vici_plugin_t *this) +{ + return "vici"; +} + +/** + * Register vici plugin features + */ +static bool register_vici(private_vici_plugin_t *this, + plugin_feature_t *feature, bool reg, void *data) +{ + return TRUE; +} + +METHOD(plugin_t, get_features, int, + private_vici_plugin_t *this, plugin_feature_t *features[]) +{ + static plugin_feature_t f[] = { + PLUGIN_CALLBACK((plugin_feature_callback_t)register_vici, NULL), + PLUGIN_PROVIDE(CUSTOM, "vici"), + }; + *features = f; + return countof(f); +} + +METHOD(plugin_t, destroy, void, + private_vici_plugin_t *this) +{ + free(this); +} + +/* + * see header file + */ +plugin_t *vici_plugin_create() +{ + private_vici_plugin_t *this; + + INIT(this, + .public = { + .plugin = { + .get_name = _get_name, + .reload = (void*)return_false, + .get_features = _get_features, + .destroy = _destroy, + }, + }, + ); + + return &this->public.plugin; +} diff --git a/src/libcharon/plugins/vici/vici_plugin.h b/src/libcharon/plugins/vici/vici_plugin.h new file mode 100644 index 000000000..b4c380200 --- /dev/null +++ b/src/libcharon/plugins/vici/vici_plugin.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2014 Martin Willi + * Copyright (C) 2014 revosec 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 . + * + * 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. + */ + +/** + * @defgroup vici vici + * @ingroup cplugins + * + * @defgroup vici_plugin vici_plugin + * @{ @ingroup vici + */ + +#ifndef VICI_PLUGIN_H_ +#define VICI_PLUGIN_H_ + +#include + +typedef struct vici_plugin_t vici_plugin_t; + +/** + * vici plugin, the "Versatile IKE Control Interface" interface. + */ +struct vici_plugin_t { + + /** + * Implements plugin interface + */ + plugin_t plugin; +}; + +#endif /** VICI_PLUGIN_H_ @}*/ From 1e39454214b2091b2fea92aa71fa16aa9efe04df Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Tue, 21 Jan 2014 14:39:35 +0100 Subject: [PATCH 08/72] vici: Add a test runner for plugin --- src/libcharon/plugins/vici/.gitignore | 1 + src/libcharon/plugins/vici/Makefile.am | 19 +++++++++ src/libcharon/plugins/vici/vici_tests.c | 53 +++++++++++++++++++++++++ src/libcharon/plugins/vici/vici_tests.h | 14 +++++++ 4 files changed, 87 insertions(+) create mode 100644 src/libcharon/plugins/vici/.gitignore create mode 100644 src/libcharon/plugins/vici/vici_tests.c create mode 100644 src/libcharon/plugins/vici/vici_tests.h diff --git a/src/libcharon/plugins/vici/.gitignore b/src/libcharon/plugins/vici/.gitignore new file mode 100644 index 000000000..a78cc13cf --- /dev/null +++ b/src/libcharon/plugins/vici/.gitignore @@ -0,0 +1 @@ +vici_tests diff --git a/src/libcharon/plugins/vici/Makefile.am b/src/libcharon/plugins/vici/Makefile.am index 93de45add..f27fb09c8 100644 --- a/src/libcharon/plugins/vici/Makefile.am +++ b/src/libcharon/plugins/vici/Makefile.am @@ -17,3 +17,22 @@ libstrongswan_vici_la_SOURCES = \ vici_plugin.h vici_plugin.c libstrongswan_vici_la_LDFLAGS = -module -avoid-version + +TESTS = vici_tests + +check_PROGRAMS = $(TESTS) + +vici_tests_SOURCES = \ + vici_tests.h vici_tests.c + +vici_tests_CFLAGS = \ + -I$(top_srcdir)/src/libstrongswan \ + -I$(top_srcdir)/src/libstrongswan/tests \ + @COVERAGE_CFLAGS@ + +vici_tests_LDFLAGS = @COVERAGE_LDFLAGS@ +vici_tests_LDADD = \ + $(top_builddir)/src/libcharon/libcharon.la \ + $(top_builddir)/src/libhydra/libhydra.la \ + $(top_builddir)/src/libstrongswan/libstrongswan.la \ + $(top_builddir)/src/libstrongswan/tests/libtest.la diff --git a/src/libcharon/plugins/vici/vici_tests.c b/src/libcharon/plugins/vici/vici_tests.c new file mode 100644 index 000000000..ec5afa45d --- /dev/null +++ b/src/libcharon/plugins/vici/vici_tests.c @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2014 Martin Willi + * Copyright (C) 2014 revosec 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 . + * + * 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 + +#include +#include + +/* declare test suite constructors */ +#define TEST_SUITE(x) test_suite_t* x(); +#include "vici_tests.h" +#undef TEST_SUITE + +static test_configuration_t tests[] = { +#define TEST_SUITE(x) \ + { .suite = x, }, +#include "vici_tests.h" + { .suite = NULL, } +}; + +static bool test_runner_init(bool init) +{ + if (init) + { + libhydra_init("vici-tests"); + libcharon_init("vici-tests"); + } + else + { + lib->processor->set_threads(lib->processor, 0); + lib->processor->cancel(lib->processor); + libcharon_deinit(); + libhydra_deinit(); + } + return TRUE; +} + +int main(int argc, char *argv[]) +{ + return test_runner_run("vici", tests, test_runner_init); +} diff --git a/src/libcharon/plugins/vici/vici_tests.h b/src/libcharon/plugins/vici/vici_tests.h new file mode 100644 index 000000000..05502f18e --- /dev/null +++ b/src/libcharon/plugins/vici/vici_tests.h @@ -0,0 +1,14 @@ +/* + * Copyright (C) 2014 Martin Willi + * Copyright (C) 2014 revosec 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 . + * + * 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. + */ From 8457da75286f4e7cde996bd7125f5f03ea236849 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Tue, 21 Jan 2014 17:53:15 +0100 Subject: [PATCH 09/72] vici: Add a fully asynchronous IPC socket segmenting messages on/from stream --- src/libcharon/plugins/vici/Makefile.am | 6 + .../plugins/vici/suites/test_message.c | 266 +++++++++ .../plugins/vici/suites/test_socket.c | 131 +++++ src/libcharon/plugins/vici/vici_message.c | 404 ++++++++++++++ src/libcharon/plugins/vici/vici_message.h | 125 +++++ src/libcharon/plugins/vici/vici_socket.c | 513 ++++++++++++++++++ src/libcharon/plugins/vici/vici_socket.h | 90 +++ src/libcharon/plugins/vici/vici_tests.h | 3 + 8 files changed, 1538 insertions(+) create mode 100644 src/libcharon/plugins/vici/suites/test_message.c create mode 100644 src/libcharon/plugins/vici/suites/test_socket.c create mode 100644 src/libcharon/plugins/vici/vici_message.c create mode 100644 src/libcharon/plugins/vici/vici_message.h create mode 100644 src/libcharon/plugins/vici/vici_socket.c create mode 100644 src/libcharon/plugins/vici/vici_socket.h diff --git a/src/libcharon/plugins/vici/Makefile.am b/src/libcharon/plugins/vici/Makefile.am index f27fb09c8..d166d594a 100644 --- a/src/libcharon/plugins/vici/Makefile.am +++ b/src/libcharon/plugins/vici/Makefile.am @@ -14,6 +14,8 @@ plugin_LTLIBRARIES = libstrongswan-vici.la endif libstrongswan_vici_la_SOURCES = \ + vici_socket.h vici_socket.c \ + vici_message.h vici_message.c \ vici_plugin.h vici_plugin.c libstrongswan_vici_la_LDFLAGS = -module -avoid-version @@ -23,6 +25,10 @@ TESTS = vici_tests check_PROGRAMS = $(TESTS) vici_tests_SOURCES = \ + suites/test_socket.c \ + suites/test_message.c \ + vici_socket.c \ + vici_message.c \ vici_tests.h vici_tests.c vici_tests_CFLAGS = \ diff --git a/src/libcharon/plugins/vici/suites/test_message.c b/src/libcharon/plugins/vici/suites/test_message.c new file mode 100644 index 000000000..a35c30905 --- /dev/null +++ b/src/libcharon/plugins/vici/suites/test_message.c @@ -0,0 +1,266 @@ +/* + * Copyright (C) 2014 Martin Willi + * Copyright (C) 2014 revosec 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 . + * + * 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 + +#include "../vici_message.h" + +#include + +static char blob[] = { + 0xd3,0xe5,0xee,0x37,0x7b,0x96,0x2f,0x3e,0x5f,0x3e,0x91,0xea,0x38,0x44,0xba,0x6c, + 0x75,0xc8,0x42,0x32,0xaf,0x7a,0x66,0x43,0x33,0x92,0xd2,0xef,0x7d,0x91,0x7b,0x59, + 0x9f,0x9f,0xd1,0x44,0xb6,0x1e,0x8c,0xd1,0xc5,0xa0,0xd9,0xe4,0xf2,0x31,0xfd,0x7b, + 0x5b,0x56,0xa7,0xfe,0x63,0x0d,0xcb,0x31,0x74,0xd8,0xd6,0x4a,0x42,0x3a,0x88,0xf3, + 0x79,0xf9,0x41,0xa6,0xc0,0x64,0x53,0x31,0x42,0xe2,0xd4,0x4a,0x22,0x5f,0x3f,0x99, + 0xe0,0x1a,0xcb,0x93,0x26,0xd0,0xec,0xac,0x90,0x97,0x0a,0x5f,0x69,0x86,0xf1,0xda, + 0xfc,0xa7,0xac,0xd0,0xd8,0x81,0xcf,0x7d,0x47,0x22,0xbe,0xbf,0x00,0x9b,0x6b,0x86, + 0x92,0x89,0xbe,0x7f,0x74,0x13,0x53,0xf1,0x4c,0x2b,0xc9,0xe1,0x39,0xd6,0xfc,0x50, + 0x3f,0x00,0xfb,0x76,0x42,0xa6,0xa4,0x70,0xfc,0x93,0x17,0x4a,0x35,0xce,0x5e,0x78, + 0x41,0x88,0x24,0x50,0x78,0xf2,0x38,0x08,0xff,0x40,0xef,0x61,0xbb,0xbf,0x16,0xff, + 0x0b,0xf6,0x33,0x21,0xcb,0x48,0xbd,0x7d,0xd1,0x73,0xfa,0x6d,0xd6,0xab,0xde,0x69, + 0x63,0x17,0xdb,0x52,0xe2,0x75,0x4b,0xb7,0x1e,0xf0,0x8a,0x55,0x4f,0x70,0x8d,0x18, + 0xe5,0x38,0x6a,0x9f,0xb8,0x06,0xb5,0x91,0x90,0x2b,0xc5,0x67,0xa9,0x12,0xe5,0xf3, + 0x48,0x2f,0x80,0x03,0xa1,0xa0,0xfc,0x43,0xe9,0x0f,0x83,0x2b,0xbc,0x7c,0xa8,0x3b, + 0x6c,0xc1,0xc8,0x72,0x5f,0x87,0x63,0x77,0x93,0x9b,0xe2,0xd7,0x4e,0xe6,0x65,0xa1, + 0x69,0x00,0xda,0xf8,0xb4,0x61,0xee,0xb7,0x20,0xe7,0x2a,0x35,0x23,0xf0,0x37,0x4b, + 0x67,0xcf,0x8d,0x85,0x72,0x22,0x6d,0x7a,0xb2,0x96,0xff,0x49,0xf4,0x94,0x3e,0x7e, + 0x87,0x26,0x5d,0x34,0x05,0x26,0x60,0x9b,0x89,0xfe,0xf9,0x91,0xd3,0x03,0xe7,0x8a, + 0x03,0xf6,0x4e,0xbf,0x68,0x13,0xc6,0xf2,0x7b,0x9c,0xe6,0x36,0x1b,0xe2,0x22,0x44, + 0xb1,0x19,0x34,0x5f,0xe8,0x44,0x48,0x3a,0x19,0xe4,0xbd,0xb0,0x4e,0xb5,0x2c,0x40, + 0x55,0x39,0xe6,0x4c,0xd5,0x68,0x34,0x72,0x6b,0x6d,0x88,0xce,0x7e,0x77,0x95,0x17, + 0x2e,0x68,0x3f,0x0e,0x9d,0x70,0x9a,0x22,0xfa,0x19,0xcc,0x15,0x9d,0xba,0xaa,0xec, + 0xb1,0x67,0x19,0x51,0xce,0x60,0x9a,0x38,0xf8,0xa7,0x4e,0xe3,0x25,0x47,0x1e,0x1d, + 0x30,0x76,0x91,0x8f,0x4d,0x13,0x59,0x06,0x2f,0x01,0x10,0x95,0xdb,0x08,0x7c,0x46, + 0xed,0x47,0xa1,0x19,0x4c,0x46,0xd1,0x3a,0x3f,0x88,0x7a,0x63,0xae,0x29,0x13,0x42, + 0xe9,0x17,0xe8,0xa9,0x95,0xfc,0xd1,0xea,0xfa,0x59,0x90,0xfe,0xb7,0xbb,0x7f,0x61, + 0x1b,0xcb,0x3d,0x12,0x99,0x96,0x3e,0x23,0x23,0xec,0x3a,0x4d,0x86,0x86,0x74,0xef, + 0x38,0xa6,0xdc,0x3a,0x83,0x85,0xf8,0xb8,0xad,0x5b,0x33,0x94,0x4d,0x0e,0x68,0xbc, + 0xf2,0xc7,0x6f,0x84,0x18,0x1e,0x5a,0x66,0x1f,0x6c,0x98,0x33,0xda,0xde,0x9e,0xda, + 0x82,0xd0,0x56,0x44,0x47,0x08,0x0c,0x07,0x81,0x9d,0x8b,0x64,0x16,0x73,0x9d,0x80, + 0x54,0x9c,0x4c,0x42,0xde,0x27,0x4e,0x97,0xb2,0xcf,0x48,0xaf,0x7e,0x85,0xc1,0xcd, + 0x6a,0x4d,0x04,0x40,0x89,0xa3,0x9d,0x4e,0x89,0x56,0x60,0x31,0x1f,0x3f,0x49,0x16, +}; + +typedef struct { + vici_type_t type; + char *name; + chunk_t data; +} endecode_test_t; + +static endecode_test_t endecode_test_simple[] = { + { VICI_SECTION_START, "section1", {} }, + { VICI_KEY_VALUE, "key1", { "value1", 6 } }, + { VICI_KEY_VALUE, "key2", { "value2", 6 } }, + { VICI_SECTION_END, NULL, {} }, + { VICI_END, NULL, {} }, +}; + +static endecode_test_t endecode_test_nested[] = { + { VICI_SECTION_START, "section1", {} }, + { VICI_SECTION_START, "section2", {} }, + { VICI_SECTION_START, "section3", {} }, + { VICI_KEY_VALUE, "key1", { "value1", 6 } }, + { VICI_SECTION_START, "section4", {} }, + { VICI_KEY_VALUE, "key2", { "value2", 6 } }, + { VICI_SECTION_END, NULL, {} }, + { VICI_SECTION_END, NULL, {} }, + { VICI_SECTION_END, NULL, {} }, + { VICI_KEY_VALUE, "key3", { "value3", 6 } }, + { VICI_SECTION_END, NULL, {} }, + { VICI_END, NULL, {} }, +}; + +static endecode_test_t endecode_test_list[] = { + { VICI_SECTION_START, "section1", {} }, + { VICI_LIST_START, "list1", {} }, + { VICI_LIST_ITEM, NULL, { "item1", 5 } }, + { VICI_LIST_ITEM, NULL, { "item2", 5 } }, + { VICI_LIST_END, NULL, {} }, + { VICI_KEY_VALUE, "key1", { "value1", 6 } }, + { VICI_SECTION_END, NULL, {} }, + { VICI_END, NULL, {} }, +}; + +static endecode_test_t endecode_test_blobs[] = { + { VICI_KEY_VALUE, "key1", { blob, countof(blob) } }, + { VICI_SECTION_START, "section1", {} }, + { VICI_LIST_START, "list1", {} }, + { VICI_LIST_ITEM, NULL, { blob, countof(blob) } }, + { VICI_LIST_ITEM, NULL, { blob, countof(blob) } }, + { VICI_LIST_END, NULL, {} }, + { VICI_KEY_VALUE, "key2", { blob, countof(blob) } }, + { VICI_SECTION_END, NULL, {} }, + { VICI_END, NULL, {} }, +}; + +static endecode_test_t *endecode_tests[] = { + endecode_test_simple, + endecode_test_nested, + endecode_test_list, + endecode_test_blobs, +}; + +typedef struct { + enumerator_t public; + endecode_test_t *next; +} endecode_enum_t; + +static bool endecode_enumerate(endecode_enum_t *this, vici_type_t *type, + char **name, chunk_t *data) +{ + if (this->next) + { + *type = this->next->type; + *name = this->next->name; + *data = this->next->data; + if (this->next->type == VICI_END) + { + this->next = NULL; + } + else + { + this->next++; + } + return TRUE; + } + return FALSE; +} + +static enumerator_t *endecode_create_enumerator(endecode_test_t *test) +{ + endecode_enum_t *enumerator; + + INIT(enumerator, + .public = { + .enumerate = (void*)endecode_enumerate, + .destroy = (void*)free, + }, + .next = test, + ); + + return &enumerator->public; +} + +static void compare_vici(enumerator_t *parse, enumerator_t *tmpl) +{ + vici_type_t type, ttype; + char *name, *tname; + chunk_t data, tdata;; + + while (TRUE) + { + ck_assert(parse->enumerate(parse, &type, &name, &data)); + ck_assert(tmpl->enumerate(tmpl, &ttype, &tname, &tdata)); + ck_assert_int_eq(type, ttype); + switch (type) + { + case VICI_END: + return; + case VICI_SECTION_START: + case VICI_LIST_START: + ck_assert(streq(name, tname)); + break; + case VICI_LIST_ITEM: + ck_assert(chunk_equals(data, tdata)); + break; + case VICI_KEY_VALUE: + ck_assert(streq(name, tname)); + ck_assert(chunk_equals(data, tdata)); + break; + case VICI_SECTION_END: + case VICI_LIST_END: + break; + default: + ck_assert(FALSE); + break; + } + } +} + +START_TEST(test_endecode) +{ + enumerator_t *parse, *tmpl; + vici_message_t *m; + chunk_t data; + + tmpl = endecode_create_enumerator(endecode_tests[_i]); + m = vici_message_create_from_enumerator(tmpl); + ck_assert(m); + data = chunk_clone(m->get_encoding(m)); + tmpl = endecode_create_enumerator(endecode_tests[_i]); + parse = m->create_enumerator(m); + ck_assert(parse); + compare_vici(parse, tmpl); + tmpl->destroy(tmpl); + parse->destroy(parse); + m->destroy(m); + + m = vici_message_create_from_data(data, TRUE); + ck_assert(m); + tmpl = endecode_create_enumerator(endecode_tests[_i]); + parse = m->create_enumerator(m); + ck_assert(parse); + compare_vici(parse, tmpl); + tmpl->destroy(tmpl); + parse->destroy(parse); + m->destroy(m); +} +END_TEST + +START_TEST(test_vararg) +{ + enumerator_t *parse, *tmpl; + vici_message_t *m; + + m = vici_message_create_from_args( + VICI_SECTION_START, "section1", + VICI_LIST_START, "list1", + VICI_LIST_ITEM, chunk_from_str("item1"), + VICI_LIST_ITEM, chunk_from_str("item2"), + VICI_LIST_END, + VICI_KEY_VALUE, "key1", chunk_from_str("value1"), + VICI_SECTION_END, + VICI_END); + ck_assert(m); + tmpl = endecode_create_enumerator(endecode_test_list); + parse = m->create_enumerator(m); + ck_assert(parse); + + compare_vici(parse, tmpl); + + m->destroy(m); + tmpl->destroy(tmpl); + parse->destroy(parse); +} +END_TEST + +Suite *message_suite_create() +{ + Suite *s; + TCase *tc; + + s = suite_create("vici message"); + + tc = tcase_create("enumerator en/decode"); + tcase_add_loop_test(tc, test_endecode, 0, countof(endecode_tests)); + suite_add_tcase(s, tc); + + tc = tcase_create("vararg encode"); + tcase_add_test(tc, test_vararg); + suite_add_tcase(s, tc); + + return s; +} diff --git a/src/libcharon/plugins/vici/suites/test_socket.c b/src/libcharon/plugins/vici/suites/test_socket.c new file mode 100644 index 000000000..a9ebdbfca --- /dev/null +++ b/src/libcharon/plugins/vici/suites/test_socket.c @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2014 Martin Willi + * Copyright (C) 2014 revosec 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 . + * + * 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 + +#include "../vici_socket.h" + +#include + +typedef struct { + vici_socket_t *s; + int disconnect; + int bytes; + u_int id; +} test_data_t; + +static void echo_inbound(void *user, u_int id, chunk_t buf) +{ + test_data_t *data = user; + + ck_assert_int_eq(data->id, id); + /* count number of bytes, including the header */ + data->bytes += buf.len + sizeof(u_int16_t); + /* echo back data chunk */ + data->s->send(data->s, id, chunk_clone(buf)); +} + +static void echo_connect(void *user, u_int id) +{ + test_data_t *data = user; + + data->id = id; +} + +static void echo_disconnect(void *user, u_int id) +{ + test_data_t *data = user; + + ck_assert(id == data->id); + data->disconnect++; +} + +static struct { + char *uri; + u_int chunksize; +} echo_tests[] = { + { "tcp://127.0.0.1:6543", ~0 }, + { "tcp://127.0.0.1:6543", 1 }, + { "tcp://127.0.0.1:6543", 2 }, + { "tcp://127.0.0.1:6543", 3 }, + { "tcp://127.0.0.1:6543", 7 }, + { "unix:///tmp/strongswan-tests-vici-socket", ~0 }, + { "unix:///tmp/strongswan-tests-vici-socket", 1 }, + { "unix:///tmp/strongswan-tests-vici-socket", 2 }, + { "unix:///tmp/strongswan-tests-vici-socket", 3 }, + { "unix:///tmp/strongswan-tests-vici-socket", 7 }, +}; + +START_TEST(test_echo) +{ + stream_t *c; + test_data_t data = {}; + chunk_t x, m = chunk_from_chars( + 0x00,0x00, + 0x00,0x01, 0x01, + 0x00,0x05, 0x11,0x12,0x13,0x14,0x15, + 0x00,0x0A, 0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x02A, + ); + char buf[m.len]; + u_int16_t len; + + lib->processor->set_threads(lib->processor, 4); + + /* create socket, connect with stream */ + data.s = vici_socket_create(echo_tests[_i].uri, echo_inbound, echo_connect, + echo_disconnect, &data); + ck_assert(data.s != NULL); + c = lib->streams->connect(lib->streams, echo_tests[_i].uri); + ck_assert(c != NULL); + + /* write arbitrary chunks of messages blob depending on test */ + x = m; + while (x.len) + { + len = min(x.len, echo_tests[_i].chunksize); + ck_assert(c->write_all(c, x.ptr, len)); + x = chunk_skip(x, len); + } + + /* verify echo */ + ck_assert(c->read_all(c, buf, sizeof(buf))); + ck_assert(chunk_equals(m, chunk_from_thing(buf))); + + /* wait for completion */ + c->destroy(c); + while (data.disconnect != 1) + { + usleep(1000); + } + /* check that we got correct number of bytes/invocations */ + ck_assert_int_eq(data.bytes, m.len); + + data.s->destroy(data.s); +} +END_TEST + +Suite *socket_suite_create() +{ + Suite *s; + TCase *tc; + + s = suite_create("vici socket"); + + tc = tcase_create("echo"); + tcase_add_loop_test(tc, test_echo, 0, countof(echo_tests)); + suite_add_tcase(s, tc); + + return s; +} diff --git a/src/libcharon/plugins/vici/vici_message.c b/src/libcharon/plugins/vici/vici_message.c new file mode 100644 index 000000000..7a9e6b6ec --- /dev/null +++ b/src/libcharon/plugins/vici/vici_message.c @@ -0,0 +1,404 @@ +/* + * Copyright (C) 2014 Martin Willi + * Copyright (C) 2014 revosec 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 . + * + * 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 "vici_message.h" + +#include +#include + +typedef struct private_vici_message_t private_vici_message_t; + +/** + * Private data of an vici_message_t object. + */ +struct private_vici_message_t { + + /** + * Public vici_message_t interface. + */ + vici_message_t public; + + /** + * Message encoding + */ + chunk_t encoding; + + /** + * Free encoding during destruction? + */ + bool cleanup; +}; + +ENUM(vici_type_names, VICI_SECTION_START, VICI_END, + "section-start", + "section-end", + "key-value", + "list-start", + "list-item", + "list-end", + "end" +); + +/** + * See header. + */ +bool vici_stringify(chunk_t chunk, char *buf, size_t size) +{ + if (!chunk_printable(chunk, NULL, 0)) + { + return FALSE; + } + snprintf(buf, size, "%.*s", (int)chunk.len, chunk.ptr); + return TRUE; +} + +/** + * Verify the occurence of a given type for given section/list nesting + */ +static bool verify_type(vici_type_t type, int section, bool list) +{ + if (list) + { + if (type != VICI_LIST_END && type != VICI_LIST_ITEM) + { + DBG1(DBG_ENC, "'%N' within list", vici_type_names, type); + return FALSE; + } + } + else + { + if (type == VICI_LIST_ITEM || type == VICI_LIST_END) + { + DBG1(DBG_ENC, "'%N' outside list", vici_type_names, type); + return FALSE; + } + } + if (type == VICI_SECTION_END && section == 0) + { + DBG1(DBG_ENC, "'%N' outside of section", vici_type_names, type); + return FALSE; + } + if (type == VICI_END) + { + if (section) + { + DBG1(DBG_ENC, "'%N' within section", vici_type_names, type); + return FALSE; + } + if (list) + { + DBG1(DBG_ENC, "'%N' within list", vici_type_names, type); + return FALSE; + } + } + return TRUE; +} + +/** + * Enumerator parsing message + */ +typedef struct { + /* implements enumerator */ + enumerator_t public; + /** reader to parse from */ + bio_reader_t *reader; + /** section nesting level */ + int section; + /** currently parsing list? */ + bool list; + /** string currently enumerating */ + char name[257]; +} parse_enumerator_t; + +METHOD(enumerator_t, parse_enumerate, bool, + parse_enumerator_t *this, vici_type_t *out, char **name, chunk_t *value) +{ + u_int8_t type; + chunk_t data; + + if (!this->reader->read_uint8(this->reader, &type)) + { + *out = VICI_END; + return TRUE; + } + if (!verify_type(type, this->section, this->list)) + { + return FALSE; + } + + switch (type) + { + case VICI_SECTION_START: + if (!this->reader->read_data8(this->reader, &data) || + !vici_stringify(data, this->name, sizeof(this->name))) + { + DBG1(DBG_ENC, "invalid '%N' encoding", vici_type_names, type); + return FALSE; + } + *name = this->name; + this->section++; + break; + case VICI_SECTION_END: + this->section--; + break; + case VICI_KEY_VALUE: + if (!this->reader->read_data8(this->reader, &data) || + !vici_stringify(data, this->name, sizeof(this->name)) || + !this->reader->read_data16(this->reader, value)) + { + DBG1(DBG_ENC, "invalid '%N' encoding", vici_type_names, type); + return FALSE; + } + *name = this->name; + break; + case VICI_LIST_START: + if (!this->reader->read_data8(this->reader, &data) || + !vici_stringify(data, this->name, sizeof(this->name))) + { + DBG1(DBG_ENC, "invalid '%N' encoding", vici_type_names, type); + return FALSE; + } + *name = this->name; + this->list = TRUE; + break; + case VICI_LIST_ITEM: + this->reader->read_data16(this->reader, value); + break; + case VICI_LIST_END: + this->list = FALSE; + break; + case VICI_END: + return TRUE; + default: + DBG1(DBG_ENC, "unknown encoding type: %u", type); + return FALSE; + } + + *out = type; + + return TRUE; +} + +METHOD(enumerator_t, parse_destroy, void, + parse_enumerator_t *this) +{ + this->reader->destroy(this->reader); + free(this); +} + +METHOD(vici_message_t, create_enumerator, enumerator_t*, + private_vici_message_t *this) +{ + parse_enumerator_t *enumerator; + + INIT(enumerator, + .public = { + .enumerate = (void*)_parse_enumerate, + .destroy = _parse_destroy, + }, + .reader = bio_reader_create(this->encoding), + ); + + return &enumerator->public; +} + +METHOD(vici_message_t, get_encoding, chunk_t, + private_vici_message_t *this) +{ + return this->encoding; +} + +METHOD(vici_message_t, destroy, void, + private_vici_message_t *this) +{ + if (this->cleanup) + { + chunk_clear(&this->encoding); + } + free(this); +} + +/** + * See header + */ +vici_message_t *vici_message_create_from_data(chunk_t data, bool cleanup) +{ + private_vici_message_t *this; + + INIT(this, + .public = { + .create_enumerator = _create_enumerator, + .get_encoding = _get_encoding, + .destroy = _destroy, + }, + .encoding = data, + .cleanup = cleanup, + ); + + return &this->public; +} + +/** + * Write from enumerator to writer + */ +static bool write_from_enumerator(bio_writer_t *writer, + enumerator_t *enumerator) +{ + vici_type_t type; + char *name; + chunk_t value; + int section = 0; + bool list = FALSE; + + while (enumerator->enumerate(enumerator, &type, &name, &value)) + { + if (!verify_type(type, section, list)) + { + return FALSE; + } + + if (type != VICI_END) + { + writer->write_uint8(writer, type); + } + + switch (type) + { + case VICI_SECTION_START: + writer->write_data8(writer, chunk_from_str(name)); + section++; + break; + case VICI_SECTION_END: + section--; + break; + case VICI_KEY_VALUE: + writer->write_data8(writer, chunk_from_str(name)); + writer->write_data16(writer, value); + break; + case VICI_LIST_START: + writer->write_data8(writer, chunk_from_str(name)); + list = TRUE; + break; + case VICI_LIST_ITEM: + writer->write_data16(writer, value); + break; + case VICI_LIST_END: + list = FALSE; + break; + case VICI_END: + return TRUE; + default: + return FALSE; + } + } + return FALSE; +} + +/** + * See header + */ +vici_message_t *vici_message_create_from_enumerator(enumerator_t *enumerator) +{ + vici_message_t *message = NULL; + bio_writer_t *writer; + chunk_t data; + + writer = bio_writer_create(0); + if (write_from_enumerator(writer, enumerator)) + { + data = chunk_clone(writer->get_buf(writer)); + message = vici_message_create_from_data(data, TRUE); + } + enumerator->destroy(enumerator); + writer->destroy(writer); + + return message; +} + +/** + * Enumerator for va_list arguments + */ +typedef struct { + /* implements enumerator */ + enumerator_t public; + /** arguments to enumerate */ + va_list args; + /** first type, if not yet processed */ + vici_type_t *first; +} va_enumerator_t; + +METHOD(enumerator_t, va_enumerate, bool, + va_enumerator_t *this, vici_type_t *out, char **name, chunk_t *value) +{ + vici_type_t type; + + if (this->first) + { + type = *this->first; + this->first = NULL; + } + else + { + type = va_arg(this->args, vici_type_t); + } + switch (type) + { + case VICI_SECTION_END: + case VICI_LIST_END: + case VICI_END: + break; + case VICI_LIST_START: + case VICI_SECTION_START: + *name = va_arg(this->args, char*); + break; + case VICI_KEY_VALUE: + *name = va_arg(this->args, char*); + *value = va_arg(this->args, chunk_t); + break; + case VICI_LIST_ITEM: + *value = va_arg(this->args, chunk_t); + break; + default: + return FALSE; + } + *out = type; + return TRUE; +} + +METHOD(enumerator_t, va_destroy, void, + va_enumerator_t *this) +{ + va_end(this->args); + free(this); +} + +/** + * See header + */ +vici_message_t *vici_message_create_from_args(vici_type_t type, ...) +{ + va_enumerator_t *enumerator; + + INIT(enumerator, + .public = { + .enumerate = (void*)_va_enumerate, + .destroy = _va_destroy, + }, + .first = &type, + ); + va_start(enumerator->args, type); + + return vici_message_create_from_enumerator(&enumerator->public); +} diff --git a/src/libcharon/plugins/vici/vici_message.h b/src/libcharon/plugins/vici/vici_message.h new file mode 100644 index 000000000..81c0be06d --- /dev/null +++ b/src/libcharon/plugins/vici/vici_message.h @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2014 Martin Willi + * Copyright (C) 2014 revosec 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 . + * + * 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. + */ + +/** + * @defgroup vici_message vici_message + * @{ @ingroup vici_dispatcher + */ + +#ifndef VICI_MESSAGE_H_ +#define VICI_MESSAGE_H_ + +#include + +typedef struct vici_message_t vici_message_t; +typedef enum vici_type_t vici_type_t; + +/** + * Vici message encoding types + */ +enum vici_type_t { + /** begin of new section, argument is section name as char* */ + VICI_SECTION_START = 0, + /** end of current section, no arguments */ + VICI_SECTION_END, + /** key/value, arguments are key as char*, value as chunk_t */ + VICI_KEY_VALUE, + /** list start, argument is list name as char* */ + VICI_LIST_START, + /** list item, argument is item value as chunk_t */ + VICI_LIST_ITEM, + /** end of list, no arguments */ + VICI_LIST_END, + + /** end of argument list, no arguments (never encoded) */ + VICI_END +}; + +/** + * Names for vici encoding types + */ +extern enum_name_t *vici_type_names; + +/** + * Vici message representation, encoding/decoding routines. + */ +struct vici_message_t { + + /** + * Create an enumerator over message contents. + * + * The enumerator takes a fixed list of arguments, but depending on the + * type may set not all of them. It returns VICI_END as last argument + * to indicate the message end, and returns FALSE if parsing the message + * failed. + * + * @return enumerator over (vici_type_t, char*, chunk_t) + */ + enumerator_t* (*create_enumerator)(vici_message_t *this); + + /** + * Get encoded message. + * + * @return message data, points to internal data + */ + chunk_t (*get_encoding)(vici_message_t *this); + + /** + * Destroy a vici_message_t. + */ + void (*destroy)(vici_message_t *this); +}; + +/** + * Create a vici_message from encoded data. + * + * @param data message encoding + * @param cleanup TRUE to free data during + * @return message representation + */ +vici_message_t *vici_message_create_from_data(chunk_t data, bool cleanup); + +/** + * Create a vici_message from an enumerator. + * + * The enumerator uses the same signature as the enumerator returned + * by create_enumerator(), and gets destroyed by this function. It should + * return VICI_END to close the message, return FALSE to indicate a failure. + * + * @param enumerator enumerator over (vici_type_t, char*, chunk_t) + * @return message representation, NULL on error + */ +vici_message_t *vici_message_create_from_enumerator(enumerator_t *enumerator); + +/** + * Create vici message from a variable argument list. + * + * @param first first type beginning message + * @param ... vici_type_t and args, terminated by VICI_END + * @return message representation, NULL on error + */ +vici_message_t *vici_message_create_from_args(vici_type_t type, ...); + +/** + * Check if a chunk has a printable string, and print it to buf. + * + * @param chunkt chunk containing potential string + * @param buf buffer to write string to + * @param size size of buf + * @return TRUE if printable and string written to buf + */ +bool vici_stringify(chunk_t chunk, char *buf, size_t size); + +#endif /** VICI_MESSAGE_H_ @}*/ diff --git a/src/libcharon/plugins/vici/vici_socket.c b/src/libcharon/plugins/vici/vici_socket.c new file mode 100644 index 000000000..97c4d81ed --- /dev/null +++ b/src/libcharon/plugins/vici/vici_socket.c @@ -0,0 +1,513 @@ +/* + * Copyright (C) 2014 Martin Willi + * Copyright (C) 2014 revosec 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 . + * + * 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 "vici_socket.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +typedef struct private_vici_socket_t private_vici_socket_t; + +/** + * Private members of vici_socket_t + */ +struct private_vici_socket_t { + + /** + * public functions + */ + vici_socket_t public; + + /** + * Inbound message callback + */ + vici_inbound_cb_t inbound; + + /** + * Client connect callback + */ + vici_connect_cb_t connect; + + /** + * Client disconnect callback + */ + vici_disconnect_cb_t disconnect; + + /** + * Next client connection identifier + */ + u_int nextid; + + /** + * User data for callbacks + */ + void *user; + + /** + * Service accepting vici connections + */ + stream_service_t *service; + + /** + * Client connections, as entry_t + */ + linked_list_t *connections; + + /** + * rwlock for client connection list + */ + rwlock_t *lock; +}; + +/** + * Data to securely reference an entry + */ +typedef struct { + /* reference to socket instance */ + private_vici_socket_t *this; + /** connection identifier to disconnect */ + u_int id; +} entry_data_t; + +/** + * Partially processed message + */ +typedef struct { + /** bytes of length header sent/received */ + u_char hdrlen; + /** bytes of length header */ + char hdr[sizeof(u_int16_t)]; + /** send/receive buffer on heap */ + chunk_t buf; + /** bytes sent/received in buffer */ + u_int16_t done; +} msg_buf_t; + +/** + * Client connection entry + */ +typedef struct { + /** reference to socket */ + private_vici_socket_t *this; + /** mutex to lock this entry in/out buffers */ + mutex_t *mutex; + /** associated stream */ + stream_t *stream; + /** queued messages to send, as msg_buf_t pointers */ + array_t *out; + /** input message buffer */ + msg_buf_t in; + /** client connection identifier */ + u_int id; +} entry_t; + +/** + * Destroy an connection entry + */ +CALLBACK(destroy_entry, void, + entry_t *entry) +{ + msg_buf_t *out; + + entry->stream->destroy(entry->stream); + + entry->this->disconnect(entry->this->user, entry->id); + + entry->mutex->destroy(entry->mutex); + while (array_remove(entry->out, ARRAY_TAIL, &out)) + { + chunk_clear(&out->buf); + free(out); + } + array_destroy(entry->out); + chunk_clear(&entry->in.buf); + free(entry); +} + +/** + * Find/remove entry by id, requires proper locking + */ +static entry_t* find_entry(private_vici_socket_t *this, u_int id, bool remove) +{ + enumerator_t *enumerator; + entry_t *entry, *found = NULL; + + enumerator = this->connections->create_enumerator(this->connections); + while (enumerator->enumerate(enumerator, &entry)) + { + if (entry->id == id) + { + if (remove) + { + this->connections->remove_at(this->connections, enumerator); + } + found = entry; + break; + } + } + enumerator->destroy(enumerator); + + return found; +} + +/** + * Asynchronous callback to disconnect client + */ +CALLBACK(disconnect_async, job_requeue_t, + entry_data_t *data) +{ + entry_t *entry; + + data->this->lock->write_lock(data->this->lock); + entry = find_entry(data->this, data->id, TRUE); + data->this->lock->unlock(data->this->lock); + if (entry) + { + destroy_entry(entry); + } + return JOB_REQUEUE_NONE; +} + +/** + * Disconnect a connected client + */ +static void disconnect(private_vici_socket_t *this, u_int id) +{ + entry_data_t *data; + + INIT(data, + .this = this, + .id = id, + ); + + lib->processor->queue_job(lib->processor, + (job_t*)callback_job_create(disconnect_async, data, free, NULL)); +} + +/** + * Write queued output data + */ +static bool do_write(private_vici_socket_t *this, entry_t *entry, + stream_t *stream) +{ + msg_buf_t *out; + ssize_t len; + + while (array_get(entry->out, ARRAY_HEAD, &out)) + { + /* write header */ + while (out->hdrlen < sizeof(out->hdr)) + { + len = stream->write(stream, out->hdr + out->hdrlen, + sizeof(out->hdr) - out->hdrlen, FALSE); + if (len == 0) + { + return FALSE; + } + if (len < 0) + { + if (errno == EWOULDBLOCK) + { + return TRUE; + } + DBG1(DBG_CFG, "vici header write error: %s", strerror(errno)); + return FALSE; + } + out->hdrlen += len; + } + + /* write buffer buffer */ + while (out->buf.len > out->done) + { + len = stream->write(stream, out->buf.ptr + out->done, + out->buf.len - out->done, FALSE); + if (len == 0) + { + DBG1(DBG_CFG, "premature vici disconnect"); + return FALSE; + } + if (len < 0) + { + if (errno == EWOULDBLOCK) + { + return TRUE; + } + DBG1(DBG_CFG, "vici write error: %s", strerror(errno)); + return FALSE; + } + out->done += len; + } + + if (array_remove(entry->out, ARRAY_HEAD, &out)) + { + chunk_clear(&out->buf); + free(out); + } + } + return TRUE; +} + +/** + * Send pending messages + */ +CALLBACK(on_write, bool, + entry_t *entry, stream_t *stream) +{ + bool ret; + + entry->mutex->lock(entry->mutex); + ret = do_write(entry->this, entry, stream); + if (ret) + { + /* unregister if we have no more messages to send */ + ret = array_count(entry->out) != 0; + } + else + { + disconnect(entry->this, entry->id); + } + entry->mutex->unlock(entry->mutex); + + return ret; +} + +/** + * Read in available header with data, non-blocking cumulating to buffer + */ +static bool do_read(private_vici_socket_t *this, entry_t *entry, + stream_t *stream) +{ + ssize_t len; + + /* assemble the length header first */ + while (entry->in.hdrlen < sizeof(entry->in.hdr)) + { + len = stream->read(stream, entry->in.hdr + entry->in.hdrlen, + sizeof(entry->in.hdr) - entry->in.hdrlen, FALSE); + if (len == 0) + { + return FALSE; + } + if (len < 0) + { + if (errno == EWOULDBLOCK) + { + return TRUE; + } + DBG1(DBG_CFG, "vici header read error: %s", strerror(errno)); + return FALSE; + } + entry->in.hdrlen += len; + if (entry->in.hdrlen == sizeof(entry->in.hdr)) + { + /* header complete, continue with data */ + entry->in.buf = chunk_alloc(untoh16(entry->in.hdr)); + } + } + + /* assemble buffer */ + while (entry->in.buf.len > entry->in.done) + { + len = stream->read(stream, entry->in.buf.ptr + entry->in.done, + entry->in.buf.len - entry->in.done, FALSE); + if (len == 0) + { + DBG1(DBG_CFG, "premature vici disconnect"); + return FALSE; + } + if (len < 0) + { + if (errno == EWOULDBLOCK) + { + return TRUE; + } + DBG1(DBG_CFG, "vici read error: %s", strerror(errno)); + return FALSE; + } + entry->in.done += len; + } + + this->inbound(this->user, entry->id, entry->in.buf); + chunk_clear(&entry->in.buf); + entry->in.hdrlen = entry->in.done = 0; + + return TRUE; +} + +/** + * Process incoming messages + */ +CALLBACK(on_read, bool, + entry_t *entry, stream_t *stream) +{ + bool ret; + + entry->mutex->lock(entry->mutex); + ret = do_read(entry->this, entry, stream); + if (!ret) + { + disconnect(entry->this, entry->id); + } + entry->mutex->unlock(entry->mutex); + + return ret; +} + +/** + * Process connection request + */ +static bool on_accept(private_vici_socket_t *this, stream_t *stream) +{ + entry_t *entry; + u_int id; + + id = ref_get(&this->nextid); + + INIT(entry, + .this = this, + .stream = stream, + .id = id, + .out = array_create(0, 0), + .mutex = mutex_create(MUTEX_TYPE_RECURSIVE), + ); + + this->lock->write_lock(this->lock); + this->connections->insert_last(this->connections, entry); + stream->on_read(stream, on_read, entry); + this->lock->unlock(this->lock); + + this->connect(this->user, id); + + return TRUE; +} + +/** + * Enable on_write callback to send data + */ +CALLBACK(on_write_async, job_requeue_t, + entry_data_t *data) +{ + private_vici_socket_t *this = data->this; + entry_t *entry; + + this->lock->read_lock(this->lock); + entry = find_entry(this, data->id, FALSE); + if (entry) + { + entry->stream->on_write(entry->stream, on_write, entry); + } + this->lock->unlock(this->lock); + + return JOB_REQUEUE_NONE; +} + +METHOD(vici_socket_t, send_, void, + private_vici_socket_t *this, u_int id, chunk_t msg) +{ + if (msg.len <= (u_int16_t)~0) + { + entry_data_t *data; + msg_buf_t *out; + entry_t *entry; + + this->lock->read_lock(this->lock); + entry = find_entry(this, id, FALSE); + if (entry) + { + INIT(out, + .buf = msg, + ); + htoun16(out->hdr, msg.len); + + entry->mutex->lock(entry->mutex); + array_insert(entry->out, ARRAY_TAIL, out); + entry->mutex->unlock(entry->mutex); + + if (array_count(entry->out) == 1) + { + INIT(data, + .this = this, + .id = entry->id, + ); + /* asynchronously enable writing, as this might be called + * from the on_read() callback. */ + lib->processor->queue_job(lib->processor, + (job_t*)callback_job_create(on_write_async, + data, free, NULL)); + } + } + else + { + DBG1(DBG_CFG, "vici connection %u unknown", id); + } + this->lock->unlock(this->lock); + } + else + { + DBG1(DBG_CFG, "vici message exceeds maximum size, discarded"); + chunk_clear(&msg); + } +} + +METHOD(vici_socket_t, destroy, void, + private_vici_socket_t *this) +{ + DESTROY_IF(this->service); + this->connections->destroy_function(this->connections, destroy_entry); + this->lock->destroy(this->lock); + free(this); +} + +/* + * see header file + */ +vici_socket_t *vici_socket_create(char *uri, vici_inbound_cb_t inbound, + vici_connect_cb_t connect, + vici_disconnect_cb_t disconnect, void *user) +{ + private_vici_socket_t *this; + + INIT(this, + .public = { + .send = _send_, + .destroy = _destroy, + }, + .lock = rwlock_create(RWLOCK_TYPE_DEFAULT), + .connections = linked_list_create(), + .inbound = inbound, + .connect = connect, + .disconnect = disconnect, + .user = user, + ); + + this->service = lib->streams->create_service(lib->streams, uri, 3); + if (!this->service) + { + DBG1(DBG_CFG, "creating vici socket failed"); + destroy(this); + return NULL; + } + this->service->on_accept(this->service, (stream_service_cb_t)on_accept, + this, JOB_PRIO_CRITICAL, 0); + + return &this->public; +} diff --git a/src/libcharon/plugins/vici/vici_socket.h b/src/libcharon/plugins/vici/vici_socket.h new file mode 100644 index 000000000..f59199855 --- /dev/null +++ b/src/libcharon/plugins/vici/vici_socket.h @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2014 Martin Willi + * Copyright (C) 2014 revosec 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 . + * + * 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. + */ + +/** + * @defgroup vici_socket vici_socket + * @{ @ingroup vici + */ + +#ifndef VICI_SOCKET_H_ +#define VICI_SOCKET_H_ + +#include + +typedef struct vici_socket_t vici_socket_t; + +/** + * Callback function for dispatching inbound client messages. + * + * @param user user data, as passed during registration + * @param id unique client connection identifier + * @param data incoming message data + */ +typedef void (*vici_inbound_cb_t)(void *user, u_int id, chunk_t data); + +/** + * Callback function invoked when new clients connect + * + * @param user user data, as passed during registration + * @param id unique client connection identifier + * @return client connection context + */ +typedef void (*vici_connect_cb_t)(void *user, u_int id); + +/** + * Callback function invoked when connected clients disconnect + * + * @param user user data, as passed during registration + * @param id unique client connection identifier + */ +typedef void (*vici_disconnect_cb_t)(void *user, u_int id); + +/** + * Vici socket, low level socket input/output handling. + * + * On the socket, we pass raw chunks having a 2 byte network order length + * prefix. The length field does not count the length header itself, and + * is not included in the data passed over this interface. + */ +struct vici_socket_t { + + /** + * Send a message to a client identified by connection identifier. + * + * @param id unique client connection identifier + * @param data data to send to client, gets owned + */ + void (*send)(vici_socket_t *this, u_int id, chunk_t data); + + /** + * Destroy socket. + */ + void (*destroy)(vici_socket_t *this); +}; + +/** + * Create a vici_socket instance. + * + * @param uri socket URI to listen on + * @param inbound inbound message callback + * @param connect connect callback + * @param disconnect disconnect callback + * @param user user data to pass to callbacks + */ +vici_socket_t *vici_socket_create(char *uri, vici_inbound_cb_t inbound, + vici_connect_cb_t connect, + vici_disconnect_cb_t disconnect, void *user); + +#endif /** VICI_SOCKET_H_ @}*/ diff --git a/src/libcharon/plugins/vici/vici_tests.h b/src/libcharon/plugins/vici/vici_tests.h index 05502f18e..bdc296590 100644 --- a/src/libcharon/plugins/vici/vici_tests.h +++ b/src/libcharon/plugins/vici/vici_tests.h @@ -12,3 +12,6 @@ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. */ + +TEST_SUITE(socket_suite_create) +TEST_SUITE(message_suite_create) From 8383d626b982e3da9baf86c73ec20d3df773c53d Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Thu, 23 Jan 2014 15:32:23 +0100 Subject: [PATCH 10/72] vici: Provide a command dispatcher handling request and event registration --- src/libcharon/plugins/vici/Makefile.am | 3 + .../plugins/vici/suites/test_message.c | 65 +++ src/libcharon/plugins/vici/vici_builder.c | 253 ++++++++++ src/libcharon/plugins/vici/vici_builder.h | 129 +++++ src/libcharon/plugins/vici/vici_dispatcher.c | 455 ++++++++++++++++++ src/libcharon/plugins/vici/vici_dispatcher.h | 117 +++++ src/libcharon/plugins/vici/vici_message.c | 202 +++----- src/libcharon/plugins/vici/vici_message.h | 9 +- src/libcharon/plugins/vici/vici_plugin.c | 20 + 9 files changed, 1115 insertions(+), 138 deletions(-) create mode 100644 src/libcharon/plugins/vici/vici_builder.c create mode 100644 src/libcharon/plugins/vici/vici_builder.h create mode 100644 src/libcharon/plugins/vici/vici_dispatcher.c create mode 100644 src/libcharon/plugins/vici/vici_dispatcher.h diff --git a/src/libcharon/plugins/vici/Makefile.am b/src/libcharon/plugins/vici/Makefile.am index d166d594a..3723966ff 100644 --- a/src/libcharon/plugins/vici/Makefile.am +++ b/src/libcharon/plugins/vici/Makefile.am @@ -16,6 +16,8 @@ endif libstrongswan_vici_la_SOURCES = \ vici_socket.h vici_socket.c \ vici_message.h vici_message.c \ + vici_builder.h vici_builder.c \ + vici_dispatcher.h vici_dispatcher.c \ vici_plugin.h vici_plugin.c libstrongswan_vici_la_LDFLAGS = -module -avoid-version @@ -29,6 +31,7 @@ vici_tests_SOURCES = \ suites/test_message.c \ vici_socket.c \ vici_message.c \ + vici_builder.c \ vici_tests.h vici_tests.c vici_tests_CFLAGS = \ diff --git a/src/libcharon/plugins/vici/suites/test_message.c b/src/libcharon/plugins/vici/suites/test_message.c index a35c30905..a8196bd63 100644 --- a/src/libcharon/plugins/vici/suites/test_message.c +++ b/src/libcharon/plugins/vici/suites/test_message.c @@ -16,6 +16,7 @@ #include #include "../vici_message.h" +#include "../vici_builder.h" #include @@ -247,6 +248,62 @@ START_TEST(test_vararg) } END_TEST +START_TEST(test_builder) +{ + enumerator_t *parse, *tmpl; + vici_message_t *m; + vici_builder_t *b; + + b = vici_builder_create(); + b->add(b, VICI_SECTION_START, "section1"); + b->add(b, VICI_LIST_START, "list1"); + b->add(b, VICI_LIST_ITEM, chunk_from_str("item1")); + b->add(b, VICI_LIST_ITEM, chunk_from_str("item2")); + b->add(b, VICI_LIST_END); + b->add(b, VICI_KEY_VALUE, "key1", chunk_from_str("value1")); + b->add(b, VICI_SECTION_END); + m = b->finalize(b); + ck_assert(m); + tmpl = endecode_create_enumerator(endecode_test_list); + parse = m->create_enumerator(m); + ck_assert(parse); + + compare_vici(parse, tmpl); + + m->destroy(m); + tmpl->destroy(tmpl); + parse->destroy(parse); +} +END_TEST + +START_TEST(test_builder_fmt) +{ + enumerator_t *parse, *tmpl; + vici_message_t *m; + vici_builder_t *b; + + b = vici_builder_create(); + b->begin_section(b, "section1"); + b->begin_list(b, "list1"); + b->add_li(b, "item%u", 1); + b->add_li(b, "%s%u", "item", 2); + b->end_list(b); + b->add_kv(b, "key1", "value%u", 1); + b->end_section(b); + m = b->finalize(b); + ck_assert(m); + tmpl = endecode_create_enumerator(endecode_test_list); + parse = m->create_enumerator(m); + ck_assert(parse); + + compare_vici(parse, tmpl); + + m->destroy(m); + tmpl->destroy(tmpl); + parse->destroy(parse); +} +END_TEST + Suite *message_suite_create() { Suite *s; @@ -262,5 +319,13 @@ Suite *message_suite_create() tcase_add_test(tc, test_vararg); suite_add_tcase(s, tc); + tc = tcase_create("builder encode"); + tcase_add_test(tc, test_builder); + suite_add_tcase(s, tc); + + tc = tcase_create("builder format encode"); + tcase_add_test(tc, test_builder_fmt); + suite_add_tcase(s, tc); + return s; } diff --git a/src/libcharon/plugins/vici/vici_builder.c b/src/libcharon/plugins/vici/vici_builder.c new file mode 100644 index 000000000..561632049 --- /dev/null +++ b/src/libcharon/plugins/vici/vici_builder.c @@ -0,0 +1,253 @@ +/* + * Copyright (C) 2014 Martin Willi + * Copyright (C) 2014 revosec 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 . + * + * 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 "vici_builder.h" + +#include + +typedef struct private_vici_builder_t private_vici_builder_t; + +/** + * Private data of an vici_builder_t object. + */ +struct private_vici_builder_t { + + /** + * Public vici_builder_t interface. + */ + vici_builder_t public; + + /** + * Writer for elements + */ + bio_writer_t *writer; + + /** + * Errors encountered + */ + u_int error; + + /** + * Section nesting level + */ + u_int section; + + /** + * In list element? + */ + bool list; +}; + +METHOD(vici_builder_t, add, void, + private_vici_builder_t *this, vici_type_t type, ...) +{ + va_list args; + char *name = NULL; + chunk_t value = chunk_empty; + + va_start(args, type); + switch (type) + { + case VICI_SECTION_END: + case VICI_LIST_END: + case VICI_END: + break; + case VICI_LIST_START: + case VICI_SECTION_START: + name = va_arg(args, char*); + break; + case VICI_KEY_VALUE: + name = va_arg(args, char*); + value = va_arg(args, chunk_t); + break; + case VICI_LIST_ITEM: + value = va_arg(args, chunk_t); + break; + default: + va_end(args); + this->error++; + return; + } + va_end(args); + + if (value.len > 0xffff) + { + this->error++; + return; + } + if (!vici_verify_type(type, this->section, this->list)) + { + this->error++; + return; + } + if (type != VICI_END) + { + this->writer->write_uint8(this->writer, type); + } + switch (type) + { + case VICI_SECTION_START: + this->writer->write_data8(this->writer, chunk_from_str(name)); + this->section++; + break; + case VICI_SECTION_END: + this->section--; + break; + case VICI_KEY_VALUE: + this->writer->write_data8(this->writer, chunk_from_str(name)); + this->writer->write_data16(this->writer, value); + break; + case VICI_LIST_START: + this->writer->write_data8(this->writer, chunk_from_str(name)); + this->list = TRUE; + break; + case VICI_LIST_ITEM: + this->writer->write_data16(this->writer, value); + break; + case VICI_LIST_END: + this->list = FALSE; + break; + default: + this->error++; + break; + } +} + +METHOD(vici_builder_t, vadd_kv, void, + private_vici_builder_t *this, char *key, char *fmt, va_list args) +{ + char buf[2048]; + ssize_t len; + + len = vsnprintf(buf, sizeof(buf), fmt, args); + if (len < 0 || len >= sizeof(buf)) + { + DBG1(DBG_ENC, "vici builder format buffer exceeds limit"); + this->error++; + } + else + { + add(this, VICI_KEY_VALUE, key, chunk_create(buf, len)); + } +} + +METHOD(vici_builder_t, add_kv, void, + private_vici_builder_t *this, char *key, char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + vadd_kv(this, key, fmt, args); + va_end(args); +} + + +METHOD(vici_builder_t, vadd_li, void, + private_vici_builder_t *this, char *fmt, va_list args) +{ + char buf[2048]; + ssize_t len; + + len = vsnprintf(buf, sizeof(buf), fmt, args); + if (len < 0 || len >= sizeof(buf)) + { + DBG1(DBG_ENC, "vici builder format buffer exceeds limit"); + this->error++; + } + else + { + add(this, VICI_LIST_ITEM, chunk_create(buf, len)); + } +} + +METHOD(vici_builder_t, add_li, void, + private_vici_builder_t *this, char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + vadd_li(this, fmt, args); + va_end(args); +} + +METHOD(vici_builder_t, begin_section, void, + private_vici_builder_t *this, char *name) +{ + add(this, VICI_SECTION_START, name); +} + +METHOD(vici_builder_t, end_section, void, + private_vici_builder_t *this) +{ + add(this, VICI_SECTION_END); +} + +METHOD(vici_builder_t, begin_list, void, + private_vici_builder_t *this, char *name) +{ + add(this, VICI_LIST_START, name); +} + +METHOD(vici_builder_t, end_list, void, + private_vici_builder_t *this) +{ + add(this, VICI_LIST_END); +} + +METHOD(vici_builder_t, finalize, vici_message_t*, + private_vici_builder_t *this) +{ + vici_message_t *product; + + if (this->error || this->section || this->list) + { + DBG1(DBG_ENC, "vici builder error: %u errors (section: %u, list %u)", + this->error, this->section, this->list); + this->writer->destroy(this->writer); + free(this); + return NULL; + } + product = vici_message_create_from_data( + this->writer->extract_buf(this->writer), TRUE); + this->writer->destroy(this->writer); + free(this); + return product; +} + +/** + * See header + */ +vici_builder_t *vici_builder_create() +{ + private_vici_builder_t *this; + + INIT(this, + .public = { + .add = _add, + .add_kv = _add_kv, + .vadd_kv = _vadd_kv, + .add_li = _add_li, + .vadd_li = _vadd_li, + .begin_section = _begin_section, + .end_section = _end_section, + .begin_list = _begin_list, + .end_list = _end_list, + .finalize = _finalize, + }, + .writer = bio_writer_create(0), + ); + + return &this->public; +} diff --git a/src/libcharon/plugins/vici/vici_builder.h b/src/libcharon/plugins/vici/vici_builder.h new file mode 100644 index 000000000..5a5cc8a03 --- /dev/null +++ b/src/libcharon/plugins/vici/vici_builder.h @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2014 Martin Willi + * Copyright (C) 2014 revosec 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 . + * + * 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. + */ + +/** + * @defgroup vici_builder vici_builder + * @{ @ingroup vici + */ + +#ifndef VICI_BUILDER_H_ +#define VICI_BUILDER_H_ + +#include "vici_message.h" + +typedef struct vici_builder_t vici_builder_t; + +/** + * Build helper for vici message + */ +struct vici_builder_t { + + /** + * Append a generic message element to message. + * + * The additional arguments are type specific, it may be nothing, a string, + * a chunk value or both. + * + * @param type element type to add + * @param ... additional type specific arguments + */ + void (*add)(vici_builder_t *this, vici_type_t type, ...); + + /** + * Append a key/value element using a format string. + * + * Instead of passing the type specific value as a chunk, this method + * takes a printf() style format string followed by its arguments. The + * key name for a key/value type is still a fixed string. + * + * @param key key name of the key/value to add + * @param fmt value format string + * @param ... arguments to value format string + */ + void (*add_kv)(vici_builder_t *this, char *key, char *fmt, ...); + + /** + * Append a message element using a format string and va_list. + * + * Instead of passing the type specific value as a chunk, this method + * takes a printf() style format string followed by its arguments. The + * key name for a key/value type is still a fixed string. + * + * @param key key name of the key/value to add + * @param fmt value format string + * @param args arguments to value format string + */ + void (*vadd_kv)(vici_builder_t *this, char *key, char *fmt, va_list args); + + /** + * Append a list item element using a format string. + * + * Instead of passing the type specific value as a chunk, this method + * takes a printf() style format string followed by its arguments. + * + * @param fmt value format string + * @param ... arguments to value format string + */ + void (*add_li)(vici_builder_t *this, char *fmt, ...); + + /** + * Append a list item element using a format string and va_list. + * + * Instead of passing the type specific value as a chunk, this method + * takes a printf() style format string followed by its arguments. + * + * @param fmt value format string + * @param args arguments to value format string + */ + void (*vadd_li)(vici_builder_t *this, char *fmt, va_list args); + + /** + * Begin a new section. + * + * @param name name of section to begin + */ + void (*begin_section)(vici_builder_t *this, char *name); + + /** + * End the currently open section. + */ + void (*end_section)(vici_builder_t *this); + + /** + * Begin a new list. + * + * @param name name of list to begin + */ + void (*begin_list)(vici_builder_t *this, char *name); + + /** + * End the currently open list. + */ + void (*end_list)(vici_builder_t *this); + + /** + * Finalize a vici message with all added elements, destroy builder. + * + * @return vici message, NULL on error + */ + vici_message_t* (*finalize)(vici_builder_t *this); +}; + +/** + * Create a vici_builder instance. + */ +vici_builder_t *vici_builder_create(); + +#endif /** VICI_BUILDER_H_ @}*/ diff --git a/src/libcharon/plugins/vici/vici_dispatcher.c b/src/libcharon/plugins/vici/vici_dispatcher.c new file mode 100644 index 000000000..797e51f61 --- /dev/null +++ b/src/libcharon/plugins/vici/vici_dispatcher.c @@ -0,0 +1,455 @@ +/* + * Copyright (C) 2014 Martin Willi + * Copyright (C) 2014 revosec 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 . + * + * 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 "vici_dispatcher.h" +#include "vici_socket.h" + +#include +#include +#include +#include +#include +#include + +typedef struct private_vici_dispatcher_t private_vici_dispatcher_t; + +/** + * Private data of an vici_dispatcher_t object. + */ +struct private_vici_dispatcher_t { + + /** + * Public vici_dispatcher_t interface. + */ + vici_dispatcher_t public; + + /** + * Socket to send/receive messages + */ + vici_socket_t *socket; + + /** + * List of registered commands (char* => command_t*) + */ + hashtable_t *cmds; + + /** + * List of known events, and registered clients (char* => event_t*) + */ + hashtable_t *events; + + /** + * Mutex to lock hashtables + */ + mutex_t *mutex; + + /** + * Condvar to signal command termination + */ + condvar_t *cond; +}; + +/** + * Registered command + */ +typedef struct { + /** command name */ + char *name; + /** callback for command */ + vici_command_cb_t cb; + /** user data to pass to callback */ + void *user; + /** command currently in use? */ + u_int uses; +} command_t; + +/** + * Registered event + */ +typedef struct { + /** event name */ + char *name; + /** registered clients, as u_int */ + array_t *clients; + /** event currently in use? */ + u_int uses; +} event_t; + +/** + * Send a operation code, optionally with name and message + */ +static void send_op(private_vici_dispatcher_t *this, u_int id, + vici_operation_t op, char *name, vici_message_t *message) +{ + bio_writer_t *writer; + u_int len; + + len = sizeof(u_int8_t); + if (name) + { + len += sizeof(u_int8_t) + strlen(name); + } + if (message) + { + len += message->get_encoding(message).len; + } + writer = bio_writer_create(len); + writer->write_uint8(writer, op); + if (name) + { + writer->write_data8(writer, chunk_from_str(name)); + } + if (message) + { + writer->write_data(writer, message->get_encoding(message)); + } + this->socket->send(this->socket, id, writer->extract_buf(writer)); + writer->destroy(writer); +} + +/** + * Register client for event + */ +static void register_event(private_vici_dispatcher_t *this, char *name, + u_int id) +{ + event_t *event; + + this->mutex->lock(this->mutex); + event = this->events->get(this->events, name); + if (event) + { + array_insert(event->clients, ARRAY_TAIL, &id); + } + this->mutex->unlock(this->mutex); + + if (event) + { + send_op(this, id, VICI_EVENT_CONFIRM, NULL, NULL); + } + else + { + send_op(this, id, VICI_EVENT_UNKNOWN, NULL, NULL); + } +} + +/** + * Unregister client for event + */ +static void unregister_event(private_vici_dispatcher_t *this, char *name, + u_int id) +{ + enumerator_t *enumerator; + event_t *event; + u_int *current; + bool found = FALSE; + + this->mutex->lock(this->mutex); + event = this->events->get(this->events, name); + if (event) + { + enumerator = array_create_enumerator(event->clients); + while (enumerator->enumerate(enumerator, ¤t)) + { + if (*current == id) + { + array_remove_at(event->clients, enumerator); + found = TRUE; + break; + } + } + enumerator->destroy(enumerator); + } + this->mutex->unlock(this->mutex); + + if (found) + { + send_op(this, id, VICI_EVENT_CONFIRM, NULL, NULL); + } + else + { + send_op(this, id, VICI_EVENT_UNKNOWN, NULL, NULL); + } +} + +/** + * Process a request message + */ +void process_request(private_vici_dispatcher_t *this, char *name, u_int id, + chunk_t data) +{ + command_t *cmd; + vici_message_t *request, *response = NULL; + + this->mutex->lock(this->mutex); + cmd = this->cmds->get(this->cmds, name); + if (cmd) + { + cmd->uses++; + } + this->mutex->unlock(this->mutex); + + if (cmd) + { + request = vici_message_create_from_data(data, FALSE); + response = cmd->cb(cmd->user, cmd->name, id, request); + request->destroy(request); + + this->mutex->lock(this->mutex); + if (--cmd->uses == 0) + { + this->cond->broadcast(this->cond); + } + this->mutex->unlock(this->mutex); + } + if (response) + { + send_op(this, id, VICI_CMD_RESPONSE, NULL, response); + response->destroy(response); + } + else + { + send_op(this, id, VICI_CMD_UNKNOWN, NULL, NULL); + } +} + +CALLBACK(inbound, void, + private_vici_dispatcher_t *this, u_int id, chunk_t data) +{ + bio_reader_t *reader; + chunk_t chunk; + u_int8_t type; + char name[257]; + + reader = bio_reader_create(data); + if (reader->read_uint8(reader, &type)) + { + switch (type) + { + case VICI_EVENT_REGISTER: + if (reader->read_data8(reader, &chunk) && + vici_stringify(chunk, name, sizeof(name))) + { + register_event(this, name, id); + } + else + { + DBG1(DBG_CFG, "invalid vici register message"); + } + break; + case VICI_EVENT_UNREGISTER: + if (reader->read_data8(reader, &chunk) && + vici_stringify(chunk, name, sizeof(name))) + { + unregister_event(this, name, id); + } + else + { + DBG1(DBG_CFG, "invalid vici unregister message"); + } + break; + case VICI_CMD_REQUEST: + if (reader->read_data8(reader, &chunk) && + vici_stringify(chunk, name, sizeof(name))) + { + process_request(this, name, id, reader->peek(reader)); + } + else + { + DBG1(DBG_CFG, "invalid vici request message"); + } + break; + case VICI_CMD_RESPONSE: + case VICI_EVENT_CONFIRM: + case VICI_EVENT_UNKNOWN: + case VICI_EVENT: + default: + DBG1(DBG_CFG, "unsupported vici operation: %u", type); + break; + } + } + else + { + DBG1(DBG_CFG, "invalid vici message"); + } + reader->destroy(reader); +} + +CALLBACK(connect_, void, + private_vici_dispatcher_t *this, u_int id) +{ +} + +CALLBACK(disconnect, void, + private_vici_dispatcher_t *this, u_int id) +{ + enumerator_t *events, *ids; + event_t *event; + u_int *current; + + /* deregister all clients */ + this->mutex->lock(this->mutex); + events = this->events->create_enumerator(this->events); + while (events->enumerate(events, NULL, &event)) + { + ids = array_create_enumerator(event->clients); + while (ids->enumerate(ids, ¤t)) + { + if (id == *current) + { + array_remove_at(event->clients, ids); + } + } + ids->destroy(ids); + } + events->destroy(events); + this->mutex->unlock(this->mutex); +} + +METHOD(vici_dispatcher_t, manage_command, void, + private_vici_dispatcher_t *this, char *name, + vici_command_cb_t cb, void *user) +{ + command_t *cmd; + + this->mutex->lock(this->mutex); + if (cb) + { + INIT(cmd, + .name = strdup(name), + .cb = cb, + .user = user, + ); + cmd = this->cmds->put(this->cmds, cmd->name, cmd); + } + else + { + cmd = this->cmds->remove(this->cmds, name); + } + if (cmd) + { + while (cmd->uses) + { + this->cond->wait(this->cond, this->mutex); + } + free(cmd->name); + free(cmd); + } + this->mutex->unlock(this->mutex); +} + +METHOD(vici_dispatcher_t, manage_event, void, + private_vici_dispatcher_t *this, char *name, bool reg) +{ + event_t *event; + + this->mutex->lock(this->mutex); + if (reg) + { + INIT(event, + .name = strdup(name), + .clients = array_create(sizeof(u_int), 0), + ); + event = this->events->put(this->events, event->name, event); + } + else + { + event = this->events->remove(this->events, name); + } + if (event) + { + while (event->uses) + { + this->cond->wait(this->cond, this->mutex); + } + array_destroy(event->clients); + free(event->name); + free(event); + } + this->mutex->unlock(this->mutex); +} + +METHOD(vici_dispatcher_t, raise_event, void, + private_vici_dispatcher_t *this, char *name, vici_message_t *message) +{ + enumerator_t *enumerator; + event_t *event; + u_int *id; + + this->mutex->lock(this->mutex); + event = this->events->get(this->events, name); + if (event) + { + event->uses++; + } + this->mutex->unlock(this->mutex); + + enumerator = array_create_enumerator(event->clients); + while (enumerator->enumerate(enumerator, &id)) + { + send_op(this, *id, VICI_EVENT, name, message); + } + enumerator->destroy(enumerator); + + this->mutex->lock(this->mutex); + if (--event->uses == 0) + { + this->cond->broadcast(this->cond); + } + this->mutex->unlock(this->mutex); + + message->destroy(message); +} + +METHOD(vici_dispatcher_t, destroy, void, + private_vici_dispatcher_t *this) +{ + DESTROY_IF(this->socket); + this->mutex->destroy(this->mutex); + this->cond->destroy(this->cond); + this->cmds->destroy(this->cmds); + this->events->destroy(this->events); + free(this); +} + +/** + * See header + */ +vici_dispatcher_t *vici_dispatcher_create(char *uri) +{ + private_vici_dispatcher_t *this; + + INIT(this, + .public = { + .manage_command = _manage_command, + .manage_event = _manage_event, + .raise_event = _raise_event, + .destroy = _destroy, + }, + .cmds = hashtable_create(hashtable_hash_str, hashtable_equals_str, 1), + .events = hashtable_create(hashtable_hash_str, hashtable_equals_str, 1), + .mutex = mutex_create(MUTEX_TYPE_DEFAULT), + .cond = condvar_create(CONDVAR_TYPE_DEFAULT), + ); + + this->socket = vici_socket_create(uri, inbound, connect_, disconnect, this); + if (!this->socket) + { + destroy(this); + return NULL; + } + + return &this->public; +} diff --git a/src/libcharon/plugins/vici/vici_dispatcher.h b/src/libcharon/plugins/vici/vici_dispatcher.h new file mode 100644 index 000000000..518550491 --- /dev/null +++ b/src/libcharon/plugins/vici/vici_dispatcher.h @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2013 Martin Willi + * Copyright (C) 2013 revosec 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 . + * + * 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. + */ + +/** + * @defgroup vici_dispatcher vici_dispatcher + * @{ @ingroup vici + */ + +#ifndef VICI_DISPATCHER_H_ +#define VICI_DISPATCHER_H_ + +#include "vici_message.h" + +typedef struct vici_dispatcher_t vici_dispatcher_t; +typedef enum vici_operation_t vici_operation_t; + +/** + * Default socket URI of vici service + */ +#define VICI_DEFAULT_URI "unix://" IPSEC_PIDDIR "/charon.vici" + +/** + * Kind of vici operation + */ +enum vici_operation_t { + /** a named request message */ + VICI_CMD_REQUEST, + /** an unnamed response message to a request */ + VICI_CMD_RESPONSE, + /** unnamed response if requested command is unknown */ + VICI_CMD_UNKNOWN, + /** a named event registration request */ + VICI_EVENT_REGISTER, + /** a named event unregistration request */ + VICI_EVENT_UNREGISTER, + /** unnamed response for successful event (un-)registration */ + VICI_EVENT_CONFIRM, + /** unnamed response if event (un-)registration failed */ + VICI_EVENT_UNKNOWN, + /** a named event message */ + VICI_EVENT, +}; + +/** + * Vici command callback function + * + * @param user user data, as supplied during registration + * @param name name of the command it has been registered under + * @param id client connection identifier + * @param request request message data + * @return response message + */ +typedef vici_message_t* (*vici_command_cb_t)(void *user, char *name, u_int id, + vici_message_t *request); + +/** + * Vici command dispatcher. + */ +struct vici_dispatcher_t { + + /** + * Register/Unregister a callback invoked for a specific command request. + * + * @param name name of the command + * @param cb callback function to register, NULL to unregister + * @param user user data to pass to callback + */ + void (*manage_command)(vici_dispatcher_t *this, char *name, + vici_command_cb_t cb, void *user); + + /** + * Register/Unregister an event type to send. + * + * The dispatcher internally manages event subscriptions. Clients registered + * for an event will receive such messages when the event is raised. + * + * @param name event name to manager + * @param reg TRUE to register, FALSE to unregister + */ + void (*manage_event)(vici_dispatcher_t *this, char *name, bool reg); + + /** + * Raise an event to all clients registered to that event. + * + * @param name event name to raise + * @param message event message to send, gets destroyed + */ + void (*raise_event)(vici_dispatcher_t *this, char *name, + vici_message_t *message); + + /** + * Destroy a vici_dispatcher_t. + */ + void (*destroy)(vici_dispatcher_t *this); +}; + +/** + * Create a vici_dispatcher instance. + * + * @param uri uri for listening stream service + * @return dispatcher instance + */ +vici_dispatcher_t *vici_dispatcher_create(char *uri); + +#endif /** VICI_DISPATCHER_H_ @}*/ diff --git a/src/libcharon/plugins/vici/vici_message.c b/src/libcharon/plugins/vici/vici_message.c index 7a9e6b6ec..52094550c 100644 --- a/src/libcharon/plugins/vici/vici_message.c +++ b/src/libcharon/plugins/vici/vici_message.c @@ -14,6 +14,7 @@ */ #include "vici_message.h" +#include "vici_builder.h" #include #include @@ -65,9 +66,9 @@ bool vici_stringify(chunk_t chunk, char *buf, size_t size) } /** - * Verify the occurence of a given type for given section/list nesting + * See header. */ -static bool verify_type(vici_type_t type, int section, bool list) +bool vici_verify_type(vici_type_t type, u_int section, bool list) { if (list) { @@ -133,7 +134,7 @@ METHOD(enumerator_t, parse_enumerate, bool, *out = VICI_END; return TRUE; } - if (!verify_type(type, this->section, this->list)) + if (!vici_verify_type(type, this->section, this->list)) { return FALSE; } @@ -250,138 +251,44 @@ vici_message_t *vici_message_create_from_data(chunk_t data, bool cleanup) return &this->public; } -/** - * Write from enumerator to writer - */ -static bool write_from_enumerator(bio_writer_t *writer, - enumerator_t *enumerator) -{ - vici_type_t type; - char *name; - chunk_t value; - int section = 0; - bool list = FALSE; - - while (enumerator->enumerate(enumerator, &type, &name, &value)) - { - if (!verify_type(type, section, list)) - { - return FALSE; - } - - if (type != VICI_END) - { - writer->write_uint8(writer, type); - } - - switch (type) - { - case VICI_SECTION_START: - writer->write_data8(writer, chunk_from_str(name)); - section++; - break; - case VICI_SECTION_END: - section--; - break; - case VICI_KEY_VALUE: - writer->write_data8(writer, chunk_from_str(name)); - writer->write_data16(writer, value); - break; - case VICI_LIST_START: - writer->write_data8(writer, chunk_from_str(name)); - list = TRUE; - break; - case VICI_LIST_ITEM: - writer->write_data16(writer, value); - break; - case VICI_LIST_END: - list = FALSE; - break; - case VICI_END: - return TRUE; - default: - return FALSE; - } - } - return FALSE; -} - /** * See header */ vici_message_t *vici_message_create_from_enumerator(enumerator_t *enumerator) { - vici_message_t *message = NULL; - bio_writer_t *writer; - chunk_t data; + vici_builder_t *builder; + vici_type_t type; + char *name; + chunk_t value; - writer = bio_writer_create(0); - if (write_from_enumerator(writer, enumerator)) + builder = vici_builder_create(); + while (enumerator->enumerate(enumerator, &type, &name, &value)) { - data = chunk_clone(writer->get_buf(writer)); - message = vici_message_create_from_data(data, TRUE); + switch (type) + { + case VICI_SECTION_START: + case VICI_LIST_START: + builder->add(builder, type, name); + continue; + case VICI_KEY_VALUE: + builder->add(builder, type, name, value); + continue; + case VICI_LIST_ITEM: + builder->add(builder, type, value); + continue; + case VICI_SECTION_END: + case VICI_LIST_END: + default: + builder->add(builder, type); + continue; + case VICI_END: + break; + } + break; } enumerator->destroy(enumerator); - writer->destroy(writer); - return message; -} - -/** - * Enumerator for va_list arguments - */ -typedef struct { - /* implements enumerator */ - enumerator_t public; - /** arguments to enumerate */ - va_list args; - /** first type, if not yet processed */ - vici_type_t *first; -} va_enumerator_t; - -METHOD(enumerator_t, va_enumerate, bool, - va_enumerator_t *this, vici_type_t *out, char **name, chunk_t *value) -{ - vici_type_t type; - - if (this->first) - { - type = *this->first; - this->first = NULL; - } - else - { - type = va_arg(this->args, vici_type_t); - } - switch (type) - { - case VICI_SECTION_END: - case VICI_LIST_END: - case VICI_END: - break; - case VICI_LIST_START: - case VICI_SECTION_START: - *name = va_arg(this->args, char*); - break; - case VICI_KEY_VALUE: - *name = va_arg(this->args, char*); - *value = va_arg(this->args, chunk_t); - break; - case VICI_LIST_ITEM: - *value = va_arg(this->args, chunk_t); - break; - default: - return FALSE; - } - *out = type; - return TRUE; -} - -METHOD(enumerator_t, va_destroy, void, - va_enumerator_t *this) -{ - va_end(this->args); - free(this); + return builder->finalize(builder); } /** @@ -389,16 +296,39 @@ METHOD(enumerator_t, va_destroy, void, */ vici_message_t *vici_message_create_from_args(vici_type_t type, ...) { - va_enumerator_t *enumerator; + vici_builder_t *builder; + va_list args; + char *name; + chunk_t value; - INIT(enumerator, - .public = { - .enumerate = (void*)_va_enumerate, - .destroy = _va_destroy, - }, - .first = &type, - ); - va_start(enumerator->args, type); - - return vici_message_create_from_enumerator(&enumerator->public); + builder = vici_builder_create(); + va_start(args, type); + while (type != VICI_END) + { + switch (type) + { + case VICI_LIST_START: + case VICI_SECTION_START: + name = va_arg(args, char*); + builder->add(builder, type, name); + break; + case VICI_KEY_VALUE: + name = va_arg(args, char*); + value = va_arg(args, chunk_t); + builder->add(builder, type, name, value); + break; + case VICI_LIST_ITEM: + value = va_arg(args, chunk_t); + builder->add(builder, type, value); + break; + case VICI_SECTION_END: + case VICI_LIST_END: + default: + builder->add(builder, type); + break; + } + type = va_arg(args, vici_type_t); + } + va_end(args); + return builder->finalize(builder); } diff --git a/src/libcharon/plugins/vici/vici_message.h b/src/libcharon/plugins/vici/vici_message.h index 81c0be06d..a7f506a20 100644 --- a/src/libcharon/plugins/vici/vici_message.h +++ b/src/libcharon/plugins/vici/vici_message.h @@ -106,7 +106,7 @@ vici_message_t *vici_message_create_from_enumerator(enumerator_t *enumerator); /** * Create vici message from a variable argument list. * - * @param first first type beginning message + * @param type first type beginning message * @param ... vici_type_t and args, terminated by VICI_END * @return message representation, NULL on error */ @@ -115,11 +115,16 @@ vici_message_t *vici_message_create_from_args(vici_type_t type, ...); /** * Check if a chunk has a printable string, and print it to buf. * - * @param chunkt chunk containing potential string + * @param chunk chunk containing potential string * @param buf buffer to write string to * @param size size of buf * @return TRUE if printable and string written to buf */ bool vici_stringify(chunk_t chunk, char *buf, size_t size); +/** + * Verify the occurence of a given type for given section/list nesting + */ +bool vici_verify_type(vici_type_t type, u_int section, bool list); + #endif /** VICI_MESSAGE_H_ @}*/ diff --git a/src/libcharon/plugins/vici/vici_plugin.c b/src/libcharon/plugins/vici/vici_plugin.c index cbe3b15d4..855dbc930 100644 --- a/src/libcharon/plugins/vici/vici_plugin.c +++ b/src/libcharon/plugins/vici/vici_plugin.c @@ -14,8 +14,10 @@ */ #include "vici_plugin.h" +#include "vici_dispatcher.h" #include +#include typedef struct private_vici_plugin_t private_vici_plugin_t; @@ -28,6 +30,11 @@ struct private_vici_plugin_t { * public functions */ vici_plugin_t public; + + /** + * Dispatcher, creating socket + */ + vici_dispatcher_t *dispatcher; }; METHOD(plugin_t, get_name, char*, @@ -42,6 +49,19 @@ METHOD(plugin_t, get_name, char*, static bool register_vici(private_vici_plugin_t *this, plugin_feature_t *feature, bool reg, void *data) { + if (reg) + { + char *uri; + + uri = lib->settings->get_str(lib->settings, "%s.plugins.vici.socket", + VICI_DEFAULT_URI, lib->ns); + this->dispatcher = vici_dispatcher_create(uri); + return this->dispatcher != NULL; + } + else + { + this->dispatcher->destroy(this->dispatcher); + } return TRUE; } From eb4fd014b82e8f187581437fc2f67d97684ba544 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Wed, 29 Jan 2014 11:20:20 +0100 Subject: [PATCH 11/72] vici: Add a libvici low-level client library --- src/libcharon/plugins/vici/Makefile.am | 15 + src/libcharon/plugins/vici/libvici.c | 665 ++++++++++++++++++ src/libcharon/plugins/vici/libvici.h | 344 +++++++++ .../plugins/vici/suites/test_event.c | 144 ++++ .../plugins/vici/suites/test_request.c | 243 +++++++ src/libcharon/plugins/vici/vici_tests.h | 2 + 6 files changed, 1413 insertions(+) create mode 100644 src/libcharon/plugins/vici/libvici.c create mode 100644 src/libcharon/plugins/vici/libvici.h create mode 100644 src/libcharon/plugins/vici/suites/test_event.c create mode 100644 src/libcharon/plugins/vici/suites/test_request.c diff --git a/src/libcharon/plugins/vici/Makefile.am b/src/libcharon/plugins/vici/Makefile.am index 3723966ff..6495c1807 100644 --- a/src/libcharon/plugins/vici/Makefile.am +++ b/src/libcharon/plugins/vici/Makefile.am @@ -22,6 +22,17 @@ libstrongswan_vici_la_SOURCES = \ libstrongswan_vici_la_LDFLAGS = -module -avoid-version + +lib_LTLIBRARIES = libvici.la + +libvici_la_SOURCES = \ + vici_message.c vici_message.h \ + vici_builder.c vici_builder.h \ + libvici.c libvici.h + +libvici_la_LIBADD = $(top_builddir)/src/libstrongswan/libstrongswan.la + + TESTS = vici_tests check_PROGRAMS = $(TESTS) @@ -29,9 +40,13 @@ check_PROGRAMS = $(TESTS) vici_tests_SOURCES = \ suites/test_socket.c \ suites/test_message.c \ + suites/test_request.c \ + suites/test_event.c \ vici_socket.c \ vici_message.c \ vici_builder.c \ + vici_dispatcher.c \ + libvici.c \ vici_tests.h vici_tests.c vici_tests_CFLAGS = \ diff --git a/src/libcharon/plugins/vici/libvici.c b/src/libcharon/plugins/vici/libvici.c new file mode 100644 index 000000000..0776b6b9c --- /dev/null +++ b/src/libcharon/plugins/vici/libvici.c @@ -0,0 +1,665 @@ +/* + * Copyright (C) 2014 Martin Willi + * Copyright (C) 2014 revosec 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 . + * + * 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 "libvici.h" +#include "vici_builder.h" +#include "vici_dispatcher.h" + +#include +#include +#include +#include + +#include + +/** + * Event registration + */ +typedef struct { + /** name of event */ + char *name; + /** callback function */ + vici_event_cb_t cb; + /** user data for callback */ + void *user; +} event_t; + +/** + * Wait state signaled by asynchronous on_read callback + */ +typedef enum { + WAIT_IDLE = 0, + WAIT_SUCCESS, + WAIT_FAILED, + WAIT_READ_ERROR, +} wait_state_t; + +/** + * Private vici connection contex. + */ +struct vici_conn_t { + /** connection stream */ + stream_t *stream; + /** event registrations, as char* => event_t */ + hashtable_t *events; + /** connection lock */ + mutex_t *mutex; + /** condvar to signal incoming response */ + condvar_t *cond; + /** queued response message */ + chunk_t queue; + /** asynchronous read error */ + int error; + /** wait state */ + wait_state_t wait; +}; + +/** + * Private vici request message. + */ +struct vici_req_t { + /** connection context */ + vici_conn_t *conn; + /** name of request message */ + char *name; + /** message builder */ + vici_builder_t *b; +}; + +/** + * Private vici response/event message. + */ +struct vici_res_t { + /** response message */ + vici_message_t *message; + /** allocated strings */ + linked_list_t *strings; + /** item enumerator */ + enumerator_t *enumerator; + /** currently enumerating type */ + vici_type_t type; + /** currently enumerating name */ + char *name; + /** currently enumerating value */ + chunk_t value; +}; + +/** + * Signal wait result for waiting user thread + */ +static bool wait_result(vici_conn_t *conn, wait_state_t wait) +{ + conn->mutex->lock(conn->mutex); + conn->wait = wait; + conn->mutex->unlock(conn->mutex); + conn->cond->signal(conn->cond); + return FALSE; +} + +/** + * Signal wait error result for waiting user thread + */ +static bool read_error(vici_conn_t *conn, int err) +{ + conn->error = err; + return wait_result(conn, WAIT_READ_ERROR); +} + +/** + * Handle a command response message + */ +static bool handle_response(vici_conn_t *conn, u_int16_t len) +{ + chunk_t buf; + + buf = chunk_alloc(len); + if (!conn->stream->read_all(conn->stream, buf.ptr, buf.len)) + { + free(buf.ptr); + return read_error(conn, errno); + } + conn->queue = buf; + return wait_result(conn, WAIT_SUCCESS); +} + +/** + * Dispatch received event message + */ +static bool handle_event(vici_conn_t *conn, u_int16_t len) +{ + vici_message_t *message; + event_t *event; + u_int8_t namelen; + char name[257], *buf; + + if (len < sizeof(namelen)) + { + return read_error(conn, EBADMSG); + } + if (!conn->stream->read_all(conn->stream, &namelen, sizeof(namelen))) + { + return read_error(conn, errno); + } + if (namelen > len - sizeof(namelen)) + { + return read_error(conn, EBADMSG); + } + if (!conn->stream->read_all(conn->stream, name, namelen)) + { + return read_error(conn, errno); + } + name[namelen] = '\0'; + len -= sizeof(namelen) + namelen; + buf = malloc(len); + if (!conn->stream->read_all(conn->stream, buf, len)) + { + free(buf); + return read_error(conn, errno); + } + message = vici_message_create_from_data(chunk_create(buf, len), TRUE); + + conn->mutex->lock(conn->mutex); + event = conn->events->get(conn->events, name); + if (event) + { + vici_res_t res = { + .message = message, + .enumerator = message->create_enumerator(message), + .strings = linked_list_create(), + }; + + event->cb(event->user, name, &res); + + res.enumerator->destroy(res.enumerator); + res.strings->destroy_function(res.strings, free); + } + conn->mutex->unlock(conn->mutex); + + message->destroy(message); + + return TRUE; +} + +CALLBACK(on_read, bool, + vici_conn_t *conn, stream_t *stream) +{ + u_int16_t len; + u_int8_t op; + + if (!stream->read_all(stream, &len, sizeof(len))) + { + return read_error(conn, errno); + } + len = ntohs(len); + if (len-- < sizeof(op)) + { + return read_error(conn, EBADMSG); + } + if (!stream->read_all(stream, &op, sizeof(op))) + { + return read_error(conn, errno); + } + switch (op) + { + case VICI_EVENT: + return handle_event(conn, len); + case VICI_CMD_RESPONSE: + return handle_response(conn, len); + case VICI_EVENT_CONFIRM: + return wait_result(conn, WAIT_SUCCESS); + case VICI_CMD_UNKNOWN: + case VICI_EVENT_UNKNOWN: + return wait_result(conn, WAIT_FAILED); + case VICI_CMD_REQUEST: + case VICI_EVENT_REGISTER: + case VICI_EVENT_UNREGISTER: + default: + return read_error(conn, EBADMSG); + } +} + +vici_conn_t* vici_connect(char *uri) +{ + vici_conn_t *conn; + stream_t *stream; + + stream = lib->streams->connect(lib->streams, uri ?: VICI_DEFAULT_URI); + if (!stream) + { + return NULL; + } + + INIT(conn, + .stream = stream, + .events = hashtable_create(hashtable_hash_str, hashtable_equals_str, 1), + .mutex = mutex_create(MUTEX_TYPE_DEFAULT), + .cond = condvar_create(CONDVAR_TYPE_DEFAULT), + ); + + stream->on_read(stream, on_read, conn); + + return conn; +} + +void vici_disconnect(vici_conn_t *conn) +{ + enumerator_t *enumerator; + event_t *event; + + conn->stream->destroy(conn->stream); + enumerator = conn->events->create_enumerator(conn->events); + while (enumerator->enumerate(enumerator, NULL, &event)) + { + free(event->name); + free(event); + } + enumerator->destroy(enumerator); + conn->events->destroy(conn->events); + conn->mutex->destroy(conn->mutex); + conn->cond->destroy(conn->cond); + free(conn); +} + +vici_req_t* vici_begin(char *name) +{ + vici_req_t *req; + + INIT(req, + .name = strdup(name), + .b = vici_builder_create(), + ); + + return req; +} + +void vici_begin_section(vici_req_t *req, char *name) +{ + req->b->add(req->b, VICI_SECTION_START, name); +} + +void vici_end_section(vici_req_t *req) +{ + req->b->add(req->b, VICI_SECTION_END); +} + +void vici_add_key_value(vici_req_t *req, char *key, void *buf, int len) +{ + req->b->add(req->b, VICI_KEY_VALUE, key, chunk_create(buf, len)); +} + +void vici_add_key_valuef(vici_req_t *req, char *key, char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + req->b->vadd_kv(req->b, key, fmt, args); + va_end(args); +} + +void vici_begin_list(vici_req_t *req, char *name) +{ + req->b->add(req->b, VICI_LIST_START, name); +} + +void vici_add_list_item(vici_req_t *req, void *buf, int len) +{ + req->b->add(req->b, VICI_LIST_ITEM, chunk_create(buf, len)); +} + +void vici_add_list_itemf(vici_req_t *req, char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + req->b->vadd_li(req->b, fmt, args); + va_end(args); +} + +void vici_end_list(vici_req_t *req) +{ + req->b->add(req->b, VICI_LIST_END); +} + +vici_res_t* vici_submit(vici_req_t *req, vici_conn_t *conn) +{ + vici_message_t *message; + vici_res_t *res; + chunk_t data; + u_int16_t len; + u_int8_t namelen, op; + + message = req->b->finalize(req->b); + if (!message) + { + errno = EINVAL; + return NULL; + } + + op = VICI_CMD_REQUEST; + namelen = strlen(req->name); + data = message->get_encoding(message); + len = htons(sizeof(op) + sizeof(namelen) + namelen + data.len); + + if (!conn->stream->write_all(conn->stream, &len, sizeof(len)) || + !conn->stream->write_all(conn->stream, &op, sizeof(op)) || + !conn->stream->write_all(conn->stream, &namelen, sizeof(namelen)) || + !conn->stream->write_all(conn->stream, req->name, namelen) || + !conn->stream->write_all(conn->stream, data.ptr, data.len)) + { + free(req->name); + free(req); + message->destroy(message); + return NULL; + } + free(req->name); + free(req); + message->destroy(message); + + message = NULL; + conn->mutex->lock(conn->mutex); + while (conn->wait == WAIT_IDLE) + { + conn->cond->wait(conn->cond, conn->mutex); + } + switch (conn->wait) + { + case WAIT_SUCCESS: + message = vici_message_create_from_data(conn->queue, TRUE); + conn->queue = chunk_empty; + break; + case WAIT_READ_ERROR: + errno = conn->error; + break; + case WAIT_FAILED: + default: + errno = ENOENT; + break; + } + conn->wait = WAIT_IDLE; + conn->mutex->unlock(conn->mutex); + + conn->stream->on_read(conn->stream, on_read, conn); + + if (message) + { + INIT(res, + .message = message, + .enumerator = message->create_enumerator(message), + .strings = linked_list_create(), + ); + return res; + } + return NULL; +} + +void vici_free_req(vici_req_t *req) +{ + vici_message_t *message; + + free(req->name); + message = req->b->finalize(req->b); + if (message) + { + message->destroy(message); + } + free(req); +} + +int vici_dump(vici_res_t *res, FILE *out) +{ + enumerator_t *enumerator; + int ident = 0, delta = 2; + vici_type_t type; + char *name; + chunk_t value; + + enumerator = res->message->create_enumerator(res->message); + while (enumerator->enumerate(enumerator, &type, &name, &value)) + { + switch (type) + { + case VICI_SECTION_START: + fprintf(out, "%*s%s {\n", ident, "", name); + ident += delta; + break; + case VICI_SECTION_END: + ident -= delta; + fprintf(out, "%*s}\n", ident, ""); + break; + case VICI_KEY_VALUE: + if (chunk_printable(value, NULL, ' ')) + { + fprintf(out, "%*s%s = %.*s\n", + ident, "", name, (int)value.len, value.ptr); + } + else + { + fprintf(out, "%*s%s = 0x%+#B\n", + ident, "", name, &value); + } + break; + case VICI_LIST_START: + fprintf(out, "%*s%s = [\n", ident, "", name); + ident += delta; + break; + case VICI_LIST_END: + ident -= delta; + fprintf(out, "%*s]\n", ident, ""); + break; + case VICI_LIST_ITEM: + if (chunk_printable(value, NULL, ' ')) + { + fprintf(out, "%*s%.*s\n", + ident, "", (int)value.len, value.ptr); + } + else + { + fprintf(out, "%*s 0x%+#B\n", ident, "", &value); + } + break; + case VICI_END: + enumerator->destroy(enumerator); + return 0; + } + } + enumerator->destroy(enumerator); + errno = EBADMSG; + return 1; +} + +vici_parse_t vici_parse(vici_res_t *res) +{ + if (!res->enumerator->enumerate(res->enumerator, + &res->type, &res->name, &res->value)) + { + return VICI_PARSE_ERROR; + } + switch (res->type) + { + case VICI_END: + return VICI_PARSE_END; + case VICI_SECTION_START: + return VICI_PARSE_BEGIN_SECTION; + case VICI_SECTION_END: + return VICI_PARSE_END_SECTION; + case VICI_LIST_START: + return VICI_PARSE_BEGIN_LIST; + case VICI_LIST_ITEM: + return VICI_PARSE_LIST_ITEM; + case VICI_LIST_END: + return VICI_PARSE_END_LIST; + case VICI_KEY_VALUE: + return VICI_PARSE_KEY_VALUE; + default: + return VICI_PARSE_ERROR; + } +} + +char* vici_parse_name(vici_res_t *res) +{ + char *name; + + switch (res->type) + { + case VICI_SECTION_START: + case VICI_LIST_START: + case VICI_KEY_VALUE: + name = strdup(res->name); + res->strings->insert_last(res->strings, name); + return name; + default: + errno = EINVAL; + return NULL; + } +} + +int vici_parse_name_eq(vici_res_t *res, char *name) +{ + switch (res->type) + { + case VICI_SECTION_START: + case VICI_LIST_START: + case VICI_KEY_VALUE: + return streq(name, res->name) ? 1 : 0; + default: + return 0; + } +} + +void* vici_parse_value(vici_res_t *res, int *len) +{ + switch (res->type) + { + case VICI_LIST_ITEM: + case VICI_KEY_VALUE: + *len = res->value.len; + return res->value.ptr; + default: + errno = EINVAL; + return NULL; + } +} + +char* vici_parse_value_str(vici_res_t *res) +{ + char *val; + + switch (res->type) + { + case VICI_LIST_ITEM: + case VICI_KEY_VALUE: + if (!chunk_printable(res->value, NULL, 0)) + { + errno = EINVAL; + return NULL; + } + val = strndup(res->value.ptr, res->value.len); + res->strings->insert_last(res->strings, val); + return val; + default: + errno = EINVAL; + return NULL; + } +} + +void vici_free_res(vici_res_t *res) +{ + res->strings->destroy_function(res->strings, free); + res->message->destroy(res->message); + res->enumerator->destroy(res->enumerator); + free(res); +} + +int vici_register(vici_conn_t *conn, char *name, vici_event_cb_t cb, void *user) +{ + event_t *event; + u_int16_t len; + u_int8_t namelen, op; + int ret = 1; + + op = cb ? VICI_EVENT_REGISTER : VICI_EVENT_UNREGISTER; + namelen = strlen(name); + len = htons(sizeof(op) + sizeof(namelen) + namelen); + if (!conn->stream->write_all(conn->stream, &len, sizeof(len)) || + !conn->stream->write_all(conn->stream, &op, sizeof(op)) || + !conn->stream->write_all(conn->stream, &namelen, sizeof(namelen)) || + !conn->stream->write_all(conn->stream, name, namelen)) + { + return 1; + } + + conn->mutex->lock(conn->mutex); + while (conn->wait == WAIT_IDLE) + { + conn->cond->wait(conn->cond, conn->mutex); + } + switch (conn->wait) + { + case WAIT_SUCCESS: + ret = 0; + break; + case WAIT_READ_ERROR: + errno = conn->error; + break; + case WAIT_FAILED: + default: + errno = ENOENT; + break; + } + conn->wait = WAIT_IDLE; + conn->mutex->unlock(conn->mutex); + + conn->stream->on_read(conn->stream, on_read, conn); + + if (ret == 0) + { + conn->mutex->lock(conn->mutex); + if (cb) + { + INIT(event, + .name = strdup(name), + .cb = cb, + .user = user, + ); + event = conn->events->put(conn->events, event->name, event); + } + else + { + event = conn->events->remove(conn->events, name); + } + conn->mutex->unlock(conn->mutex); + + if (event) + { + free(event->name); + free(event); + } + } + return ret; +} + +void vici_init() +{ + library_init(NULL, "vici"); + if (lib->processor->get_total_threads(lib->processor) < 4) + { + lib->processor->set_threads(lib->processor, 4); + } +} + +void vici_deinit() +{ + library_deinit(); +} diff --git a/src/libcharon/plugins/vici/libvici.h b/src/libcharon/plugins/vici/libvici.h new file mode 100644 index 000000000..329c67c47 --- /dev/null +++ b/src/libcharon/plugins/vici/libvici.h @@ -0,0 +1,344 @@ +/* + * Copyright (C) 2014 Martin Willi + * Copyright (C) 2014 revosec 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 . + * + * 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. + */ + +/** + * @defgroup libvici libvici + * @{ @ingroup vici + * + * libvici is a low-level client library for the "Versatile IKE Control + * Interface" protocol. While it uses libstrongswan and its thread-pool for + * asynchronous message delivery, this interface does not directly depend on + * libstrongswan interfaces and should be stable. + * + * This interface provides the following basic functions: + * + * - vici_init()/vici_deinit(): Library initialization functions + * - vici_connect(): Connect to a vici service + * - vici_disconnect(): Disconnect from a vici service + * + * Library initialization is basically required to set up libstrongswan and + * a small thread pool. Initialize libstrongswan manually instead. + * + * Connecting requires an uri, which is currently either a UNIX socket path + * prefixed with unix://, or a hostname:port touple prefixed with tcp://. + * Passing NULL takes the system default socket path. + * + * After the connection has been established, request messages can be sent. + * Only a single thread may operate on a single connection instance + * simultaneously. To construct request messages, use the following functions: + * + * - vici_add_key_value() / vici_add_key_valuef(): Add key/value pairs + * - vici_begin(): Start constructing a new request message + * - vici_begin_section(): Open a new section to add contents to + * - vici_end_section(): Close a previously opened session + * - vici_begin_list(): Open a new list to add list items to + * - vici_end_list(): Close a previously opened list + * - vici_add_list_item() / vici_add_list_itemf(): Add list item + * + * Once the request message is complete, it can be sent or cancelled with: + * + * - vici_submit() + * - vici_free_req() + * + * If submitting a message is successful, a response message is returned. It + * can be processed using the following functions: + * + * - vici_parse(): Parse content type + * - vici_parse_name(): Parse name if content type provides one + * - vici_parse_name_eq(): Parse name and check if matches string + * - vici_parse_value() / vici_parse_value_str(): Parse value for content type + * - vici_dump(): Dump a full response to a FILE stream + * - vici_free_res(): Free response after use + * + * Usually vici_parse() is called in a loop, and depending on the returned + * type the name and value can be inspected. + * + * To register or unregister for asynchronous event messages vici_register() is + * used. The registered callback gets invoked by an asynchronous thread. To + * parse the event message, the vici_parse*() functions can be used. + */ + +#ifndef LIBVICI_H_ +#define LIBVICI_H_ + +#include + +/** + * Opaque vici connection contex. + */ +typedef struct vici_conn_t vici_conn_t; + +/** + * Opaque vici request message. + */ +typedef struct vici_req_t vici_req_t; + +/** + * Opaque vici response/event message. + */ +typedef struct vici_res_t vici_res_t; + +/** + * Vici parse result, as returned by vici_parse(). + */ +typedef enum { + /** encountered a section start, has a name */ + VICI_PARSE_BEGIN_SECTION, + /** encountered a section end */ + VICI_PARSE_END_SECTION, + /** encountered a list start, has a name */ + VICI_PARSE_BEGIN_LIST, + /** encountered a list element, has a value */ + VICI_PARSE_LIST_ITEM, + /** encountered a list end */ + VICI_PARSE_END_LIST, + /** encountered a key/value pair, has a name and a value */ + VICI_PARSE_KEY_VALUE, + /** encountered valid end of message */ + VICI_PARSE_END, + /** parse error */ + VICI_PARSE_ERROR, +} vici_parse_t; + +/** + * Callback function invoked for received event messages. + * + * It is not allowed to call vici_submit() from this callback. + * + * @param user user data, as passed to vici_connect + * @param name name of received event + * @param msg associated event message, destroyed by libvici + */ +typedef void (*vici_event_cb_t)(void *user, char *name, vici_res_t *msg); + +/** + * Open a new vici connection. + * + * On error, NULL is returned and errno is set appropriately. + * + * @param uri URI to connect to, NULL to use system default + * @return opaque vici connection context, NULL on error + */ +vici_conn_t* vici_connect(char *uri); + +/** + * Close a vici connection. + * + * @param conn connection context + */ +void vici_disconnect(vici_conn_t *conn); + +/** + * Begin a new vici message request. + * + * This function always succeeds. + * + * @param name name of request command + * @return request message, to add contents + */ +vici_req_t* vici_begin(char *name); + +/** + * Begin a new section in a vici request message. + * + * @param req request message to create a new section in + * @param name name of section to create + */ +void vici_begin_section(vici_req_t *req, char *name); + +/** + * End a previously opened section. + * + * @param req request message to close an open section in + */ +void vici_end_section(vici_req_t *req); + +/** + * Add a key/value pair, using an as-is blob as value. + * + * @param req request message to add key/value pair to + * @param key key name of key/value pair + * @param buf pointer to blob to add as value + * @param len length of value blob to add + */ +void vici_add_key_value(vici_req_t *req, char *key, void *buf, int len); + +/** + * Add a key/value pair, setting value from a printf() format string. + * + * @param req request message to add key/value pair to + * @param key key name of key/value pair + * @param fmt format string for value + * @param ... arguments to format string + */ +void vici_add_key_valuef(vici_req_t *req, char *key, char *fmt, ...); + +/** + * Begin a list in a request message. + * + * After starting a list, only list items can be added until the list gets + * closed by vici_end_list(). + * + * @param req request message to begin list in + * @param name name of list to begin + */ +void vici_begin_list(vici_req_t *req, char *name); + +/** + * Add a list item to a currently open list, using an as-is blob. + * + * @param req request message to add list item to + * @param buf pointer to blob to add as value + * @param len length of value blob to add + */ +void vici_add_list_item(vici_req_t *req, void *buf, int len); + +/** + * Add a list item to a currently open list, using a printf() format string. + * + * @param req request message to add list item to + * @param fmt format string to create value from + * @param ... arguments to format string + */ +void vici_add_list_itemf(vici_req_t *req, char *fmt, ...); + +/** + * End a previously opened list in a request message. + * + * @param req request message to end list in + */ +void vici_end_list(vici_req_t *req); + +/** + * Submit a request message, and wait for response. + * + * On error, NULL is returned an errno is set appropriately. The request + * messages gets cleaned up by this call and gets invalid. + * + * @param req request message to send + * @param conn connection context to send message over + * @return response message, NULL on error + */ +vici_res_t* vici_submit(vici_req_t *req, vici_conn_t *conn); + +/** + * Cancel a request message started. + * + * If a request created by vici_begin() does not get submitted using + * vici_submit(), it has to get freed using this call. + * + * @param req request message to clean up + */ +void vici_free_req(vici_req_t *req); + +/** + * Dump a message text representation to a FILE stream. + * + * @param res response message to dump + * @param out FILE to dump to + * @return 0 if dumped complete message, 1 on error + */ +int vici_dump(vici_res_t *res, FILE *out); + +/** + * Parse next element from a vici response message. + * + * @param res response message to parse + * @return parse result + */ +vici_parse_t vici_parse(vici_res_t *res); + +/** + * Parse name tag / key of a previously parsed element. + * + * This call is valid only after vici_parse() returned VICI_PARSE_KEY_VALUE, + * VICI_PARSE_BEGIN_SECTION or VICI_PARSE_BEGIN_LIST. + * + * The string is valid until vici_free_res() is called. + * + * @param res response message to parse + * @return name tag / key, NULL on error + */ +char* vici_parse_name(vici_res_t *res); + +/** + * Compare name tag / key of a previusly parsed element. + * + * This call is valid only after vici_parse() returned VICI_PARSE_KEY_VALUE, + * VICI_PARSE_BEGIN_SECTION or VICI_PARSE_BEGIN_LIST. + * + * @param res response message to parse + * @param name string to compare + * @return 1 if name equals, 0 if not + */ +int vici_parse_name_eq(vici_res_t *res, char *name); + +/** + * Parse value of a previously parsed element, as a blob. + * + * This call is valid only after vici_parse() returned VICI_PARSE_KEY_VALUE or + * VICI_PARSE_LIST_ITEM. + * The string is valid until vici_free_res() is called. + */ +void* vici_parse_value(vici_res_t *res, int *len); + +/** + * Parse value of a previously parsed element, as a string. + * + * This call is valid only after vici_parse() returned VICI_PARSE_KEY_VALUE or + * VICI_PARSE_LIST_ITEM. + * This call is successful only if the value contains no non-printable + * characters. The string is valid until vici_free_res() is called. + * + * @param res response message to parse + * @return value as string, NULL on error + */ +char* vici_parse_value_str(vici_res_t *res); + +/** + * Clean up a received response message. + * + * Event messages get cleaned up by the library, it is not allowed to call + * vici_free_res() from within a vici_event_cb_t. + * + * @param res response message to free + */ +void vici_free_res(vici_res_t *res); + +/** + * (Un-)Register for events of a given kind. + * + * Events callbacks get invoked by a different thread from the libstrongswan + * thread pool. On failure, errno is set appropriately. + * + * @param conn connection context + * @param name name of event messages to register to + * @param cb callback function to register, NULL to unregister + * @param user user data passed to callback invocations + * @return 0 if registered successfully + */ +int vici_register(vici_conn_t *conn, char *name, vici_event_cb_t cb, void *user); + +/** + * Initialize libvici before first time use. + */ +void vici_init(); + +/** + * Deinitialize libvici after use. + */ +void vici_deinit(); + +#endif /** LIBVICI_H_ @}*/ diff --git a/src/libcharon/plugins/vici/suites/test_event.c b/src/libcharon/plugins/vici/suites/test_event.c new file mode 100644 index 000000000..771f12701 --- /dev/null +++ b/src/libcharon/plugins/vici/suites/test_event.c @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2014 Martin Willi + * Copyright (C) 2014 revosec 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 . + * + * 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 + +#include "../vici_dispatcher.h" +#include "../libvici.h" + +#include + +#define URI "unix:///tmp/strongswan-vici-event-test" + +static void event_cb(void *user, char *name, vici_res_t *ev) +{ + int *count = (int*)user; + + ck_assert_str_eq(name, "test"); + ck_assert(vici_parse(ev) == VICI_PARSE_KEY_VALUE); + ck_assert_str_eq(vici_parse_name(ev), "key1"); + ck_assert_str_eq(vici_parse_value_str(ev), "value1"); + ck_assert(vici_parse(ev) == VICI_PARSE_END); + + (*count)++; +} + +START_TEST(test_event) +{ + vici_dispatcher_t *dispatcher; + vici_conn_t *conn; + int count = 0; + + lib->processor->set_threads(lib->processor, 8); + + dispatcher = vici_dispatcher_create(URI); + ck_assert(dispatcher); + + dispatcher->manage_event(dispatcher, "test", TRUE); + + vici_init(); + conn = vici_connect(URI); + ck_assert(conn); + + ck_assert(vici_register(conn, "test", event_cb, &count) == 0); + ck_assert(vici_register(conn, "nonexistent", event_cb, &count) != 0); + + dispatcher->raise_event(dispatcher, "test", vici_message_create_from_args( + VICI_KEY_VALUE, "key1", chunk_from_str("value1"), + VICI_END)); + + while (count == 0) + { + usleep(1000); + } + + vici_disconnect(conn); + + dispatcher->manage_event(dispatcher, "test", FALSE); + + lib->processor->cancel(lib->processor); + dispatcher->destroy(dispatcher); + + vici_deinit(); +} +END_TEST + +START_TEST(test_stress) +{ + vici_dispatcher_t *dispatcher; + vici_conn_t *conn; + int count = 0, i, total = 50; + + lib->processor->set_threads(lib->processor, 8); + + dispatcher = vici_dispatcher_create(URI); + ck_assert(dispatcher); + + dispatcher->manage_event(dispatcher, "test", TRUE); + dispatcher->manage_event(dispatcher, "dummy", TRUE); + + vici_init(); + conn = vici_connect(URI); + ck_assert(conn); + + vici_register(conn, "test", event_cb, &count); + + for (i = 0; i < total; i++) + { + /* do some event re/deregistration in between */ + ck_assert(vici_register(conn, "dummy", event_cb, NULL) == 0); + + dispatcher->raise_event(dispatcher, "test", + vici_message_create_from_args( + VICI_KEY_VALUE, "key1", chunk_from_str("value1"), + VICI_END)); + + ck_assert(vici_register(conn, "dummy", NULL, NULL) == 0); + } + + while (count < total) + { + usleep(1000); + } + + vici_disconnect(conn); + + dispatcher->manage_event(dispatcher, "test", FALSE); + dispatcher->manage_event(dispatcher, "dummy", FALSE); + + lib->processor->cancel(lib->processor); + dispatcher->destroy(dispatcher); + + vici_deinit(); +} +END_TEST + +Suite *event_suite_create() +{ + Suite *s; + TCase *tc; + + s = suite_create("vici events"); + + tc = tcase_create("single"); + tcase_add_test(tc, test_event); + suite_add_tcase(s, tc); + + tc = tcase_create("stress"); + tcase_add_test(tc, test_stress); + suite_add_tcase(s, tc); + + return s; +} diff --git a/src/libcharon/plugins/vici/suites/test_request.c b/src/libcharon/plugins/vici/suites/test_request.c new file mode 100644 index 000000000..1f124d3dc --- /dev/null +++ b/src/libcharon/plugins/vici/suites/test_request.c @@ -0,0 +1,243 @@ +/* + * Copyright (C) 2014 Martin Willi + * Copyright (C) 2014 revosec 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 . + * + * 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 + +#include "../vici_dispatcher.h" +#include "../libvici.h" + +#include + +#define URI "unix:///tmp/strongswan-vici-request-test" + +static void encode_section(vici_req_t *req) +{ + vici_begin_section(req, "section1"); + vici_add_key_valuef(req, "key1", "value%u", 1); + vici_add_key_value(req, "key2", "value2", strlen("value2")); + vici_end_section(req); +} + +static void decode_section(vici_res_t *res) +{ + char *str; + int len; + + ck_assert(vici_parse(res) == VICI_PARSE_BEGIN_SECTION); + ck_assert_str_eq(vici_parse_name(res), "section1"); + ck_assert(vici_parse(res) == VICI_PARSE_KEY_VALUE); + ck_assert_str_eq(vici_parse_name(res), "key1"); + ck_assert_str_eq(vici_parse_value_str(res), "value1"); + ck_assert(vici_parse(res) == VICI_PARSE_KEY_VALUE); + ck_assert_str_eq(vici_parse_name(res), "key2"); + str = vici_parse_value(res, &len); + ck_assert(chunk_equals(chunk_from_str("value2"), chunk_create(str, len))); + ck_assert(vici_parse(res) == VICI_PARSE_END_SECTION); + ck_assert(vici_parse(res) == VICI_PARSE_END); +} + +static void encode_list(vici_req_t *req) +{ + vici_begin_list(req, "list1"); + vici_add_list_item(req, "item1", strlen("item1")); + vici_add_list_itemf(req, "item%u", 2); + vici_end_list(req); +} + +static void decode_list(vici_res_t *res) +{ + char *str; + int len; + + ck_assert(vici_parse(res) == VICI_PARSE_BEGIN_LIST); + ck_assert_str_eq(vici_parse_name(res), "list1"); + ck_assert(vici_parse(res) == VICI_PARSE_LIST_ITEM); + ck_assert_str_eq(vici_parse_value_str(res), "item1"); + ck_assert(vici_parse(res) == VICI_PARSE_LIST_ITEM); + str = vici_parse_value(res, &len); + ck_assert(chunk_equals(chunk_from_str("item2"), chunk_create(str, len))); + ck_assert(vici_parse(res) == VICI_PARSE_END_LIST); + ck_assert(vici_parse(res) == VICI_PARSE_END); +} + +static struct { + void (*encode)(vici_req_t* req); + void (*decode)(vici_res_t* res); +} echo_tests[] = { + { encode_section, decode_section }, + { encode_list, decode_list }, +}; + +static vici_message_t* echo_cb(void *user, char *name, + u_int id, vici_message_t *request) +{ + ck_assert_str_eq(name, "echo"); + ck_assert_int_eq((uintptr_t)user, 1); + + return vici_message_create_from_enumerator(request->create_enumerator(request)); +} + +START_TEST(test_echo) +{ + vici_dispatcher_t *dispatcher; + vici_conn_t *conn; + vici_req_t *req; + vici_res_t *res; + + lib->processor->set_threads(lib->processor, 8); + + dispatcher = vici_dispatcher_create(URI); + ck_assert(dispatcher); + + dispatcher->manage_command(dispatcher, "echo", echo_cb, (void*)(uintptr_t)1); + + vici_init(); + conn = vici_connect(URI); + ck_assert(conn); + + req = vici_begin("echo"); + echo_tests[_i].encode(req); + res = vici_submit(req, conn); + ck_assert(res); + echo_tests[_i].decode(res); + vici_free_res(res); + + vici_disconnect(conn); + + dispatcher->manage_command(dispatcher, "echo", NULL, NULL); + + lib->processor->cancel(lib->processor); + dispatcher->destroy(dispatcher); + + vici_deinit(); +} +END_TEST + +START_TEST(test_missing) +{ + vici_dispatcher_t *dispatcher; + vici_conn_t *conn; + vici_req_t *req; + vici_res_t *res; + + lib->processor->set_threads(lib->processor, 8); + + dispatcher = vici_dispatcher_create(URI); + ck_assert(dispatcher); + + vici_init(); + conn = vici_connect(URI); + ck_assert(conn); + + req = vici_begin("nonexistent"); + encode_section(req); + res = vici_submit(req, conn); + ck_assert(res == NULL); + + vici_disconnect(conn); + + dispatcher->manage_command(dispatcher, "echo", NULL, NULL); + + lib->processor->cancel(lib->processor); + dispatcher->destroy(dispatcher); + + vici_deinit(); +} +END_TEST + +static void event_cb(void *user, char *name, vici_res_t *ev) +{ + int *events = (int*)user; + + (*events)++; +} + +START_TEST(test_stress) +{ + vici_dispatcher_t *dispatcher; + vici_conn_t *conn; + vici_req_t *req; + vici_res_t *res; + int i, total = 50, events = 0; + + lib->processor->set_threads(lib->processor, 8); + + dispatcher = vici_dispatcher_create(URI); + ck_assert(dispatcher); + + dispatcher->manage_command(dispatcher, "echo", echo_cb, (void*)(uintptr_t)1); + dispatcher->manage_event(dispatcher, "dummy", TRUE); + + vici_init(); + conn = vici_connect(URI); + ck_assert(conn); + + for (i = 0; i < total; i++) + { + /* do some event management in between */ + ck_assert(vici_register(conn, "dummy", event_cb, &events) == 0); + dispatcher->raise_event(dispatcher, "dummy", + vici_message_create_from_args( + VICI_KEY_VALUE, "key1", chunk_from_str("value1"), + VICI_END)); + + req = vici_begin("echo"); + encode_section(req); + res = vici_submit(req, conn); + ck_assert(res); + decode_section(res); + vici_free_res(res); + + ck_assert(vici_register(conn, "dummy", NULL, NULL) == 0); + } + + while (events < total) + { + usleep(1000); + } + + vici_disconnect(conn); + + dispatcher->manage_command(dispatcher, "echo", NULL, NULL); + dispatcher->manage_event(dispatcher, "dummy", FALSE); + + lib->processor->cancel(lib->processor); + dispatcher->destroy(dispatcher); + + vici_deinit(); +} +END_TEST + +Suite *request_suite_create() +{ + Suite *s; + TCase *tc; + + s = suite_create("vici request"); + + tc = tcase_create("echo"); + tcase_add_loop_test(tc, test_echo, 0, countof(echo_tests)); + suite_add_tcase(s, tc); + + tc = tcase_create("missing"); + tcase_add_test(tc, test_missing); + suite_add_tcase(s, tc); + + tc = tcase_create("stress"); + tcase_add_test(tc, test_stress); + suite_add_tcase(s, tc); + + return s; +} diff --git a/src/libcharon/plugins/vici/vici_tests.h b/src/libcharon/plugins/vici/vici_tests.h index bdc296590..3e8f170e4 100644 --- a/src/libcharon/plugins/vici/vici_tests.h +++ b/src/libcharon/plugins/vici/vici_tests.h @@ -15,3 +15,5 @@ TEST_SUITE(socket_suite_create) TEST_SUITE(message_suite_create) +TEST_SUITE(request_suite_create) +TEST_SUITE(event_suite_create) From 252025210fa1f6f6b6d4b260a1562e6168d73167 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Fri, 24 Jan 2014 17:15:49 +0100 Subject: [PATCH 12/72] vici: Add a query class, currently implementing a list-sas command --- src/libcharon/plugins/vici/Makefile.am | 1 + src/libcharon/plugins/vici/vici_plugin.c | 14 +- src/libcharon/plugins/vici/vici_query.c | 379 +++++++++++++++++++++++ src/libcharon/plugins/vici/vici_query.h | 47 +++ 4 files changed, 440 insertions(+), 1 deletion(-) create mode 100644 src/libcharon/plugins/vici/vici_query.c create mode 100644 src/libcharon/plugins/vici/vici_query.h diff --git a/src/libcharon/plugins/vici/Makefile.am b/src/libcharon/plugins/vici/Makefile.am index 6495c1807..df6ecc1d4 100644 --- a/src/libcharon/plugins/vici/Makefile.am +++ b/src/libcharon/plugins/vici/Makefile.am @@ -18,6 +18,7 @@ libstrongswan_vici_la_SOURCES = \ vici_message.h vici_message.c \ vici_builder.h vici_builder.c \ vici_dispatcher.h vici_dispatcher.c \ + vici_query.h vici_query.c \ vici_plugin.h vici_plugin.c libstrongswan_vici_la_LDFLAGS = -module -avoid-version diff --git a/src/libcharon/plugins/vici/vici_plugin.c b/src/libcharon/plugins/vici/vici_plugin.c index 855dbc930..fb016acd9 100644 --- a/src/libcharon/plugins/vici/vici_plugin.c +++ b/src/libcharon/plugins/vici/vici_plugin.c @@ -15,6 +15,7 @@ #include "vici_plugin.h" #include "vici_dispatcher.h" +#include "vici_query.h" #include #include @@ -35,6 +36,11 @@ struct private_vici_plugin_t { * Dispatcher, creating socket */ vici_dispatcher_t *dispatcher; + + /** + * Query commands + */ + vici_query_t *query; }; METHOD(plugin_t, get_name, char*, @@ -56,10 +62,16 @@ static bool register_vici(private_vici_plugin_t *this, uri = lib->settings->get_str(lib->settings, "%s.plugins.vici.socket", VICI_DEFAULT_URI, lib->ns); this->dispatcher = vici_dispatcher_create(uri); - return this->dispatcher != NULL; + if (this->dispatcher) + { + this->query = vici_query_create(this->dispatcher); + return TRUE; + } + return FALSE; } else { + this->query->destroy(this->query); this->dispatcher->destroy(this->dispatcher); } return TRUE; diff --git a/src/libcharon/plugins/vici/vici_query.c b/src/libcharon/plugins/vici/vici_query.c new file mode 100644 index 000000000..c34ea0367 --- /dev/null +++ b/src/libcharon/plugins/vici/vici_query.c @@ -0,0 +1,379 @@ +/* + * Copyright (C) 2014 Martin Willi + * Copyright (C) 2014 revosec 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 . + * + * 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 "vici_query.h" +#include "vici_builder.h" + +#include + +#include + +typedef struct private_vici_query_t private_vici_query_t; + +/** + * Private data of an vici_query_t object. + */ +struct private_vici_query_t { + + /** + * Public vici_query_t interface. + */ + vici_query_t public; + + /** + * Dispatcher + */ + vici_dispatcher_t *dispatcher; +}; + +/** + * List details of a CHILD_SA + */ +static void list_child(private_vici_query_t *this, vici_builder_t *b, + child_sa_t *child, time_t now) +{ + time_t t; + u_int64_t bytes, packets; + u_int16_t alg, ks; + proposal_t *proposal; + enumerator_t *enumerator; + traffic_selector_t *ts; + + b->add_kv(b, "reqid", "%u", child->get_reqid(child)); + b->add_kv(b, "state", "%N", child_sa_state_names, child->get_state(child)); + b->add_kv(b, "mode", "%N", ipsec_mode_names, child->get_mode(child)); + if (child->get_state(child) == CHILD_INSTALLED || + child->get_state(child) == CHILD_REKEYING) + { + b->add_kv(b, "protocol", "%N", protocol_id_names, + child->get_protocol(child)); + if (child->has_encap(child)) + { + b->add_kv(b, "encap", "yes"); + } + b->add_kv(b, "spi-in", "%.8x", ntohl(child->get_spi(child, TRUE))); + b->add_kv(b, "spi-out", "%.8x", ntohl(child->get_spi(child, FALSE))); + + if (child->get_ipcomp(child) != IPCOMP_NONE) + { + b->add_kv(b, "cpi-in", "%.4x", ntohs(child->get_cpi(child, TRUE))); + b->add_kv(b, "cpi-out", "%.4x", ntohs(child->get_cpi(child, FALSE))); + } + proposal = child->get_proposal(child); + if (proposal) + { + if (proposal->get_algorithm(proposal, ENCRYPTION_ALGORITHM, + &alg, &ks) && alg != ENCR_UNDEFINED) + { + b->add_kv(b, "encr-alg", "%N", encryption_algorithm_names, alg); + if (ks) + { + b->add_kv(b, "encr-keysize", "%u", ks); + } + } + if (proposal->get_algorithm(proposal, INTEGRITY_ALGORITHM, + &alg, &ks) && alg != ENCR_UNDEFINED) + { + b->add_kv(b, "integ-alg", "%N", integrity_algorithm_names, alg); + if (ks) + { + b->add_kv(b, "integ-keysize", "%u", ks); + } + } + if (proposal->get_algorithm(proposal, PSEUDO_RANDOM_FUNCTION, + &alg, NULL)) + { + b->add_kv(b, "prf-alg", "%N", pseudo_random_function_names, alg); + } + if (proposal->get_algorithm(proposal, DIFFIE_HELLMAN_GROUP, + &alg, NULL)) + { + b->add_kv(b, "dh-group", "%N", diffie_hellman_group_names, alg); + } + if (proposal->get_algorithm(proposal, EXTENDED_SEQUENCE_NUMBERS, + &alg, NULL) && alg == EXT_SEQ_NUMBERS) + { + b->add_kv(b, "esn", "1"); + } + } + + child->get_usestats(child, TRUE, &t, &bytes, &packets); + b->add_kv(b, "bytes-in", "%" PRIu64, bytes); + b->add_kv(b, "packets-in", "%" PRIu64, packets); + if (t) + { + b->add_kv(b, "use-in", "%"PRIu64, (u_int64_t)(now - t)); + } + + child->get_usestats(child, FALSE, &t, &bytes, &packets); + b->add_kv(b, "bytes-out", "%"PRIu64, bytes); + b->add_kv(b, "packets-out", "%"PRIu64, packets); + if (t) + { + b->add_kv(b, "use-out", "%"PRIu64, (u_int64_t)(now - t)); + } + + t = child->get_lifetime(child, FALSE); + if (t) + { + b->add_kv(b, "rekey-time", "%"PRId64, (int64_t)(t - now)); + } + t = child->get_lifetime(child, TRUE); + if (t) + { + b->add_kv(b, "life-time", "%"PRId64, (int64_t)(t - now)); + } + t = child->get_installtime(child); + b->add_kv(b, "install-time", "%"PRId64, (int64_t)(now - t)); + } + + b->begin_list(b, "local-ts"); + enumerator = child->create_ts_enumerator(child, TRUE); + while (enumerator->enumerate(enumerator, &ts)) + { + b->add_li(b, "%R", ts); + } + enumerator->destroy(enumerator); + b->end_list(b /* local-ts */); + + b->begin_list(b, "remote-ts"); + enumerator = child->create_ts_enumerator(child, FALSE); + while (enumerator->enumerate(enumerator, &ts)) + { + b->add_li(b, "%R", ts); + } + enumerator->destroy(enumerator); + b->end_list(b /* remote-ts */); +} + +/** + * List tasks in a specific queue + */ +static void list_task_queue(private_vici_query_t *this, vici_builder_t *b, + ike_sa_t *ike_sa, task_queue_t q, char *name) +{ + enumerator_t *enumerator; + bool has = FALSE; + task_t *task; + + enumerator = ike_sa->create_task_enumerator(ike_sa, q); + while (enumerator->enumerate(enumerator, &task)) + { + if (!has) + { + b->begin_list(b, name); + has = TRUE; + } + b->add_li(b, "%N", task_type_names, task->get_type(task)); + } + enumerator->destroy(enumerator); + if (has) + { + b->end_list(b); + } +} + +/** + * List details of an IKE_SA + */ +static void list_ike(private_vici_query_t *this, vici_builder_t *b, + ike_sa_t *ike_sa, time_t now) +{ + time_t t; + ike_sa_id_t *id; + identification_t *eap; + proposal_t *proposal; + u_int16_t alg, ks; + + b->add_kv(b, "uniqueid", "%u", ike_sa->get_unique_id(ike_sa)); + b->add_kv(b, "version", "%u", ike_sa->get_version(ike_sa)); + b->add_kv(b, "state", "%N", ike_sa_state_names, ike_sa->get_state(ike_sa)); + + b->add_kv(b, "local-host", "%H", ike_sa->get_my_host(ike_sa)); + b->add_kv(b, "local-id", "%Y", ike_sa->get_my_id(ike_sa)); + + b->add_kv(b, "remote-host", "%H", ike_sa->get_other_host(ike_sa)); + b->add_kv(b, "remote-id", "%Y", ike_sa->get_other_id(ike_sa)); + + eap = ike_sa->get_other_eap_id(ike_sa); + + if (!eap->equals(eap, ike_sa->get_other_id(ike_sa))) + { + if (ike_sa->get_version(ike_sa) == IKEV1) + { + b->add_kv(b, "remote-xauth-id", "%Y", eap); + } + else + { + b->add_kv(b, "remote-eap-id", "%Y", eap); + } + } + + id = ike_sa->get_id(ike_sa); + if (id->is_initiator(id)) + { + b->add_kv(b, "initiator", "yes"); + } + b->add_kv(b, "initiator-spi", "%.16"PRIx64, id->get_initiator_spi(id)); + b->add_kv(b, "responder-spi", "%.16"PRIx64, id->get_responder_spi(id)); + + proposal = ike_sa->get_proposal(ike_sa); + if (proposal) + { + if (proposal->get_algorithm(proposal, ENCRYPTION_ALGORITHM, &alg, &ks)) + { + b->add_kv(b, "encr-alg", "%N", encryption_algorithm_names, alg); + if (ks) + { + b->add_kv(b, "encr-keysize", "%u", ks); + } + } + if (proposal->get_algorithm(proposal, INTEGRITY_ALGORITHM, &alg, &ks)) + { + b->add_kv(b, "integ-alg", "%N", integrity_algorithm_names, alg); + if (ks) + { + b->add_kv(b, "integ-keysize", "%u", ks); + } + } + if (proposal->get_algorithm(proposal, PSEUDO_RANDOM_FUNCTION, &alg, NULL)) + { + b->add_kv(b, "prf-alg", "%N", pseudo_random_function_names, alg); + } + if (proposal->get_algorithm(proposal, DIFFIE_HELLMAN_GROUP, &alg, NULL)) + { + b->add_kv(b, "dh-group", "%N", diffie_hellman_group_names, alg); + } + } + + if (ike_sa->get_state(ike_sa) == IKE_ESTABLISHED) + { + t = ike_sa->get_statistic(ike_sa, STAT_ESTABLISHED); + b->add_kv(b, "established", "%"PRId64, (int64_t)(now - t)); + t = ike_sa->get_statistic(ike_sa, STAT_REKEY); + if (t) + { + b->add_kv(b, "rekey-time", "%"PRId64, (int64_t)(t - now)); + } + t = ike_sa->get_statistic(ike_sa, STAT_REAUTH); + if (t) + { + b->add_kv(b, "reauth-time", "%"PRId64, (int64_t)(t - now)); + } + } + + list_task_queue(this, b, ike_sa, TASK_QUEUE_QUEUED, "tasks-queued"); + list_task_queue(this, b, ike_sa, TASK_QUEUE_ACTIVE, "tasks-active"); + list_task_queue(this, b, ike_sa, TASK_QUEUE_PASSIVE, "tasks-passive"); +} + +CALLBACK(list_sas, vici_message_t*, + private_vici_query_t *this, char *name, u_int id, vici_message_t *request) +{ + vici_builder_t *b; + enumerator_t *isas, *csas; + ike_sa_t *ike_sa; + child_sa_t *child_sa; + time_t now; + char *ike; + u_int ike_id; + bool bl; + + bl = request->get_str(request, NULL, "noblock") == NULL; + ike = request->get_str(request, NULL, "ike"); + ike_id = request->get_int(request, 0, "ike-id"); + + isas = charon->controller->create_ike_sa_enumerator(charon->controller, bl); + while (isas->enumerate(isas, &ike_sa)) + { + if (ike && !streq(ike, ike_sa->get_name(ike_sa))) + { + continue; + } + if (ike_id && ike_id != ike_sa->get_unique_id(ike_sa)) + { + continue; + } + + now = time_monotonic(NULL); + + b = vici_builder_create(); + b->begin_section(b, ike_sa->get_name(ike_sa)); + + list_ike(this, b, ike_sa, now); + + b->begin_section(b, "child-sas"); + csas = ike_sa->create_child_sa_enumerator(ike_sa); + while (csas->enumerate(csas, &child_sa)) + { + b->begin_section(b, child_sa->get_name(child_sa)); + list_child(this, b, child_sa, now); + b->end_section(b); + } + csas->destroy(csas); + b->end_section(b /* child-sas */ ); + + b->end_section(b); + + this->dispatcher->raise_event(this->dispatcher, "list-sa", id, + b->finalize(b)); + } + isas->destroy(isas); + + b = vici_builder_create(); + return b->finalize(b); +} + +static void manage_command(private_vici_query_t *this, + char *name, vici_command_cb_t cb, bool reg) +{ + this->dispatcher->manage_command(this->dispatcher, name, + reg ? cb : NULL, this); +} + +/** + * (Un-)register dispatcher functions + */ +static void manage_commands(private_vici_query_t *this, bool reg) +{ + this->dispatcher->manage_event(this->dispatcher, "list-sa", reg); + manage_command(this, "list-sas", list_sas, reg); +} + +METHOD(vici_query_t, destroy, void, + private_vici_query_t *this) +{ + manage_commands(this, FALSE); + free(this); +} + +/** + * See header + */ +vici_query_t *vici_query_create(vici_dispatcher_t *dispatcher) +{ + private_vici_query_t *this; + + INIT(this, + .public = { + .destroy = _destroy, + }, + .dispatcher = dispatcher, + ); + + manage_commands(this, TRUE); + + return &this->public; +} diff --git a/src/libcharon/plugins/vici/vici_query.h b/src/libcharon/plugins/vici/vici_query.h new file mode 100644 index 000000000..da72b1411 --- /dev/null +++ b/src/libcharon/plugins/vici/vici_query.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2014 Martin Willi + * Copyright (C) 2014 revosec 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 . + * + * 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. + */ + +/** + * @defgroup vici_query vici_query + * @{ @ingroup vici + */ + +#include "vici_dispatcher.h" + +#ifndef VICI_QUERY_H_ +#define VICI_QUERY_H_ + +typedef struct vici_query_t vici_query_t; + +/** + * Query helper, provides various commands to query/list daemon info. + */ +struct vici_query_t { + + /** + * Destroy a vici_query_t. + */ + void (*destroy)(vici_query_t *this); +}; + +/** + * Create a vici_query instance. + * + * @param dispatcher dispatcher to receive requests from + * @return query handler + */ +vici_query_t *vici_query_create(vici_dispatcher_t *dispatcher); + +#endif /** VICI_QUERY_H_ @}*/ From 293431269b59377af84c519c29ba0fabdbd3885b Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Mon, 10 Feb 2014 17:04:35 +0100 Subject: [PATCH 13/72] vici: Invoke dispatcher outside of connection log, allowing events from commands --- src/libcharon/plugins/vici/vici_socket.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/libcharon/plugins/vici/vici_socket.c b/src/libcharon/plugins/vici/vici_socket.c index 97c4d81ed..6cb445b2a 100644 --- a/src/libcharon/plugins/vici/vici_socket.c +++ b/src/libcharon/plugins/vici/vici_socket.c @@ -346,10 +346,6 @@ static bool do_read(private_vici_socket_t *this, entry_t *entry, entry->in.done += len; } - this->inbound(this->user, entry->id, entry->in.buf); - chunk_clear(&entry->in.buf); - entry->in.hdrlen = entry->in.done = 0; - return TRUE; } @@ -359,6 +355,7 @@ static bool do_read(private_vici_socket_t *this, entry_t *entry, CALLBACK(on_read, bool, entry_t *entry, stream_t *stream) { + chunk_t data = chunk_empty; bool ret; entry->mutex->lock(entry->mutex); @@ -367,8 +364,20 @@ CALLBACK(on_read, bool, { disconnect(entry->this, entry->id); } + if (entry->in.buf.len == entry->in.done) + { + data = entry->in.buf; + entry->in.buf = chunk_empty; + entry->in.hdrlen = entry->in.done = 0; + } entry->mutex->unlock(entry->mutex); + if (data.len) + { + entry->this->inbound(entry->this->user, entry->id, data); + chunk_clear(&data); + } + return ret; } From b40a12a96fecf688f81a81ecb292ff435e65bcd2 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Mon, 10 Feb 2014 17:09:01 +0100 Subject: [PATCH 14/72] vici: Raise events with an optional identifier for specific connections --- src/libcharon/plugins/vici/suites/test_event.c | 4 ++-- src/libcharon/plugins/vici/suites/test_request.c | 2 +- src/libcharon/plugins/vici/vici_dispatcher.c | 12 ++++++++---- src/libcharon/plugins/vici/vici_dispatcher.h | 5 +++-- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/libcharon/plugins/vici/suites/test_event.c b/src/libcharon/plugins/vici/suites/test_event.c index 771f12701..4dd9c7309 100644 --- a/src/libcharon/plugins/vici/suites/test_event.c +++ b/src/libcharon/plugins/vici/suites/test_event.c @@ -55,7 +55,7 @@ START_TEST(test_event) ck_assert(vici_register(conn, "test", event_cb, &count) == 0); ck_assert(vici_register(conn, "nonexistent", event_cb, &count) != 0); - dispatcher->raise_event(dispatcher, "test", vici_message_create_from_args( + dispatcher->raise_event(dispatcher, "test", 0, vici_message_create_from_args( VICI_KEY_VALUE, "key1", chunk_from_str("value1"), VICI_END)); @@ -100,7 +100,7 @@ START_TEST(test_stress) /* do some event re/deregistration in between */ ck_assert(vici_register(conn, "dummy", event_cb, NULL) == 0); - dispatcher->raise_event(dispatcher, "test", + dispatcher->raise_event(dispatcher, "test", 0, vici_message_create_from_args( VICI_KEY_VALUE, "key1", chunk_from_str("value1"), VICI_END)); diff --git a/src/libcharon/plugins/vici/suites/test_request.c b/src/libcharon/plugins/vici/suites/test_request.c index 1f124d3dc..8cb11a7ea 100644 --- a/src/libcharon/plugins/vici/suites/test_request.c +++ b/src/libcharon/plugins/vici/suites/test_request.c @@ -188,7 +188,7 @@ START_TEST(test_stress) { /* do some event management in between */ ck_assert(vici_register(conn, "dummy", event_cb, &events) == 0); - dispatcher->raise_event(dispatcher, "dummy", + dispatcher->raise_event(dispatcher, "dummy", 0, vici_message_create_from_args( VICI_KEY_VALUE, "key1", chunk_from_str("value1"), VICI_END)); diff --git a/src/libcharon/plugins/vici/vici_dispatcher.c b/src/libcharon/plugins/vici/vici_dispatcher.c index 797e51f61..982206af1 100644 --- a/src/libcharon/plugins/vici/vici_dispatcher.c +++ b/src/libcharon/plugins/vici/vici_dispatcher.c @@ -382,11 +382,12 @@ METHOD(vici_dispatcher_t, manage_event, void, } METHOD(vici_dispatcher_t, raise_event, void, - private_vici_dispatcher_t *this, char *name, vici_message_t *message) + private_vici_dispatcher_t *this, char *name, u_int id, + vici_message_t *message) { enumerator_t *enumerator; event_t *event; - u_int *id; + u_int *current; this->mutex->lock(this->mutex); event = this->events->get(this->events, name); @@ -397,9 +398,12 @@ METHOD(vici_dispatcher_t, raise_event, void, this->mutex->unlock(this->mutex); enumerator = array_create_enumerator(event->clients); - while (enumerator->enumerate(enumerator, &id)) + while (enumerator->enumerate(enumerator, ¤t)) { - send_op(this, *id, VICI_EVENT, name, message); + if (id == 0 || id == *current) + { + send_op(this, *current, VICI_EVENT, name, message); + } } enumerator->destroy(enumerator); diff --git a/src/libcharon/plugins/vici/vici_dispatcher.h b/src/libcharon/plugins/vici/vici_dispatcher.h index 518550491..effe5a670 100644 --- a/src/libcharon/plugins/vici/vici_dispatcher.h +++ b/src/libcharon/plugins/vici/vici_dispatcher.h @@ -92,12 +92,13 @@ struct vici_dispatcher_t { void (*manage_event)(vici_dispatcher_t *this, char *name, bool reg); /** - * Raise an event to all clients registered to that event. + * Raise an event to a specific or all clients registered to that event. * * @param name event name to raise + * @param id client connection ID, 0 for all * @param message event message to send, gets destroyed */ - void (*raise_event)(vici_dispatcher_t *this, char *name, + void (*raise_event)(vici_dispatcher_t *this, char *name, u_int id, vici_message_t *message); /** From db18495514e4a8a7adcedacd69334c44eaf7add9 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Mon, 10 Feb 2014 17:09:52 +0100 Subject: [PATCH 15/72] vici: Add some convenience getters on message --- .../plugins/vici/suites/test_message.c | 76 +++++++ src/libcharon/plugins/vici/vici_message.c | 201 +++++++++++++++++- src/libcharon/plugins/vici/vici_message.h | 61 ++++++ 3 files changed, 337 insertions(+), 1 deletion(-) diff --git a/src/libcharon/plugins/vici/suites/test_message.c b/src/libcharon/plugins/vici/suites/test_message.c index a8196bd63..293117348 100644 --- a/src/libcharon/plugins/vici/suites/test_message.c +++ b/src/libcharon/plugins/vici/suites/test_message.c @@ -304,6 +304,76 @@ START_TEST(test_builder_fmt) } END_TEST +static vici_message_t* build_getter_msg() +{ + return vici_message_create_from_args( + VICI_KEY_VALUE, "key1", chunk_from_str("1"), + VICI_SECTION_START, "section1", + VICI_KEY_VALUE, "key2", chunk_from_str("0x12"), + VICI_SECTION_START, "section2", + VICI_KEY_VALUE, "key3", chunk_from_str("-1"), + VICI_SECTION_END, + VICI_KEY_VALUE, "key4", chunk_from_str("asdf"), + VICI_SECTION_END, + VICI_KEY_VALUE, "key5", chunk_from_str(""), + VICI_END); +} + +START_TEST(test_get_str) +{ + vici_message_t *m; + + m = build_getter_msg(); + + ck_assert_str_eq(m->get_str(m, "def", "key1"), "1"); + ck_assert_str_eq(m->get_str(m, "def", "section1.key2"), "0x12"); + ck_assert_str_eq(m->get_str(m, "def", "section%d.section2.key3", 1), "-1"); + ck_assert_str_eq(m->get_str(m, "def", "section1.key4"), "asdf"); + ck_assert_str_eq(m->get_str(m, "def", "key5"), ""); + ck_assert_str_eq(m->get_str(m, "no", "nonexistent"), "no"); + ck_assert_str_eq(m->get_str(m, "no", "n.o.n.e.x.i.s.t.e.n.t"), "no"); + + m->destroy(m); +} +END_TEST + +START_TEST(test_get_int) +{ + vici_message_t *m; + + m = build_getter_msg(); + + ck_assert_int_eq(m->get_int(m, 2, "key1"), 1); + ck_assert_int_eq(m->get_int(m, 2, "section1.key2"), 0x12); + ck_assert_int_eq(m->get_int(m, 2, "section1.section2.key3"), -1); + ck_assert_int_eq(m->get_int(m, 2, "section1.key4"), 2); + ck_assert_int_eq(m->get_int(m, 2, "key5"), 0); + ck_assert_int_eq(m->get_int(m, 2, "nonexistent"), 2); + ck_assert_int_eq(m->get_int(m, 2, "n.o.n.e.x.i.s.t.e.n.t"), 2); + + m->destroy(m); +} +END_TEST + +START_TEST(test_get_value) +{ + vici_message_t *m; + chunk_t d = chunk_from_chars('d','e','f'); + + m = build_getter_msg(); + + ck_assert_chunk_eq(m->get_value(m, d, "key1"), chunk_from_str("1")); + ck_assert_chunk_eq(m->get_value(m, d, "section1.key2"), chunk_from_str("0x12")); + ck_assert_chunk_eq(m->get_value(m, d, "section1.section2.key3"), chunk_from_str("-1")); + ck_assert_chunk_eq(m->get_value(m, d, "section1.key4"), chunk_from_str("asdf")); + ck_assert_chunk_eq(m->get_value(m, d, "key5"), chunk_empty); + ck_assert_chunk_eq(m->get_value(m, d, "nonexistent"), d); + ck_assert_chunk_eq(m->get_value(m, d, "n.o.n.e.x.i.s.t.e.n.t"), d); + + m->destroy(m); +} +END_TEST + Suite *message_suite_create() { Suite *s; @@ -327,5 +397,11 @@ Suite *message_suite_create() tcase_add_test(tc, test_builder_fmt); suite_add_tcase(s, tc); + tc = tcase_create("convenience getters"); + tcase_add_test(tc, test_get_str); + tcase_add_test(tc, test_get_int); + tcase_add_test(tc, test_get_value); + suite_add_tcase(s, tc); + return s; } diff --git a/src/libcharon/plugins/vici/vici_message.c b/src/libcharon/plugins/vici/vici_message.c index 52094550c..1a1820acb 100644 --- a/src/libcharon/plugins/vici/vici_message.c +++ b/src/libcharon/plugins/vici/vici_message.c @@ -19,6 +19,8 @@ #include #include +#include + typedef struct private_vici_message_t private_vici_message_t; /** @@ -40,6 +42,11 @@ struct private_vici_message_t { * Free encoding during destruction? */ bool cleanup; + + /** + * Allocated strings we maintain for get_str() + */ + linked_list_t *strings; }; ENUM(vici_type_names, VICI_SECTION_START, VICI_END, @@ -129,7 +136,8 @@ METHOD(enumerator_t, parse_enumerate, bool, u_int8_t type; chunk_t data; - if (!this->reader->read_uint8(this->reader, &type)) + if (!this->reader->remaining(this->reader) || + !this->reader->read_uint8(this->reader, &type)) { *out = VICI_END; return TRUE; @@ -215,6 +223,189 @@ METHOD(vici_message_t, create_enumerator, enumerator_t*, return &enumerator->public; } +/** + * Find a value for given vararg key + */ +static bool find_value(private_vici_message_t *this, chunk_t *value, + char *fmt, va_list args) +{ + enumerator_t *enumerator; + char buf[128], *name, *key, *dot, *next; + int section = 0, keysection = 0; + bool found = FALSE; + chunk_t current; + vici_type_t type; + + vsnprintf(buf, sizeof(buf), fmt, args); + next = buf; + + enumerator = create_enumerator(this); + + /* descent into section */ + while (TRUE) + { + dot = strchr(next, '.'); + if (!dot) + { + key = next; + break; + } + *dot = '\0'; + key = next; + next = dot + 1; + keysection++; + + while (enumerator->enumerate(enumerator, &type, &name, ¤t)) + { + switch (type) + { + case VICI_SECTION_START: + section++; + if (section == keysection && streq(name, key)) + { + break; + } + break; + case VICI_SECTION_END: + section--; + continue; + case VICI_END: + break; + default: + continue; + } + break; + } + } + + /* find key/value in current section */ + while (enumerator->enumerate(enumerator, &type, &name, ¤t)) + { + switch (type) + { + case VICI_KEY_VALUE: + if (section == keysection && streq(key, name)) + { + *value = current; + found = TRUE; + break; + } + continue; + case VICI_SECTION_START: + section++; + continue; + case VICI_SECTION_END: + section--; + continue; + case VICI_END: + break; + default: + continue; + } + break; + } + + enumerator->destroy(enumerator); + + return found; +} + +METHOD(vici_message_t, vget_str, char*, + private_vici_message_t *this, char *def, char *fmt, va_list args) +{ + chunk_t value; + bool found; + char *str; + + found = find_value(this, &value, fmt, args); + if (found) + { + if (chunk_printable(value, NULL, 0)) + { + str = strndup(value.ptr, value.len); + /* keep a reference to string, so caller doesn't have to care */ + this->strings->insert_last(this->strings, str); + return str; + } + } + return def; +} + +METHOD(vici_message_t, get_str, char*, + private_vici_message_t *this, char *def, char *fmt, ...) +{ + va_list args; + char *str; + + va_start(args, fmt); + str = vget_str(this, def, fmt, args); + va_end(args); + return str; +} + +METHOD(vici_message_t, vget_int, int, + private_vici_message_t *this, int def, char *fmt, va_list args) +{ + chunk_t value; + bool found; + char buf[32], *pos; + int ret; + + found = find_value(this, &value, fmt, args); + if (found) + { + if (chunk_printable(value, NULL, 0)) + { + snprintf(buf, sizeof(buf), "%.*s", (int)value.len, value.ptr); + errno = 0; + ret = strtol(buf, &pos, 0); + if (errno == 0 && pos == buf + strlen(buf)) + { + return ret; + } + } + } + return def; +} + +METHOD(vici_message_t, get_int, int, + private_vici_message_t *this, int def, char *fmt, ...) +{ + va_list args; + int val; + + va_start(args, fmt); + val = vget_int(this, def, fmt, args); + va_end(args); + return val; +} + +METHOD(vici_message_t, vget_value, chunk_t, + private_vici_message_t *this, chunk_t def, char *fmt, va_list args) +{ + chunk_t value; + bool found; + + found = find_value(this, &value, fmt, args); + if (found) + { + return value; + } + return def; +} + +METHOD(vici_message_t, get_value, chunk_t, + private_vici_message_t *this, chunk_t def, char *fmt, ...) +{ + va_list args; + chunk_t value; + + va_start(args, fmt); + value = vget_value(this, def, fmt, args); + va_end(args); + return value; +} + METHOD(vici_message_t, get_encoding, chunk_t, private_vici_message_t *this) { @@ -228,6 +419,7 @@ METHOD(vici_message_t, destroy, void, { chunk_clear(&this->encoding); } + this->strings->destroy_function(this->strings, free); free(this); } @@ -241,9 +433,16 @@ vici_message_t *vici_message_create_from_data(chunk_t data, bool cleanup) INIT(this, .public = { .create_enumerator = _create_enumerator, + .get_str = _get_str, + .vget_str = _vget_str, + .get_int = _get_int, + .vget_int = _vget_int, + .get_value = _get_value, + .vget_value = _vget_value, .get_encoding = _get_encoding, .destroy = _destroy, }, + .strings = linked_list_create(), .encoding = data, .cleanup = cleanup, ); diff --git a/src/libcharon/plugins/vici/vici_message.h b/src/libcharon/plugins/vici/vici_message.h index a7f506a20..9e1a36659 100644 --- a/src/libcharon/plugins/vici/vici_message.h +++ b/src/libcharon/plugins/vici/vici_message.h @@ -69,6 +69,67 @@ struct vici_message_t { */ enumerator_t* (*create_enumerator)(vici_message_t *this); + /** + * Get the value of a key/value pair as a string. + * + * @param def default value if not found + * @param fmt printf style format string for key, with sections + * @param ... arguments to fmt string + * @return string + */ + char* (*get_str)(vici_message_t *this, char *def, char *fmt, ...); + + /** + * Get the value of a key/value pair as a string, va_list variant. + * + * @param def default value if not found + * @param fmt printf style format string for key, with sections + * @param args arguments to fmt string + * @return string + */ + char* (*vget_str)(vici_message_t *this, char *def, char *fmt, va_list args); + + /** + * Get the value of a key/value pair as integer. + * + * @param def default value if not found + * @param fmt printf style format string for key, with sections + * @param ... arguments to fmt string + * @return value + */ + int (*get_int)(vici_message_t *this, int def, char *fmt, ...); + + /** + * Get the value of a key/value pair as integer, va_list variant + * + * @param def default value if not found + * @param fmt printf style format string for key, with sections + * @param args arguments to fmt string + * @return value + */ + int (*vget_int)(vici_message_t *this, int def, char *fmt, va_list args); + + /** + * Get the raw value of a key/value pair. + * + * @param def default value if not found + * @param fmt printf style format string for key, with sections + * @param ... arguments to fmt string + * @return value + */ + chunk_t (*get_value)(vici_message_t *this, chunk_t def, char *fmt, ...); + + /** + * Get the raw value of a key/value pair, va_list variant. + * + * @param def default value if not found + * @param fmt printf style format string for key, with sections + * @param args arguments to fmt string + * @return value + */ + chunk_t (*vget_value)(vici_message_t *this, chunk_t def, + char *fmt, va_list args); + /** * Get encoded message. * From 7f4cfdff58f61ad016eb51271e5ac25553adf054 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Tue, 11 Feb 2014 14:09:14 +0100 Subject: [PATCH 16/72] vici: Add convenience value/string/integer getter to libvici --- src/libcharon/plugins/vici/libvici.c | 37 ++++++++++++++++++++++++++ src/libcharon/plugins/vici/libvici.h | 39 ++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+) diff --git a/src/libcharon/plugins/vici/libvici.c b/src/libcharon/plugins/vici/libvici.c index 0776b6b9c..f184a3b27 100644 --- a/src/libcharon/plugins/vici/libvici.c +++ b/src/libcharon/plugins/vici/libvici.c @@ -574,6 +574,43 @@ char* vici_parse_value_str(vici_res_t *res) } } +void* vici_find(vici_res_t *res, int *len, char *fmt, ...) +{ + va_list args; + chunk_t value; + + va_start(args, fmt); + value = res->message->vget_value(res->message, chunk_empty, fmt, args); + va_end(args); + + *len = value.len; + return value.ptr; +} + +char* vici_find_str(vici_res_t *res, char *def, char *fmt, ...) +{ + va_list args; + char *str; + + va_start(args, fmt); + str = res->message->vget_str(res->message, def, fmt, args); + va_end(args); + + return str; +} + +int vici_find_int(vici_res_t *res, int def, char *fmt, ...) +{ + va_list args; + int val; + + va_start(args, fmt); + val = res->message->vget_int(res->message, def, fmt, args); + va_end(args); + + return val; +} + void vici_free_res(vici_res_t *res) { res->strings->destroy_function(res->strings, free); diff --git a/src/libcharon/plugins/vici/libvici.h b/src/libcharon/plugins/vici/libvici.h index 329c67c47..fed356547 100644 --- a/src/libcharon/plugins/vici/libvici.h +++ b/src/libcharon/plugins/vici/libvici.h @@ -307,6 +307,45 @@ void* vici_parse_value(vici_res_t *res, int *len); */ char* vici_parse_value_str(vici_res_t *res); +/** + * Find a blob value in a message for a given key. + * + * Sections can be selected by prefixing them separated by dots. + * + * @param res response message to parse + * @param len length of returned object + * @param fmt printf format string of key and sections + * @param ... arguments to format string + * @return blob value, having *len bytes, NULL if not found + */ +void *vici_find(vici_res_t *res, int *len, char *fmt, ...); + +/** + * Find a string value in a message for a given key. + * + * Sections can be selected by prefixing them separated by dots. + * + * @param res response message to parse + * @param def default value, if key not found + * @param fmt printf format string of key and sections + * @param ... arguments to format string + * @return string, def if not found + */ +char* vici_find_str(vici_res_t *res, char *def, char *fmt, ...); + +/** + * Find an integer value in a message for a given key. + * + * Sections can be selected by prefixing them separated by dots. + * + * @param res response message to parse + * @param def default value, if key not found + * @param fmt printf format string of key and sections + * @param ... arguments to format string + * @return integer value, def if not found + */ +int vici_find_int(vici_res_t *res, int def, char *fmt, ...); + /** * Clean up a received response message. * From 045bdf528376e1edb1f97828776a9bb22d26799f Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Tue, 11 Feb 2014 14:36:42 +0100 Subject: [PATCH 17/72] vici: Move dumping to core message class, use it in libvici --- src/libcharon/plugins/vici/libvici.c | 58 +------------------- src/libcharon/plugins/vici/libvici.h | 3 +- src/libcharon/plugins/vici/vici_message.c | 67 +++++++++++++++++++++++ src/libcharon/plugins/vici/vici_message.h | 9 +++ 4 files changed, 81 insertions(+), 56 deletions(-) diff --git a/src/libcharon/plugins/vici/libvici.c b/src/libcharon/plugins/vici/libvici.c index f184a3b27..61ff5b993 100644 --- a/src/libcharon/plugins/vici/libvici.c +++ b/src/libcharon/plugins/vici/libvici.c @@ -417,64 +417,12 @@ void vici_free_req(vici_req_t *req) free(req); } -int vici_dump(vici_res_t *res, FILE *out) +int vici_dump(vici_res_t *res, char *label, FILE *out) { - enumerator_t *enumerator; - int ident = 0, delta = 2; - vici_type_t type; - char *name; - chunk_t value; - - enumerator = res->message->create_enumerator(res->message); - while (enumerator->enumerate(enumerator, &type, &name, &value)) + if (res->message->dump(res->message, label, out)) { - switch (type) - { - case VICI_SECTION_START: - fprintf(out, "%*s%s {\n", ident, "", name); - ident += delta; - break; - case VICI_SECTION_END: - ident -= delta; - fprintf(out, "%*s}\n", ident, ""); - break; - case VICI_KEY_VALUE: - if (chunk_printable(value, NULL, ' ')) - { - fprintf(out, "%*s%s = %.*s\n", - ident, "", name, (int)value.len, value.ptr); - } - else - { - fprintf(out, "%*s%s = 0x%+#B\n", - ident, "", name, &value); - } - break; - case VICI_LIST_START: - fprintf(out, "%*s%s = [\n", ident, "", name); - ident += delta; - break; - case VICI_LIST_END: - ident -= delta; - fprintf(out, "%*s]\n", ident, ""); - break; - case VICI_LIST_ITEM: - if (chunk_printable(value, NULL, ' ')) - { - fprintf(out, "%*s%.*s\n", - ident, "", (int)value.len, value.ptr); - } - else - { - fprintf(out, "%*s 0x%+#B\n", ident, "", &value); - } - break; - case VICI_END: - enumerator->destroy(enumerator); - return 0; - } + return 0; } - enumerator->destroy(enumerator); errno = EBADMSG; return 1; } diff --git a/src/libcharon/plugins/vici/libvici.h b/src/libcharon/plugins/vici/libvici.h index fed356547..499e9a0b1 100644 --- a/src/libcharon/plugins/vici/libvici.h +++ b/src/libcharon/plugins/vici/libvici.h @@ -247,10 +247,11 @@ void vici_free_req(vici_req_t *req); * Dump a message text representation to a FILE stream. * * @param res response message to dump + * @param label a label to print for this message * @param out FILE to dump to * @return 0 if dumped complete message, 1 on error */ -int vici_dump(vici_res_t *res, FILE *out); +int vici_dump(vici_res_t *res, char *label, FILE *out); /** * Parse next element from a vici response message. diff --git a/src/libcharon/plugins/vici/vici_message.c b/src/libcharon/plugins/vici/vici_message.c index 1a1820acb..644cbce59 100644 --- a/src/libcharon/plugins/vici/vici_message.c +++ b/src/libcharon/plugins/vici/vici_message.c @@ -412,6 +412,72 @@ METHOD(vici_message_t, get_encoding, chunk_t, return this->encoding; } +METHOD(vici_message_t, dump, bool, + private_vici_message_t *this, char *label, FILE *out) +{ + enumerator_t *enumerator; + int ident = 0, delta = 2; + vici_type_t type; + char *name; + chunk_t value; + + fprintf(out, "%s {\n", label); + ident += delta; + + enumerator = create_enumerator(this); + while (enumerator->enumerate(enumerator, &type, &name, &value)) + { + switch (type) + { + case VICI_SECTION_START: + fprintf(out, "%*s%s {\n", ident, "", name); + ident += delta; + break; + case VICI_SECTION_END: + ident -= delta; + fprintf(out, "%*s}\n", ident, ""); + break; + case VICI_KEY_VALUE: + if (chunk_printable(value, NULL, ' ')) + { + fprintf(out, "%*s%s = %.*s\n", + ident, "", name, (int)value.len, value.ptr); + } + else + { + fprintf(out, "%*s%s = 0x%+#B\n", + ident, "", name, &value); + } + break; + case VICI_LIST_START: + fprintf(out, "%*s%s = [\n", ident, "", name); + ident += delta; + break; + case VICI_LIST_END: + ident -= delta; + fprintf(out, "%*s]\n", ident, ""); + break; + case VICI_LIST_ITEM: + if (chunk_printable(value, NULL, ' ')) + { + fprintf(out, "%*s%.*s\n", + ident, "", (int)value.len, value.ptr); + } + else + { + fprintf(out, "%*s 0x%+#B\n", ident, "", &value); + } + break; + case VICI_END: + fprintf(out, "}\n"); + enumerator->destroy(enumerator); + return TRUE; + } + } + enumerator->destroy(enumerator); + return FALSE; +} + METHOD(vici_message_t, destroy, void, private_vici_message_t *this) { @@ -440,6 +506,7 @@ vici_message_t *vici_message_create_from_data(chunk_t data, bool cleanup) .get_value = _get_value, .vget_value = _vget_value, .get_encoding = _get_encoding, + .dump = _dump, .destroy = _destroy, }, .strings = linked_list_create(), diff --git a/src/libcharon/plugins/vici/vici_message.h b/src/libcharon/plugins/vici/vici_message.h index 9e1a36659..4fc59d746 100644 --- a/src/libcharon/plugins/vici/vici_message.h +++ b/src/libcharon/plugins/vici/vici_message.h @@ -137,6 +137,15 @@ struct vici_message_t { */ chunk_t (*get_encoding)(vici_message_t *this); + /** + * Dump a message text representation to a FILE stream. + * + * @param label label to print for message + * @param out FILE stream to dump to + * @return TRUE if message valid + */ + bool (*dump)(vici_message_t *this, char *label, FILE *out); + /** * Destroy a vici_message_t. */ From ecc4b510488feca208ad651ebdef9d56966aa6f5 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Tue, 11 Feb 2014 15:09:08 +0100 Subject: [PATCH 18/72] vici: Support thread cancellation in command callbacks --- src/libcharon/plugins/vici/vici_dispatcher.c | 68 ++++++++++++++------ src/libcharon/plugins/vici/vici_socket.c | 4 +- 2 files changed, 53 insertions(+), 19 deletions(-) diff --git a/src/libcharon/plugins/vici/vici_dispatcher.c b/src/libcharon/plugins/vici/vici_dispatcher.c index 982206af1..b66d0d5e8 100644 --- a/src/libcharon/plugins/vici/vici_dispatcher.c +++ b/src/libcharon/plugins/vici/vici_dispatcher.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -184,40 +185,69 @@ static void unregister_event(private_vici_dispatcher_t *this, char *name, } } +/** + * Data to release on thread cancellation + */ +typedef struct { + private_vici_dispatcher_t *this; + command_t *cmd; + vici_message_t *request; +} release_data_t; + +/** + * Release command after execution/cancellation + */ +CALLBACK(release_command, void, + release_data_t *release) +{ + release->request->destroy(release->request); + + release->this->mutex->lock(release->this->mutex); + if (--release->cmd->uses == 0) + { + release->this->cond->broadcast(release->this->cond); + } + release->this->mutex->unlock(release->this->mutex); + + free(release); +} + /** * Process a request message */ void process_request(private_vici_dispatcher_t *this, char *name, u_int id, chunk_t data) { - command_t *cmd; - vici_message_t *request, *response = NULL; + vici_message_t *response = NULL; + release_data_t *release; + + INIT(release, + .this = this, + ); this->mutex->lock(this->mutex); - cmd = this->cmds->get(this->cmds, name); - if (cmd) + release->cmd = this->cmds->get(this->cmds, name); + if (release->cmd) { - cmd->uses++; + release->cmd->uses++; } this->mutex->unlock(this->mutex); - if (cmd) + if (release->cmd) { - request = vici_message_create_from_data(data, FALSE); - response = cmd->cb(cmd->user, cmd->name, id, request); - request->destroy(request); + thread_cleanup_push(release_command, release); - this->mutex->lock(this->mutex); - if (--cmd->uses == 0) + release->request = vici_message_create_from_data(data, FALSE); + response = release->cmd->cb(release->cmd->user, release->cmd->name, + id, release->request); + + thread_cleanup_pop(TRUE); + + if (response) { - this->cond->broadcast(this->cond); + send_op(this, id, VICI_CMD_RESPONSE, NULL, response); + response->destroy(response); } - this->mutex->unlock(this->mutex); - } - if (response) - { - send_op(this, id, VICI_CMD_RESPONSE, NULL, response); - response->destroy(response); } else { @@ -264,7 +294,9 @@ CALLBACK(inbound, void, if (reader->read_data8(reader, &chunk) && vici_stringify(chunk, name, sizeof(name))) { + thread_cleanup_push((void*)reader->destroy, reader); process_request(this, name, id, reader->peek(reader)); + thread_cleanup_pop(FALSE); } else { diff --git a/src/libcharon/plugins/vici/vici_socket.c b/src/libcharon/plugins/vici/vici_socket.c index 6cb445b2a..b2251982e 100644 --- a/src/libcharon/plugins/vici/vici_socket.c +++ b/src/libcharon/plugins/vici/vici_socket.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -374,8 +375,9 @@ CALLBACK(on_read, bool, if (data.len) { + thread_cleanup_push(free, data.ptr); entry->this->inbound(entry->this->user, entry->id, data); - chunk_clear(&data); + thread_cleanup_pop(TRUE); } return ret; From 5f95657c657d700ae994aa8690f2b32f2fad68b8 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Mon, 10 Feb 2014 17:10:54 +0100 Subject: [PATCH 19/72] vici: Add a control backend, currently to initiate connections by name --- src/libcharon/plugins/vici/Makefile.am | 1 + src/libcharon/plugins/vici/vici_control.c | 219 ++++++++++++++++++++++ src/libcharon/plugins/vici/vici_control.h | 47 +++++ src/libcharon/plugins/vici/vici_plugin.c | 8 + 4 files changed, 275 insertions(+) create mode 100644 src/libcharon/plugins/vici/vici_control.c create mode 100644 src/libcharon/plugins/vici/vici_control.h diff --git a/src/libcharon/plugins/vici/Makefile.am b/src/libcharon/plugins/vici/Makefile.am index df6ecc1d4..6ec830ea4 100644 --- a/src/libcharon/plugins/vici/Makefile.am +++ b/src/libcharon/plugins/vici/Makefile.am @@ -19,6 +19,7 @@ libstrongswan_vici_la_SOURCES = \ vici_builder.h vici_builder.c \ vici_dispatcher.h vici_dispatcher.c \ vici_query.h vici_query.c \ + vici_control.h vici_control.c \ vici_plugin.h vici_plugin.c libstrongswan_vici_la_LDFLAGS = -module -avoid-version diff --git a/src/libcharon/plugins/vici/vici_control.c b/src/libcharon/plugins/vici/vici_control.c new file mode 100644 index 000000000..ecf19096a --- /dev/null +++ b/src/libcharon/plugins/vici/vici_control.c @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2014 Martin Willi + * Copyright (C) 2014 revosec 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 . + * + * 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 "vici_control.h" +#include "vici_builder.h" + +#include + +#include + +typedef struct private_vici_control_t private_vici_control_t; + +/** + * Private data of an vici_control_t object. + */ +struct private_vici_control_t { + + /** + * Public vici_control_t interface. + */ + vici_control_t public; + + /** + * Dispatcher + */ + vici_dispatcher_t *dispatcher; +}; + +/** + * Log callback helper data + */ +typedef struct { + /** dispatcher to send log messages over */ + vici_dispatcher_t *dispatcher; + /** connection ID to send messages to */ + u_int id; + /** loglevel */ + level_t level; +} log_info_t; + +/** + * Log using vici event messages + */ +static bool log_vici(log_info_t *info, debug_t group, level_t level, + ike_sa_t *ike_sa, char *text) +{ + if (level <= info->level) + { + vici_message_t *message; + vici_builder_t *builder; + + builder = vici_builder_create(); + builder->add_kv(builder, "group", "%N", debug_names, group); + builder->add_kv(builder, "level", "%d", level); + if (ike_sa) + { + builder->add_kv(builder, "ikesa-name", "%s", + ike_sa->get_name(ike_sa)); + builder->add_kv(builder, "ikesa-uniqueid", "%u", + ike_sa->get_unique_id(ike_sa)); + } + builder->add_kv(builder, "msg", "%s", text); + + message = builder->finalize(builder); + if (message) + { + info->dispatcher->raise_event(info->dispatcher, "control-log", + info->id, message); + } + } + return TRUE; +} + +/** + * Send a (error) reply message + */ +static vici_message_t* send_reply(private_vici_control_t *this, char *fmt, ...) +{ + vici_builder_t *builder; + va_list args; + + builder = vici_builder_create(); + builder->add_kv(builder, "success", fmt ? "no" : "yes"); + if (fmt) + { + va_start(args, fmt); + builder->vadd_kv(builder, "errmsg", fmt, args); + va_end(args); + } + return builder->finalize(builder); +} + +/** + * Get the child_cfg having name from peer_cfg + */ +static child_cfg_t* get_child_from_peer(peer_cfg_t *peer_cfg, char *name) +{ + child_cfg_t *current, *found = NULL; + enumerator_t *enumerator; + + enumerator = peer_cfg->create_child_cfg_enumerator(peer_cfg); + while (enumerator->enumerate(enumerator, ¤t)) + { + if (streq(current->get_name(current), name)) + { + found = current; + found->get_ref(found); + break; + } + } + enumerator->destroy(enumerator); + return found; +} + +CALLBACK(initiate, vici_message_t*, + private_vici_control_t *this, char *name, u_int id, vici_message_t *request) +{ + child_cfg_t *child_cfg = NULL; + peer_cfg_t *peer_cfg; + enumerator_t *enumerator; + char *child; + u_int timeout; + log_info_t log = { + .dispatcher = this->dispatcher, + .id = id, + }; + + child = request->get_str(request, NULL, "child"); + timeout = request->get_int(request, 0, "timeout"); + log.level = request->get_int(request, 1, "loglevel"); + + if (!child) + { + return send_reply(this, "missing configuration name"); + } + enumerator = charon->backends->create_peer_cfg_enumerator(charon->backends, + NULL, NULL, NULL, NULL, IKE_ANY); + while (enumerator->enumerate(enumerator, &peer_cfg)) + { + child_cfg = get_child_from_peer(peer_cfg, child); + if (child_cfg) + { + peer_cfg->get_ref(peer_cfg); + break; + } + } + enumerator->destroy(enumerator); + + if (!child_cfg) + { + return send_reply(this, "CHILD_SA config '%s' not found", child); + } + switch (charon->controller->initiate(charon->controller, + peer_cfg, child_cfg, (controller_cb_t)log_vici, &log, timeout)) + { + case SUCCESS: + return send_reply(this, NULL); + case OUT_OF_RES: + return send_reply(this, "CHILD_SA '%s' not established after %dms", + child, timeout); + case FAILED: + default: + return send_reply(this, "establishing CHILD_SA '%s' failed", child); + } +} + +static void manage_command(private_vici_control_t *this, + char *name, vici_command_cb_t cb, bool reg) +{ + this->dispatcher->manage_command(this->dispatcher, name, + reg ? cb : NULL, this); +} + +/** + * (Un-)register dispatcher functions + */ +static void manage_commands(private_vici_control_t *this, bool reg) +{ + manage_command(this, "initiate", initiate, reg); + this->dispatcher->manage_event(this->dispatcher, "control-log", reg); +} + +METHOD(vici_control_t, destroy, void, + private_vici_control_t *this) +{ + manage_commands(this, FALSE); + free(this); +} + +/** + * See header + */ +vici_control_t *vici_control_create(vici_dispatcher_t *dispatcher) +{ + private_vici_control_t *this; + + INIT(this, + .public = { + .destroy = _destroy, + }, + .dispatcher = dispatcher, + ); + + manage_commands(this, TRUE); + + return &this->public; +} diff --git a/src/libcharon/plugins/vici/vici_control.h b/src/libcharon/plugins/vici/vici_control.h new file mode 100644 index 000000000..71a13a074 --- /dev/null +++ b/src/libcharon/plugins/vici/vici_control.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2014 Martin Willi + * Copyright (C) 2014 revosec 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 . + * + * 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. + */ + +/** + * @defgroup vici_control vici_control + * @{ @ingroup vici + */ + +#include "vici_dispatcher.h" + +#ifndef VICI_CONTROL_H_ +#define VICI_CONTROL_H_ + +typedef struct vici_control_t vici_control_t; + +/** + * Control helper, provides initiate/terminate and other commands. + */ +struct vici_control_t { + + /** + * Destroy a vici_control_t. + */ + void (*destroy)(vici_control_t *this); +}; + +/** + * Create a vici_control instance. + * + * @param dispatcher dispatcher to receive requests from + * @return query handler + */ +vici_control_t *vici_control_create(vici_dispatcher_t *dispatcher); + +#endif /** VICI_CONTROL_H_ @}*/ diff --git a/src/libcharon/plugins/vici/vici_plugin.c b/src/libcharon/plugins/vici/vici_plugin.c index fb016acd9..0bceefca1 100644 --- a/src/libcharon/plugins/vici/vici_plugin.c +++ b/src/libcharon/plugins/vici/vici_plugin.c @@ -16,6 +16,7 @@ #include "vici_plugin.h" #include "vici_dispatcher.h" #include "vici_query.h" +#include "vici_control.h" #include #include @@ -41,6 +42,11 @@ struct private_vici_plugin_t { * Query commands */ vici_query_t *query; + + /** + * Control commands + */ + vici_control_t *control; }; METHOD(plugin_t, get_name, char*, @@ -65,12 +71,14 @@ static bool register_vici(private_vici_plugin_t *this, if (this->dispatcher) { this->query = vici_query_create(this->dispatcher); + this->control = vici_control_create(this->dispatcher); return TRUE; } return FALSE; } else { + this->control->destroy(this->control); this->query->destroy(this->query); this->dispatcher->destroy(this->dispatcher); } From c7d4dad66d4f8c566129c973c124a951d0341b6f Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Tue, 11 Feb 2014 17:14:22 +0100 Subject: [PATCH 20/72] vici: Implement a terminate command to close IKE or CHILD_SAs --- src/libcharon/plugins/vici/vici_control.c | 118 ++++++++++++++++++++++ 1 file changed, 118 insertions(+) diff --git a/src/libcharon/plugins/vici/vici_control.c b/src/libcharon/plugins/vici/vici_control.c index ecf19096a..4a4ee0cbb 100644 --- a/src/libcharon/plugins/vici/vici_control.c +++ b/src/libcharon/plugins/vici/vici_control.c @@ -19,6 +19,7 @@ #include #include +#include typedef struct private_vici_control_t private_vici_control_t; @@ -176,6 +177,122 @@ CALLBACK(initiate, vici_message_t*, } } +CALLBACK(terminate, vici_message_t*, + private_vici_control_t *this, char *name, u_int id, vici_message_t *request) +{ + enumerator_t *enumerator, *isas, *csas; + char *child, *ike; + u_int timeout, child_id, ike_id, current, *del, done = 0; + ike_sa_t *ike_sa; + child_sa_t *child_sa; + array_t *ids; + vici_message_t *reply; + log_info_t log = { + .dispatcher = this->dispatcher, + .id = id, + }; + + child = request->get_str(request, NULL, "child"); + ike = request->get_str(request, NULL, "ike"); + child_id = request->get_int(request, 0, "child-id"); + ike_id = request->get_int(request, 0, "ike-id"); + timeout = request->get_int(request, 0, "timeout"); + log.level = request->get_int(request, 1, "loglevel"); + + if (!child && !ike && !ike_id && !child_id) + { + return send_reply(this, "missing terminate selector"); + } + + ids = array_create(sizeof(u_int), 0); + + isas = charon->controller->create_ike_sa_enumerator(charon->controller, TRUE); + while (isas->enumerate(isas, &ike_sa)) + { + if (child || child_id) + { + if (ike && !streq(ike, ike_sa->get_name(ike_sa))) + { + continue; + } + if (ike_id && ike_id != ike_sa->get_unique_id(ike_sa)) + { + continue; + } + csas = ike_sa->create_child_sa_enumerator(ike_sa); + while (csas->enumerate(csas, &child_sa)) + { + if (child && !streq(child, child_sa->get_name(child_sa))) + { + continue; + } + if (child_id && child_sa->get_reqid(child_sa) != child_id) + { + continue; + } + current = child_sa->get_reqid(child_sa); + array_insert(ids, ARRAY_TAIL, ¤t); + } + csas->destroy(csas); + } + else if (ike && streq(ike, ike_sa->get_name(ike_sa))) + { + current = ike_sa->get_unique_id(ike_sa); + array_insert(ids, ARRAY_TAIL, ¤t); + } + else if (ike_id && ike_id == ike_sa->get_unique_id(ike_sa)) + { + array_insert(ids, ARRAY_TAIL, &ike_id); + } + } + isas->destroy(isas); + + enumerator = array_create_enumerator(ids); + while (enumerator->enumerate(enumerator, &del)) + { + if (child || child_id) + { + if (charon->controller->terminate_child(charon->controller, *del, + (controller_cb_t)log_vici, &log, timeout) == SUCCESS) + { + done++; + } + } + else + { + if (charon->controller->terminate_ike(charon->controller, *del, + (controller_cb_t)log_vici, &log, timeout) == SUCCESS) + { + done++; + } + } + } + enumerator->destroy(enumerator); + + if (array_count(ids) == 0) + { + reply = send_reply(this, "no matching SAs to terminate found"); + } + else if (done < array_count(ids)) + { + if (array_count(ids) == 1) + { + reply = send_reply(this, "terminating SA failed"); + } + else + { + reply = send_reply(this, "terminated %u of %u SAs", + done, array_count(ids)); + } + } + else + { + reply = send_reply(this, NULL); + } + array_destroy(ids); + return reply; +} + static void manage_command(private_vici_control_t *this, char *name, vici_command_cb_t cb, bool reg) { @@ -189,6 +306,7 @@ static void manage_command(private_vici_control_t *this, static void manage_commands(private_vici_control_t *this, bool reg) { manage_command(this, "initiate", initiate, reg); + manage_command(this, "terminate", terminate, reg); this->dispatcher->manage_event(this->dispatcher, "control-log", reg); } From d9ae1c68e515e47be2e66090148eee00feaf3e79 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Tue, 11 Feb 2014 17:41:05 +0100 Subject: [PATCH 21/72] vici: Add a version command to print daemon and OS version info --- src/libcharon/plugins/vici/vici_query.c | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/libcharon/plugins/vici/vici_query.c b/src/libcharon/plugins/vici/vici_query.c index c34ea0367..8c7a1de34 100644 --- a/src/libcharon/plugins/vici/vici_query.c +++ b/src/libcharon/plugins/vici/vici_query.c @@ -17,6 +17,7 @@ #include "vici_builder.h" #include +#include #include @@ -336,6 +337,27 @@ CALLBACK(list_sas, vici_message_t*, return b->finalize(b); } +CALLBACK(version, vici_message_t*, + private_vici_query_t *this, char *name, u_int id, vici_message_t *request) +{ + struct utsname utsname; + vici_builder_t *b; + + b = vici_builder_create(); + + b->add_kv(b, "daemon", "%s", lib->ns); + b->add_kv(b, "version", "%s", VERSION); + + if (uname(&utsname) == 0) + { + b->add_kv(b, "sysname", "%s", utsname.sysname); + b->add_kv(b, "release", "%s", utsname.release); + b->add_kv(b, "machine", "%s", utsname.machine); + } + + return b->finalize(b); +} + static void manage_command(private_vici_query_t *this, char *name, vici_command_cb_t cb, bool reg) { @@ -350,6 +372,7 @@ static void manage_commands(private_vici_query_t *this, bool reg) { this->dispatcher->manage_event(this->dispatcher, "list-sa", reg); manage_command(this, "list-sas", list_sas, reg); + manage_command(this, "version", version, reg); } METHOD(vici_query_t, destroy, void, From 993bfe95fb75ddf46491ddc4f59916f424f8a849 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Wed, 12 Feb 2014 15:37:06 +0100 Subject: [PATCH 22/72] vici: Add a callback based recursive parser function --- src/libcharon/plugins/vici/libvici.c | 95 ++++++++++++++++++++++++++++ src/libcharon/plugins/vici/libvici.h | 41 ++++++++++++ 2 files changed, 136 insertions(+) diff --git a/src/libcharon/plugins/vici/libvici.c b/src/libcharon/plugins/vici/libvici.c index 61ff5b993..9b7ccb13e 100644 --- a/src/libcharon/plugins/vici/libvici.c +++ b/src/libcharon/plugins/vici/libvici.c @@ -94,6 +94,8 @@ struct vici_res_t { char *name; /** currently enumerating value */ chunk_t value; + /** section nesting level of callback parser */ + int level; }; /** @@ -495,6 +497,7 @@ void* vici_parse_value(vici_res_t *res, int *len) *len = res->value.len; return res->value.ptr; default: + *len = 0; errno = EINVAL; return NULL; } @@ -522,6 +525,98 @@ char* vici_parse_value_str(vici_res_t *res) } } +int vici_parse_cb(vici_res_t *res, vici_parse_section_cb_t section, + vici_parse_value_cb_t kv, vici_parse_value_cb_t li, + void *user) +{ + char *name, *list = NULL; + void *value; + int base, len, ret; + + base = res->level; + + while (TRUE) + { + switch (vici_parse(res)) + { + case VICI_PARSE_KEY_VALUE: + if (res->level == base) + { + if (kv) + { + name = vici_parse_name(res); + value = vici_parse_value(res, &len); + if (name && value) + { + ret = kv(user, res, name, value, len); + if (ret) + { + return ret; + } + } + } + } + break; + case VICI_PARSE_BEGIN_SECTION: + if (res->level++ == base) + { + if (section) + { + name = vici_parse_name(res); + if (name) + { + ret = section(user, res, name); + if (ret) + { + return ret; + } + } + } + } + break; + case VICI_PARSE_END_SECTION: + if (res->level-- == base) + { + return 0; + } + break; + case VICI_PARSE_END: + res->level = 0; + return 0; + case VICI_PARSE_BEGIN_LIST: + if (res->level == base) + { + list = vici_parse_name(res); + } + break; + case VICI_PARSE_LIST_ITEM: + if (list && li) + { + value = vici_parse_value(res, &len); + if (value) + { + ret = li(user, res, list, value, len); + if (ret) + { + return ret; + } + } + } + break; + case VICI_PARSE_END_LIST: + if (res->level == base) + { + list = NULL; + } + break; + case VICI_PARSE_ERROR: + res->level = 0; + errno = EBADMSG; + return 1; + } + } +} + void* vici_find(vici_res_t *res, int *len, char *fmt, ...) { va_list args; diff --git a/src/libcharon/plugins/vici/libvici.h b/src/libcharon/plugins/vici/libvici.h index 499e9a0b1..2689b39e1 100644 --- a/src/libcharon/plugins/vici/libvici.h +++ b/src/libcharon/plugins/vici/libvici.h @@ -123,6 +123,29 @@ typedef enum { */ typedef void (*vici_event_cb_t)(void *user, char *name, vici_res_t *msg); +/** + * Callback function for key/value and list items, invoked by vici_parse_cb(). + * + * @param user user data, as passed to vici_parse_cb() + * @param res message currently parsing + * @param name name of key or list + * @param value value buffer + * @param len length of value buffer + * @return 0 if parsed successfully + */ +typedef int (*vici_parse_value_cb_t)(void *user, vici_res_t *res, char *name, + void *value, int len); + +/** + * Callback function for sections, invoked by vici_parse_cb(). + * + * @param user user data, as passed to vici_parse_cb() + * @param res message currently parsing + * @param name name of the section + * @return 0 if parsed successfully + */ +typedef int (*vici_parse_section_cb_t)(void *user, vici_res_t *res, char *name); + /** * Open a new vici connection. * @@ -309,6 +332,24 @@ void* vici_parse_value(vici_res_t *res, int *len); char* vici_parse_value_str(vici_res_t *res); /** + * Parse a complete message with callbacks. + * + * Any of the callbacks may be NULL to skip this kind of item. Callbacks are + * invoked for the current section level only. To descent into sections, call + * vici_parse_cb() from within a section callback. + * + * @param res message to parse + * @param section callback invoked for each section + * @param kv callback invoked for key/value pairs + * @param li callback invoked for list items + * @param user user data to pass to callbacks + * @return 0 if parsing successful + */ +int vici_parse_cb(vici_res_t *res, vici_parse_section_cb_t section, + vici_parse_value_cb_t kv, vici_parse_value_cb_t li, + void *user); + +/* * Find a blob value in a message for a given key. * * Sections can be selected by prefixing them separated by dots. From 3b5808a0f626b9f3c359fd0c515bc1fe4938dcd8 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Wed, 12 Feb 2014 17:55:05 +0100 Subject: [PATCH 23/72] vici: Avoid recursive control log invocations --- src/libcharon/plugins/vici/vici_control.c | 42 +++++++++++++---------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/src/libcharon/plugins/vici/vici_control.c b/src/libcharon/plugins/vici/vici_control.c index 4a4ee0cbb..b48a8c1fc 100644 --- a/src/libcharon/plugins/vici/vici_control.c +++ b/src/libcharon/plugins/vici/vici_control.c @@ -49,6 +49,8 @@ typedef struct { u_int id; /** loglevel */ level_t level; + /** prevent recursive log */ + u_int recursive; } log_info_t; /** @@ -59,27 +61,31 @@ static bool log_vici(log_info_t *info, debug_t group, level_t level, { if (level <= info->level) { - vici_message_t *message; - vici_builder_t *builder; - - builder = vici_builder_create(); - builder->add_kv(builder, "group", "%N", debug_names, group); - builder->add_kv(builder, "level", "%d", level); - if (ike_sa) + if (info->recursive++ == 0) { - builder->add_kv(builder, "ikesa-name", "%s", - ike_sa->get_name(ike_sa)); - builder->add_kv(builder, "ikesa-uniqueid", "%u", - ike_sa->get_unique_id(ike_sa)); - } - builder->add_kv(builder, "msg", "%s", text); + vici_message_t *message; + vici_builder_t *builder; - message = builder->finalize(builder); - if (message) - { - info->dispatcher->raise_event(info->dispatcher, "control-log", - info->id, message); + builder = vici_builder_create(); + builder->add_kv(builder, "group", "%N", debug_names, group); + builder->add_kv(builder, "level", "%d", level); + if (ike_sa) + { + builder->add_kv(builder, "ikesa-name", "%s", + ike_sa->get_name(ike_sa)); + builder->add_kv(builder, "ikesa-uniqueid", "%u", + ike_sa->get_unique_id(ike_sa)); + } + builder->add_kv(builder, "msg", "%s", text); + + message = builder->finalize(builder); + if (message) + { + info->dispatcher->raise_event(info->dispatcher, "control-log", + info->id, message); + } } + info->recursive--; } return TRUE; } From e2496bda024e283cf8d5131b6571de7818b5cd90 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Thu, 13 Feb 2014 09:43:32 +0100 Subject: [PATCH 24/72] vici: Add a test case raising events during request, checks in-order delivery --- .../plugins/vici/suites/test_event.c | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/src/libcharon/plugins/vici/suites/test_event.c b/src/libcharon/plugins/vici/suites/test_event.c index 4dd9c7309..12fd03107 100644 --- a/src/libcharon/plugins/vici/suites/test_event.c +++ b/src/libcharon/plugins/vici/suites/test_event.c @@ -75,6 +75,78 @@ START_TEST(test_event) } END_TEST +#define EVENT_COUNT 500 + +CALLBACK(raise_cb, vici_message_t*, + vici_dispatcher_t *dispatcher, char *name, u_int id, vici_message_t *req) +{ + u_int i; + + for (i = 0; i < EVENT_COUNT; i++) + { + dispatcher->raise_event(dispatcher, "event", id, + vici_message_create_from_args( + VICI_KEY_VALUE, "counter", chunk_from_thing(i), + VICI_END)); + } + return vici_message_create_from_args(VICI_END); +} + +CALLBACK(raise_event_cb, void, + int *count, char *name, vici_res_t *ev) +{ + u_int *value, len; + + ck_assert_str_eq(name, "event"); + ck_assert(vici_parse(ev) == VICI_PARSE_KEY_VALUE); + ck_assert_str_eq(vici_parse_name(ev), "counter"); + value = vici_parse_value(ev, &len); + ck_assert_int_eq(len, sizeof(*value)); + ck_assert(vici_parse(ev) == VICI_PARSE_END); + + ck_assert_int_eq(*count, *value); + (*count)++; +} + +START_TEST(test_raise_events) +{ + vici_dispatcher_t *dispatcher; + vici_res_t *res; + vici_conn_t *conn; + int count = 0; + + lib->processor->set_threads(lib->processor, 8); + + dispatcher = vici_dispatcher_create(URI); + ck_assert(dispatcher); + + dispatcher->manage_event(dispatcher, "event", TRUE); + dispatcher->manage_command(dispatcher, "raise", raise_cb, dispatcher); + + vici_init(); + conn = vici_connect(URI); + ck_assert(conn); + + ck_assert(vici_register(conn, "event", raise_event_cb, &count) == 0); + + res = vici_submit(vici_begin("raise"), conn); + + ck_assert_int_eq(count, EVENT_COUNT); + ck_assert(res); + vici_free_res(res); + + vici_disconnect(conn); + + dispatcher->manage_event(dispatcher, "event", FALSE); + dispatcher->manage_command(dispatcher, "raise", NULL, NULL); + + lib->processor->cancel(lib->processor); + dispatcher->destroy(dispatcher); + + vici_deinit(); +} +END_TEST + START_TEST(test_stress) { vici_dispatcher_t *dispatcher; @@ -136,6 +208,10 @@ Suite *event_suite_create() tcase_add_test(tc, test_event); suite_add_tcase(s, tc); + tc = tcase_create("raise events"); + tcase_add_test(tc, test_raise_events); + suite_add_tcase(s, tc); + tc = tcase_create("stress"); tcase_add_test(tc, test_stress); suite_add_tcase(s, tc); From 9bfa397ebaca5ce2ce1414a6e60a36838b181d35 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Thu, 13 Feb 2014 09:44:34 +0100 Subject: [PATCH 25/72] vici: Fix dispatcher leak when handling unknown request --- src/libcharon/plugins/vici/vici_dispatcher.c | 21 ++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/libcharon/plugins/vici/vici_dispatcher.c b/src/libcharon/plugins/vici/vici_dispatcher.c index b66d0d5e8..3bb5eda26 100644 --- a/src/libcharon/plugins/vici/vici_dispatcher.c +++ b/src/libcharon/plugins/vici/vici_dispatcher.c @@ -220,26 +220,27 @@ void process_request(private_vici_dispatcher_t *this, char *name, u_int id, { vici_message_t *response = NULL; release_data_t *release; - - INIT(release, - .this = this, - ); + command_t *cmd; this->mutex->lock(this->mutex); - release->cmd = this->cmds->get(this->cmds, name); - if (release->cmd) + cmd = this->cmds->get(this->cmds, name); + if (cmd) { - release->cmd->uses++; + cmd->uses++; } this->mutex->unlock(this->mutex); - if (release->cmd) + if (cmd) { + INIT(release, + .this = this, + .cmd = cmd, + ); + thread_cleanup_push(release_command, release); release->request = vici_message_create_from_data(data, FALSE); - response = release->cmd->cb(release->cmd->user, release->cmd->name, - id, release->request); + response = release->cmd->cb(cmd->user, cmd->name, id, release->request); thread_cleanup_pop(TRUE); From e567675d293e3a564c1d2756755d7356700fb00c Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Wed, 12 Feb 2014 17:55:38 +0100 Subject: [PATCH 26/72] vici: Refactor socket to clean up locking Uses separate locks for socket read and write operations. While holding the socket reader lock, a different thread can still claim the socket write lock. This allows to asynchronously send event messages while holding the read lock. --- src/libcharon/plugins/vici/vici_socket.c | 320 +++++++++++++++++------ 1 file changed, 233 insertions(+), 87 deletions(-) diff --git a/src/libcharon/plugins/vici/vici_socket.c b/src/libcharon/plugins/vici/vici_socket.c index b2251982e..fa44c7061 100644 --- a/src/libcharon/plugins/vici/vici_socket.c +++ b/src/libcharon/plugins/vici/vici_socket.c @@ -17,7 +17,7 @@ #include #include -#include +#include #include #include #include @@ -74,9 +74,9 @@ struct private_vici_socket_t { linked_list_t *connections; /** - * rwlock for client connection list + * mutex for client connections */ - rwlock_t *lock; + mutex_t *mutex; }; /** @@ -85,9 +85,9 @@ struct private_vici_socket_t { typedef struct { /* reference to socket instance */ private_vici_socket_t *this; - /** connection identifier to disconnect */ + /** connection identifier of entry */ u_int id; -} entry_data_t; +} entry_selector_t; /** * Partially processed message @@ -109,16 +109,24 @@ typedef struct { typedef struct { /** reference to socket */ private_vici_socket_t *this; - /** mutex to lock this entry in/out buffers */ - mutex_t *mutex; /** associated stream */ stream_t *stream; /** queued messages to send, as msg_buf_t pointers */ array_t *out; /** input message buffer */ msg_buf_t in; + /** queued input messages to process, as chunk_t */ + array_t *queue; + /** do we have job processing input queue? */ + bool has_processor; /** client connection identifier */ u_int id; + /** any users reading over this connection? */ + int readers; + /** any users writing over this connection? */ + int writers; + /** condvar to wait for usage */ + condvar_t *cond; } entry_t; /** @@ -128,59 +136,148 @@ CALLBACK(destroy_entry, void, entry_t *entry) { msg_buf_t *out; + chunk_t chunk; entry->stream->destroy(entry->stream); - entry->this->disconnect(entry->this->user, entry->id); + entry->cond->destroy(entry->cond); - entry->mutex->destroy(entry->mutex); while (array_remove(entry->out, ARRAY_TAIL, &out)) { chunk_clear(&out->buf); free(out); } array_destroy(entry->out); + while (array_remove(entry->queue, ARRAY_TAIL, &chunk)) + { + chunk_clear(&chunk); + } + array_destroy(entry->queue); chunk_clear(&entry->in.buf); free(entry); } /** - * Find/remove entry by id, requires proper locking + * Find entry by stream (if given) or id, claim use */ -static entry_t* find_entry(private_vici_socket_t *this, u_int id, bool remove) +static entry_t* find_entry(private_vici_socket_t *this, stream_t *stream, + u_int id, bool reader, bool writer) { enumerator_t *enumerator; entry_t *entry, *found = NULL; + bool candidate = TRUE; - enumerator = this->connections->create_enumerator(this->connections); - while (enumerator->enumerate(enumerator, &entry)) + this->mutex->lock(this->mutex); + while (candidate && !found) { - if (entry->id == id) + candidate = FALSE; + enumerator = this->connections->create_enumerator(this->connections); + while (enumerator->enumerate(enumerator, &entry)) { - if (remove) + if (stream) { - this->connections->remove_at(this->connections, enumerator); + if (entry->stream != stream) + { + continue; + } + } + else + { + if (entry->id != id) + { + continue; + } + } + candidate = TRUE; + + if ((reader && entry->readers) || + (writer && entry->writers)) + { + entry->cond->wait(entry->cond, this->mutex); + break; + } + if (reader) + { + entry->readers++; + } + if (writer) + { + entry->writers++; } found = entry; break; } + enumerator->destroy(enumerator); } - enumerator->destroy(enumerator); + this->mutex->unlock(this->mutex); return found; } +/** + * Remove entry by id, claim use + */ +static entry_t* remove_entry(private_vici_socket_t *this, u_int id) +{ + enumerator_t *enumerator; + entry_t *entry, *found = NULL; + bool candidate = TRUE; + + this->mutex->lock(this->mutex); + while (candidate && !found) + { + candidate = FALSE; + enumerator = this->connections->create_enumerator(this->connections); + while (enumerator->enumerate(enumerator, &entry)) + { + if (entry->id == id) + { + candidate = TRUE; + if (entry->readers || entry->writers) + { + entry->cond->wait(entry->cond, this->mutex); + break; + } + this->connections->remove_at(this->connections, enumerator); + found = entry; + break; + } + } + enumerator->destroy(enumerator); + } + this->mutex->unlock(this->mutex); + + return found; +} + +/** + * Release a claimed entry + */ +static void put_entry(private_vici_socket_t *this, entry_t *entry, + bool reader, bool writer) +{ + this->mutex->lock(this->mutex); + if (reader) + { + entry->readers--; + } + if (writer) + { + entry->writers--; + } + entry->cond->signal(entry->cond); + this->mutex->unlock(this->mutex); +} + /** * Asynchronous callback to disconnect client */ CALLBACK(disconnect_async, job_requeue_t, - entry_data_t *data) + entry_selector_t *sel) { entry_t *entry; - data->this->lock->write_lock(data->this->lock); - entry = find_entry(data->this, data->id, TRUE); - data->this->lock->unlock(data->this->lock); + entry = remove_entry(sel->this, sel->id); if (entry) { destroy_entry(entry); @@ -193,15 +290,15 @@ CALLBACK(disconnect_async, job_requeue_t, */ static void disconnect(private_vici_socket_t *this, u_int id) { - entry_data_t *data; + entry_selector_t *sel; - INIT(data, + INIT(sel, .this = this, .id = id, ); lib->processor->queue_job(lib->processor, - (job_t*)callback_job_create(disconnect_async, data, free, NULL)); + (job_t*)callback_job_create(disconnect_async, sel, free, NULL)); } /** @@ -271,22 +368,26 @@ static bool do_write(private_vici_socket_t *this, entry_t *entry, * Send pending messages */ CALLBACK(on_write, bool, - entry_t *entry, stream_t *stream) + private_vici_socket_t *this, stream_t *stream) { - bool ret; + entry_t *entry; + bool ret = FALSE; - entry->mutex->lock(entry->mutex); - ret = do_write(entry->this, entry, stream); - if (ret) + entry = find_entry(this, stream, 0, FALSE, TRUE); + if (entry) { - /* unregister if we have no more messages to send */ - ret = array_count(entry->out) != 0; + ret = do_write(this, entry, stream); + if (ret) + { + /* unregister if we have no more messages to send */ + ret = array_count(entry->out) != 0; + } + else + { + disconnect(entry->this, entry->id); + } + put_entry(this, entry, FALSE, TRUE); } - else - { - disconnect(entry->this, entry->id); - } - entry->mutex->unlock(entry->mutex); return ret; } @@ -350,34 +451,81 @@ static bool do_read(private_vici_socket_t *this, entry_t *entry, return TRUE; } +/** + * Callback processing incoming requestes in strict order + */ +CALLBACK(process_queue, job_requeue_t, + entry_selector_t *sel) +{ + entry_t *entry; + chunk_t chunk; + bool found; + u_int id; + + while (TRUE) + { + entry = find_entry(sel->this, NULL, sel->id, TRUE, FALSE); + if (!entry) + { + break; + } + + found = array_remove(entry->queue, ARRAY_HEAD, &chunk); + if (!found) + { + entry->has_processor = FALSE; + } + id = entry->id; + put_entry(sel->this, entry, TRUE, FALSE); + if (!found) + { + break; + } + + thread_cleanup_push(free, chunk.ptr); + sel->this->inbound(sel->this->user, id, chunk); + thread_cleanup_pop(TRUE); + } + return JOB_REQUEUE_NONE; +} + /** * Process incoming messages */ CALLBACK(on_read, bool, - entry_t *entry, stream_t *stream) + private_vici_socket_t *this, stream_t *stream) { - chunk_t data = chunk_empty; - bool ret; + entry_selector_t *sel; + entry_t *entry; + bool ret = FALSE; - entry->mutex->lock(entry->mutex); - ret = do_read(entry->this, entry, stream); - if (!ret) + entry = find_entry(this, stream, 0, TRUE, FALSE); + if (entry) { - disconnect(entry->this, entry->id); - } - if (entry->in.buf.len == entry->in.done) - { - data = entry->in.buf; - entry->in.buf = chunk_empty; - entry->in.hdrlen = entry->in.done = 0; - } - entry->mutex->unlock(entry->mutex); + ret = do_read(this, entry, stream); + if (!ret) + { + disconnect(this, entry->id); + } + else if (entry->in.buf.len == entry->in.done) + { + array_insert(entry->queue, ARRAY_TAIL, &entry->in.buf); + entry->in.buf = chunk_empty; + entry->in.hdrlen = entry->in.done = 0; - if (data.len) - { - thread_cleanup_push(free, data.ptr); - entry->this->inbound(entry->this->user, entry->id, data); - thread_cleanup_pop(TRUE); + if (!entry->has_processor) + { + INIT(sel, + .this = this, + .id = entry->id, + ); + lib->processor->queue_job(lib->processor, + (job_t*)callback_job_create(process_queue, + sel, free, NULL)); + entry->has_processor = TRUE; + } + } + put_entry(this, entry, TRUE, FALSE); } return ret; @@ -386,7 +534,8 @@ CALLBACK(on_read, bool, /** * Process connection request */ -static bool on_accept(private_vici_socket_t *this, stream_t *stream) +CALLBACK(on_accept, bool, + private_vici_socket_t *this, stream_t *stream) { entry_t *entry; u_int id; @@ -398,13 +547,18 @@ static bool on_accept(private_vici_socket_t *this, stream_t *stream) .stream = stream, .id = id, .out = array_create(0, 0), - .mutex = mutex_create(MUTEX_TYPE_RECURSIVE), + .queue = array_create(sizeof(chunk_t), 0), + .cond = condvar_create(CONDVAR_TYPE_DEFAULT), + .readers = 1, ); - this->lock->write_lock(this->lock); + this->mutex->lock(this->mutex); this->connections->insert_last(this->connections, entry); - stream->on_read(stream, on_read, entry); - this->lock->unlock(this->lock); + this->mutex->unlock(this->mutex); + + stream->on_read(stream, on_read, this); + + put_entry(this, entry, TRUE, FALSE); this->connect(this->user, id); @@ -412,22 +566,19 @@ static bool on_accept(private_vici_socket_t *this, stream_t *stream) } /** - * Enable on_write callback to send data + * Async callback to enable writer */ -CALLBACK(on_write_async, job_requeue_t, - entry_data_t *data) +CALLBACK(enable_writer, job_requeue_t, + entry_selector_t *sel) { - private_vici_socket_t *this = data->this; entry_t *entry; - this->lock->read_lock(this->lock); - entry = find_entry(this, data->id, FALSE); + entry = find_entry(sel->this, NULL, sel->id, FALSE, TRUE); if (entry) { - entry->stream->on_write(entry->stream, on_write, entry); + entry->stream->on_write(entry->stream, on_write, sel->this); + put_entry(sel->this, entry, FALSE, TRUE); } - this->lock->unlock(this->lock); - return JOB_REQUEUE_NONE; } @@ -436,12 +587,11 @@ METHOD(vici_socket_t, send_, void, { if (msg.len <= (u_int16_t)~0) { - entry_data_t *data; + entry_selector_t *sel; msg_buf_t *out; entry_t *entry; - this->lock->read_lock(this->lock); - entry = find_entry(this, id, FALSE); + entry = find_entry(this, NULL, id, FALSE, TRUE); if (entry) { INIT(out, @@ -449,28 +599,24 @@ METHOD(vici_socket_t, send_, void, ); htoun16(out->hdr, msg.len); - entry->mutex->lock(entry->mutex); array_insert(entry->out, ARRAY_TAIL, out); - entry->mutex->unlock(entry->mutex); - if (array_count(entry->out) == 1) - { - INIT(data, + { /* asynchronously re-enable on_write callback when we get data */ + INIT(sel, .this = this, .id = entry->id, ); - /* asynchronously enable writing, as this might be called - * from the on_read() callback. */ lib->processor->queue_job(lib->processor, - (job_t*)callback_job_create(on_write_async, - data, free, NULL)); + (job_t*)callback_job_create(enable_writer, + sel, free, NULL)); } + put_entry(this, entry, FALSE, TRUE); } else { DBG1(DBG_CFG, "vici connection %u unknown", id); + chunk_clear(&msg); } - this->lock->unlock(this->lock); } else { @@ -484,7 +630,7 @@ METHOD(vici_socket_t, destroy, void, { DESTROY_IF(this->service); this->connections->destroy_function(this->connections, destroy_entry); - this->lock->destroy(this->lock); + this->mutex->destroy(this->mutex); free(this); } @@ -502,7 +648,7 @@ vici_socket_t *vici_socket_create(char *uri, vici_inbound_cb_t inbound, .send = _send_, .destroy = _destroy, }, - .lock = rwlock_create(RWLOCK_TYPE_DEFAULT), + .mutex = mutex_create(MUTEX_TYPE_DEFAULT), .connections = linked_list_create(), .inbound = inbound, .connect = connect, @@ -517,8 +663,8 @@ vici_socket_t *vici_socket_create(char *uri, vici_inbound_cb_t inbound, destroy(this); return NULL; } - this->service->on_accept(this->service, (stream_service_cb_t)on_accept, - this, JOB_PRIO_CRITICAL, 0); + this->service->on_accept(this->service, on_accept, this, + JOB_PRIO_CRITICAL, 0); return &this->public; } From 550f3f5646a076f39d6c7200698cafc28ba3b206 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Thu, 13 Feb 2014 15:20:05 +0100 Subject: [PATCH 27/72] vici: Extract CHILD_SA config lookup method --- src/libcharon/plugins/vici/vici_control.c | 40 +++++++++++++++-------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/src/libcharon/plugins/vici/vici_control.c b/src/libcharon/plugins/vici/vici_control.c index b48a8c1fc..d01631bc0 100644 --- a/src/libcharon/plugins/vici/vici_control.c +++ b/src/libcharon/plugins/vici/vici_control.c @@ -131,12 +131,36 @@ static child_cfg_t* get_child_from_peer(peer_cfg_t *peer_cfg, char *name) return found; } +/** + * Find a peer/child config from a child config name + */ +static child_cfg_t* find_child_cfg(char *name, peer_cfg_t **out) +{ + enumerator_t *enumerator; + peer_cfg_t *peer_cfg; + child_cfg_t *child_cfg; + + enumerator = charon->backends->create_peer_cfg_enumerator( + charon->backends, NULL, NULL, NULL, NULL, IKE_ANY); + while (enumerator->enumerate(enumerator, &peer_cfg)) + { + child_cfg = get_child_from_peer(peer_cfg, name); + if (child_cfg) + { + *out = peer_cfg->get_ref(peer_cfg); + break; + } + } + enumerator->destroy(enumerator); + + return child_cfg; +} + CALLBACK(initiate, vici_message_t*, private_vici_control_t *this, char *name, u_int id, vici_message_t *request) { child_cfg_t *child_cfg = NULL; peer_cfg_t *peer_cfg; - enumerator_t *enumerator; char *child; u_int timeout; log_info_t log = { @@ -152,19 +176,7 @@ CALLBACK(initiate, vici_message_t*, { return send_reply(this, "missing configuration name"); } - enumerator = charon->backends->create_peer_cfg_enumerator(charon->backends, - NULL, NULL, NULL, NULL, IKE_ANY); - while (enumerator->enumerate(enumerator, &peer_cfg)) - { - child_cfg = get_child_from_peer(peer_cfg, child); - if (child_cfg) - { - peer_cfg->get_ref(peer_cfg); - break; - } - } - enumerator->destroy(enumerator); - + child_cfg = find_child_cfg(child, &peer_cfg); if (!child_cfg) { return send_reply(this, "CHILD_SA config '%s' not found", child); From 5c6e81dcf8beea01a5b0491a445eec5830090823 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Thu, 13 Feb 2014 15:20:34 +0100 Subject: [PATCH 28/72] vici: Add install/uninstall commands to manage trap and shunt policies --- src/libcharon/plugins/vici/vici_control.c | 112 ++++++++++++++++++++++ 1 file changed, 112 insertions(+) diff --git a/src/libcharon/plugins/vici/vici_control.c b/src/libcharon/plugins/vici/vici_control.c index d01631bc0..f74f1d8c1 100644 --- a/src/libcharon/plugins/vici/vici_control.c +++ b/src/libcharon/plugins/vici/vici_control.c @@ -311,6 +311,116 @@ CALLBACK(terminate, vici_message_t*, return reply; } +/** + * Find reqid of an existing CHILD_SA + */ +static u_int32_t find_reqid(child_cfg_t *cfg) +{ + enumerator_t *enumerator, *children; + child_sa_t *child_sa; + ike_sa_t *ike_sa; + u_int32_t reqid; + + reqid = charon->traps->find_reqid(charon->traps, cfg); + if (reqid) + { /* already trapped */ + return reqid; + } + + enumerator = charon->controller->create_ike_sa_enumerator( + charon->controller, TRUE); + while (!reqid && enumerator->enumerate(enumerator, &ike_sa)) + { + children = ike_sa->create_child_sa_enumerator(ike_sa); + while (children->enumerate(children, &child_sa)) + { + if (streq(cfg->get_name(cfg), child_sa->get_name(child_sa))) + { + reqid = child_sa->get_reqid(child_sa); + break; + } + } + children->destroy(children); + } + enumerator->destroy(enumerator); + return reqid; +} + +CALLBACK(install, vici_message_t*, + private_vici_control_t *this, char *name, u_int id, vici_message_t *request) +{ + child_cfg_t *child_cfg = NULL; + peer_cfg_t *peer_cfg; + char *child; + bool ok; + + child = request->get_str(request, NULL, "child"); + if (!child) + { + return send_reply(this, "missing configuration name"); + } + child_cfg = find_child_cfg(child, &peer_cfg); + if (!child_cfg) + { + return send_reply(this, "configuration name not found"); + } + switch (child_cfg->get_mode(child_cfg)) + { + case MODE_PASS: + case MODE_DROP: + ok = charon->shunts->install(charon->shunts, child_cfg); + break; + default: + ok = charon->traps->install(charon->traps, peer_cfg, child_cfg, + find_reqid(child_cfg)); + break; + } + peer_cfg->destroy(peer_cfg); + child_cfg->destroy(child_cfg); + + return send_reply(this, ok ? NULL : "installing policy '%s' failed", child); +} + +CALLBACK(uninstall, vici_message_t*, + private_vici_control_t *this, char *name, u_int id, vici_message_t *request) +{ + child_sa_t *child_sa; + enumerator_t *enumerator; + u_int32_t reqid = 0; + char *child; + + child = request->get_str(request, NULL, "child"); + if (!child) + { + return send_reply(this, "missing configuration name"); + } + if (charon->shunts->uninstall(charon->shunts, child)) + { + return send_reply(this, NULL); + } + + enumerator = charon->traps->create_enumerator(charon->traps); + while (enumerator->enumerate(enumerator, NULL, &child_sa)) + { + if (streq(child, child_sa->get_name(child_sa))) + { + reqid = child_sa->get_reqid(child_sa); + break; + } + } + enumerator->destroy(enumerator); + + if (reqid) + { + if (charon->traps->uninstall(charon->traps, reqid)) + { + return send_reply(this, NULL); + } + return send_reply(this, "uninstalling policy '%s' failed", child); + } + return send_reply(this, "policy '%s' not found", child); +} + static void manage_command(private_vici_control_t *this, char *name, vici_command_cb_t cb, bool reg) { @@ -325,6 +435,8 @@ static void manage_commands(private_vici_control_t *this, bool reg) { manage_command(this, "initiate", initiate, reg); manage_command(this, "terminate", terminate, reg); + manage_command(this, "install", install, reg); + manage_command(this, "uninstall", uninstall, reg); this->dispatcher->manage_event(this->dispatcher, "control-log", reg); } From 1f2e63ea41a97f4a9eb1fe87640e8aa6b14a9a83 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Thu, 13 Feb 2014 16:12:21 +0100 Subject: [PATCH 29/72] vici: Add a list-policy command to query trap and shunt policies --- src/libcharon/plugins/vici/vici_query.c | 147 ++++++++++++++++++++++++ 1 file changed, 147 insertions(+) diff --git a/src/libcharon/plugins/vici/vici_query.c b/src/libcharon/plugins/vici/vici_query.c index 8c7a1de34..2ecc65d10 100644 --- a/src/libcharon/plugins/vici/vici_query.c +++ b/src/libcharon/plugins/vici/vici_query.c @@ -337,6 +337,151 @@ CALLBACK(list_sas, vici_message_t*, return b->finalize(b); } +/** + * Raise a list-policy event for given CHILD_SA + */ +static void raise_policy(private_vici_query_t *this, u_int id, child_sa_t *child) +{ + enumerator_t *enumerator; + traffic_selector_t *ts; + vici_builder_t *b; + + b = vici_builder_create(); + b->begin_section(b, child->get_name(child)); + + b->add_kv(b, "mode", "%N", ipsec_mode_names, child->get_mode(child)); + + b->begin_list(b, "local-ts"); + enumerator = child->create_ts_enumerator(child, TRUE); + while (enumerator->enumerate(enumerator, &ts)) + { + b->add_li(b, "%R", ts); + } + enumerator->destroy(enumerator); + b->end_list(b /* local-ts */); + + b->begin_list(b, "remote-ts"); + enumerator = child->create_ts_enumerator(child, FALSE); + while (enumerator->enumerate(enumerator, &ts)) + { + b->add_li(b, "%R", ts); + } + enumerator->destroy(enumerator); + b->end_list(b /* remote-ts */); + + b->end_section(b); + + this->dispatcher->raise_event(this->dispatcher, "list-policy", id, + b->finalize(b)); +} + +/** + * Raise a list-policy event for given CHILD_SA config + */ +static void raise_policy_cfg(private_vici_query_t *this, u_int id, + child_cfg_t *cfg) +{ + enumerator_t *enumerator; + linked_list_t *list; + traffic_selector_t *ts; + vici_builder_t *b; + + b = vici_builder_create(); + b->begin_section(b, cfg->get_name(cfg)); + + b->add_kv(b, "mode", "%N", ipsec_mode_names, cfg->get_mode(cfg)); + + b->begin_list(b, "local-ts"); + list = cfg->get_traffic_selectors(cfg, TRUE, NULL, NULL); + enumerator = list->create_enumerator(list); + while (enumerator->enumerate(enumerator, &ts)) + { + b->add_li(b, "%R", ts); + } + enumerator->destroy(enumerator); + list->destroy_offset(list, offsetof(traffic_selector_t, destroy)); + b->end_list(b /* local-ts */); + + b->begin_list(b, "remote-ts"); + list = cfg->get_traffic_selectors(cfg, FALSE, NULL, NULL); + enumerator = list->create_enumerator(list); + while (enumerator->enumerate(enumerator, &ts)) + { + b->add_li(b, "%R", ts); + } + enumerator->destroy(enumerator); + list->destroy_offset(list, offsetof(traffic_selector_t, destroy)); + b->end_list(b /* remote-ts */); + + b->end_section(b); + + this->dispatcher->raise_event(this->dispatcher, "list-policy", id, + b->finalize(b)); +} + +CALLBACK(list_policies, vici_message_t*, + private_vici_query_t *this, char *name, u_int id, vici_message_t *request) +{ + enumerator_t *enumerator; + vici_builder_t *b; + child_sa_t *child_sa; + child_cfg_t *child_cfg; + bool drop, pass, trap; + char *child; + + drop = request->get_str(request, NULL, "drop") != NULL; + pass = request->get_str(request, NULL, "pass") != NULL; + trap = request->get_str(request, NULL, "trap") != NULL; + child = request->get_str(request, NULL, "child"); + + if (trap) + { + enumerator = charon->traps->create_enumerator(charon->traps); + while (enumerator->enumerate(enumerator, NULL, &child_sa)) + { + if (child && !streq(child, child_sa->get_name(child_sa))) + { + continue; + } + raise_policy(this, id, child_sa); + } + enumerator->destroy(enumerator); + } + + if (drop || pass) + { + enumerator = charon->shunts->create_enumerator(charon->shunts); + while (enumerator->enumerate(enumerator, &child_cfg)) + { + if (child && !streq(child, child_cfg->get_name(child_cfg))) + { + continue; + } + switch (child_cfg->get_mode(child_cfg)) + { + case MODE_DROP: + if (drop) + { + raise_policy_cfg(this, id, child_cfg); + } + break; + case MODE_PASS: + if (pass) + { + raise_policy_cfg(this, id, child_cfg); + } + break; + default: + break; + } + } + enumerator->destroy(enumerator); + } + + b = vici_builder_create(); + return b->finalize(b); +} + CALLBACK(version, vici_message_t*, private_vici_query_t *this, char *name, u_int id, vici_message_t *request) { @@ -371,7 +516,9 @@ static void manage_command(private_vici_query_t *this, static void manage_commands(private_vici_query_t *this, bool reg) { this->dispatcher->manage_event(this->dispatcher, "list-sa", reg); + this->dispatcher->manage_event(this->dispatcher, "list-policy", reg); manage_command(this, "list-sas", list_sas, reg); + manage_command(this, "list-policies", list_policies, reg); manage_command(this, "version", version, reg); } From dd5ce0a97ad431c07159bac3c4bf53145eeb9731 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Mon, 17 Feb 2014 18:27:45 +0100 Subject: [PATCH 30/72] vici: Add generic callback based vici message parsing --- src/libcharon/plugins/vici/vici_message.c | 95 +++++++++++++++++++++++ src/libcharon/plugins/vici/vici_message.h | 44 +++++++++++ 2 files changed, 139 insertions(+) diff --git a/src/libcharon/plugins/vici/vici_message.c b/src/libcharon/plugins/vici/vici_message.c index 644cbce59..078c33ead 100644 --- a/src/libcharon/plugins/vici/vici_message.c +++ b/src/libcharon/plugins/vici/vici_message.c @@ -412,6 +412,100 @@ METHOD(vici_message_t, get_encoding, chunk_t, return this->encoding; } +/** + * Private parse context data + */ +struct vici_parse_context_t { + /** current section nesting level */ + int level; + /** parse enumerator */ + enumerator_t *e; +}; + +METHOD(vici_message_t, parse, bool, + private_vici_message_t *this, vici_parse_context_t *ctx, + vici_section_cb_t section, vici_value_cb_t kv, vici_value_cb_t li, + void *user) +{ + vici_parse_context_t root = {}; + char *name, *list = NULL; + vici_type_t type; + chunk_t value; + int base; + bool ok = TRUE; + + if (!ctx) + { + ctx = &root; + root.e = create_enumerator(this); + } + + base = ctx->level; + + while (ok) + { + ok = ctx->e->enumerate(ctx->e, &type, &name, &value); + if (ok) + { + switch (type) + { + case VICI_KEY_VALUE: + if (ctx->level == base && kv) + { + name = strdup(name); + this->strings->insert_last(this->strings, name); + ok = kv(user, &this->public, name, value); + } + continue; + case VICI_LIST_START: + if (ctx->level == base) + { + list = strdup(name); + this->strings->insert_last(this->strings, list); + } + continue; + case VICI_LIST_ITEM: + if (list && li) + { + name = strdup(name); + this->strings->insert_last(this->strings, name); + ok = li(user, &this->public, list, value); + } + continue; + case VICI_LIST_END: + if (ctx->level == base) + { + list = NULL; + } + continue; + case VICI_SECTION_START: + if (ctx->level++ == base && section) + { + name = strdup(name); + this->strings->insert_last(this->strings, name); + ok = section(user, &this->public, ctx, name); + } + continue; + case VICI_SECTION_END: + if (ctx->level-- == base) + { + break; + } + continue; + case VICI_END: + break; + } + } + break; + } + + if (ctx == &root) + { + root.e->destroy(root.e); + } + return ok; +} + METHOD(vici_message_t, dump, bool, private_vici_message_t *this, char *label, FILE *out) { @@ -506,6 +600,7 @@ vici_message_t *vici_message_create_from_data(chunk_t data, bool cleanup) .get_value = _get_value, .vget_value = _vget_value, .get_encoding = _get_encoding, + .parse = _parse, .dump = _dump, .destroy = _destroy, }, diff --git a/src/libcharon/plugins/vici/vici_message.h b/src/libcharon/plugins/vici/vici_message.h index 4fc59d746..8817959c1 100644 --- a/src/libcharon/plugins/vici/vici_message.h +++ b/src/libcharon/plugins/vici/vici_message.h @@ -24,6 +24,7 @@ #include typedef struct vici_message_t vici_message_t; +typedef struct vici_parse_context_t vici_parse_context_t; typedef enum vici_type_t vici_type_t; /** @@ -47,6 +48,30 @@ enum vici_type_t { VICI_END }; +/** + * Callback function for key/value and list items, invoked by parse(). + * + * @param user user data, as passed to parse() + * @param message message currently parsing + * @param name name of key or list + * @param value parsed value + * @return TRUE if parsed successfully + */ +typedef bool (*vici_value_cb_t)(void *user, vici_message_t *message, + char *name, chunk_t value); + +/** + * Callback function for sections, invoked by parse(). + * + * @param user user data, as passed to parse() + * @param message message currently parsing + * @param ctx parse context, to pass to recursive parse() invocations. + * @param name name of the section + * @return TRUE if parsed successfully + */ +typedef bool (*vici_section_cb_t)(void *user, vici_message_t *message, + vici_parse_context_t *ctx, char *name); + /** * Names for vici encoding types */ @@ -137,6 +162,25 @@ struct vici_message_t { */ chunk_t (*get_encoding)(vici_message_t *this); + /** + * Parse a message using callback functions. + * + * Any of the callbacks may be NULL to skip this kind of item. Callbacks are + * invoked for the current section level only. To descent into sections, + * call parse() from within a section callback using the provided parse + * context. + * + * @param ctx parse context, NULL for root level + * @param section callback invoked for each section + * @param kv callback invoked for key/value pairs + * @param li callback invoked for list items + * @param user user data to pass to callbacks + * @return TRUE if parsed successfully + */ + bool (*parse)(vici_message_t *this, vici_parse_context_t *ctx, + vici_section_cb_t section, vici_value_cb_t kv, + vici_value_cb_t li, void *user); + /** * Dump a message text representation to a FILE stream. * From b3d8bd8d26519d7fcba514eabd5ea6c3b49ea3d7 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Mon, 17 Feb 2014 18:28:53 +0100 Subject: [PATCH 31/72] vici: Add backend providing in-memory connections --- src/libcharon/plugins/vici/Makefile.am | 1 + src/libcharon/plugins/vici/vici_config.c | 1539 ++++++++++++++++++++++ src/libcharon/plugins/vici/vici_config.h | 53 + src/libcharon/plugins/vici/vici_plugin.c | 14 + 4 files changed, 1607 insertions(+) create mode 100644 src/libcharon/plugins/vici/vici_config.c create mode 100644 src/libcharon/plugins/vici/vici_config.h diff --git a/src/libcharon/plugins/vici/Makefile.am b/src/libcharon/plugins/vici/Makefile.am index 6ec830ea4..ded5263f0 100644 --- a/src/libcharon/plugins/vici/Makefile.am +++ b/src/libcharon/plugins/vici/Makefile.am @@ -20,6 +20,7 @@ libstrongswan_vici_la_SOURCES = \ vici_dispatcher.h vici_dispatcher.c \ vici_query.h vici_query.c \ vici_control.h vici_control.c \ + vici_config.h vici_config.c \ vici_plugin.h vici_plugin.c libstrongswan_vici_la_LDFLAGS = -module -avoid-version diff --git a/src/libcharon/plugins/vici/vici_config.c b/src/libcharon/plugins/vici/vici_config.c new file mode 100644 index 000000000..8daa78b85 --- /dev/null +++ b/src/libcharon/plugins/vici/vici_config.c @@ -0,0 +1,1539 @@ +/* + * Copyright (C) 2014 Martin Willi + * Copyright (C) 2014 revosec 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 . + * + * 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. + */ + +#define _GNU_SOURCE + +#include "vici_config.h" +#include "vici_builder.h" + +#include +#include +#include + +#include +#include + +typedef struct private_vici_config_t private_vici_config_t; + +/** + * Private data of an vici_config_t object. + */ +struct private_vici_config_t { + + /** + * Public vici_config_t interface. + */ + vici_config_t public; + + /** + * Dispatcher + */ + vici_dispatcher_t *dispatcher; + + /** + * List of loaded connections, as peer_cfg_t + */ + linked_list_t *conns; + + /** + * Lock for conns list + */ + rwlock_t *lock; +}; + +METHOD(backend_t, create_peer_cfg_enumerator, enumerator_t*, + private_vici_config_t *this, identification_t *me, identification_t *other) +{ + this->lock->read_lock(this->lock); + return enumerator_create_cleaner(this->conns->create_enumerator(this->conns), + (void*)this->lock->unlock, this->lock); +} + +/** + * Enumerator filter function for ike configs + */ +static bool ike_filter(void *data, peer_cfg_t **in, ike_cfg_t **out) +{ + *out = (*in)->get_ike_cfg(*in); + return TRUE; +} + +METHOD(backend_t, create_ike_cfg_enumerator, enumerator_t*, + private_vici_config_t *this, host_t *me, host_t *other) +{ + this->lock->read_lock(this->lock); + return enumerator_create_filter(this->conns->create_enumerator(this->conns), + (void*)ike_filter, this->lock, + (void*)this->lock->unlock); +} + +METHOD(backend_t, get_peer_cfg_by_name, peer_cfg_t*, + private_vici_config_t *this, char *name) +{ + peer_cfg_t *current, *found = NULL; + enumerator_t *enumerator; + + this->lock->read_lock(this->lock); + enumerator = this->conns->create_enumerator(this->conns); + while (enumerator->enumerate(enumerator, ¤t)) + { + if (streq(current->get_name(current), name)) + { + found = current; + found->get_ref(found); + break; + } + } + enumerator->destroy(enumerator); + this->lock->unlock(this->lock); + + return found; +} + +/** + * Create a (error) reply message + */ +static vici_message_t* create_reply(char *fmt, ...) +{ + vici_builder_t *builder; + va_list args; + + builder = vici_builder_create(); + builder->add_kv(builder, "success", fmt ? "no" : "yes"); + if (fmt) + { + va_start(args, fmt); + builder->vadd_kv(builder, "errmsg", fmt, args); + va_end(args); + } + return builder->finalize(builder); +} + +/** + * A rule to parse a key/value or list item + */ +typedef struct { + /** name of the key/value or list */ + char *name; + /** function to parse value */ + bool (*parse)(void *out, chunk_t value); + /** result, passed to parse() */ + void *out; +} parse_rule_t; + +/** + * Parse key/values using a rule-set + */ +static bool parse_rules(parse_rule_t *rules, int count, char *name, + chunk_t value, vici_message_t **reply) +{ + int i; + + for (i = 0; i < count; i++) + { + if (streq(name, rules[i].name)) + { + if (rules[i].parse(rules[i].out, value)) + { + return TRUE; + } + *reply = create_reply("invalid value for: %s, config discarded", + name); + return FALSE; + } + } + *reply = create_reply("unknown option: %s, config discarded", name); + return FALSE; +} + +/** + * Parse callback data, passed to each callback + */ +typedef struct { + private_vici_config_t *this; + vici_message_t *reply; +} request_data_t; + +/** + * Data associated to a peer config + */ +typedef struct { + request_data_t *request; + u_int32_t version; + bool aggressive; + bool encap; + bool mobike; + bool send_certreq; + bool pull; + cert_policy_t send_cert; + u_int64_t dpd_delay; + u_int64_t dpd_timeout; + fragmentation_t fragmentation; + unique_policy_t unique; + u_int32_t keyingtries; + u_int32_t local_port; + u_int32_t remote_port; + char *local_addrs; + char *remote_addrs; + linked_list_t *local; + linked_list_t *remote; + linked_list_t *proposals; + linked_list_t *children; + linked_list_t *vips; + u_int64_t reauth_time; + u_int64_t rekey_time; + u_int64_t over_time; + u_int64_t rand_time; +} peer_data_t; + +/** + * Log relevant auth config data + */ +static void log_auth(auth_cfg_t *auth) +{ + enumerator_t *enumerator; + auth_rule_t rule; + union { + uintptr_t u; + identification_t *id; + char *str; + } v; + + enumerator = auth->create_enumerator(auth); + while (enumerator->enumerate(enumerator, &rule, &v)) + { + switch (rule) + { + case AUTH_RULE_AUTH_CLASS: + DBG2(DBG_CFG, " class = %N", auth_class_names, v.u); + break; + case AUTH_RULE_EAP_TYPE: + DBG2(DBG_CFG, " eap-type = %N", eap_type_names, v.u); + break; + case AUTH_RULE_EAP_VENDOR: + DBG2(DBG_CFG, " eap-vendor = %u", v.u); + break; + case AUTH_RULE_XAUTH_BACKEND: + DBG2(DBG_CFG, " xauth = %s", v.str); + break; + case AUTH_RULE_CRL_VALIDATION: + DBG2(DBG_CFG, " revocation = %N", cert_validation_names, v.u); + break; + case AUTH_RULE_IDENTITY: + DBG2(DBG_CFG, " id = %Y", v.id); + break; + case AUTH_RULE_AAA_IDENTITY: + DBG2(DBG_CFG, " aaa_id = %Y", v.id); + break; + case AUTH_RULE_EAP_IDENTITY: + DBG2(DBG_CFG, " eap_id = %Y", v.id); + break; + case AUTH_RULE_XAUTH_IDENTITY: + DBG2(DBG_CFG, " xauth_id = %Y", v.id); + break; + default: + break; + } + } + enumerator->destroy(enumerator); +} + +/** + * Log parsed peer data + */ +static void log_peer_data(peer_data_t *data) +{ + enumerator_t *enumerator; + auth_cfg_t *auth; + host_t *host; + + DBG2(DBG_CFG, " version = %u", data->version); + DBG2(DBG_CFG, " local_addrs = %s", data->local_addrs); + DBG2(DBG_CFG, " remote_addrs = %s", data->remote_addrs); + DBG2(DBG_CFG, " local_port = %u", data->local_port); + DBG2(DBG_CFG, " remote_port = %u", data->remote_port); + DBG2(DBG_CFG, " send_certreq = %u", data->send_certreq); + DBG2(DBG_CFG, " send_cert = %N", cert_policy_names, data->send_cert); + DBG2(DBG_CFG, " mobike = %u", data->mobike); + DBG2(DBG_CFG, " aggressive = %u", data->aggressive); + DBG2(DBG_CFG, " encap = %u", data->encap); + DBG2(DBG_CFG, " dpd_delay = %llu", data->dpd_delay); + DBG2(DBG_CFG, " dpd_timeout = %llu", data->dpd_timeout); + DBG2(DBG_CFG, " fragmentation = %u", data->fragmentation); + DBG2(DBG_CFG, " unique = %N", unique_policy_names, data->unique); + DBG2(DBG_CFG, " keyingtries = %u", data->keyingtries); + DBG2(DBG_CFG, " reauth_time = %llu", data->reauth_time); + DBG2(DBG_CFG, " rekey_time = %llu", data->rekey_time); + DBG2(DBG_CFG, " over_time = %llu", data->over_time); + DBG2(DBG_CFG, " rand_time = %llu", data->rand_time); + DBG2(DBG_CFG, " proposals = %#P", data->proposals); + + if (data->vips->get_count(data->vips)) + { + DBG2(DBG_CFG, " vips:"); + } + enumerator = data->vips->create_enumerator(data->vips); + while (enumerator->enumerate(enumerator, &host)) + { + DBG2(DBG_CFG, " %H", host); + } + enumerator->destroy(enumerator); + + enumerator = data->local->create_enumerator(data->local); + while (enumerator->enumerate(enumerator, &auth)) + { + DBG2(DBG_CFG, " local:"); + log_auth(auth); + } + enumerator->destroy(enumerator); + + enumerator = data->remote->create_enumerator(data->remote); + while (enumerator->enumerate(enumerator, &auth)) + { + DBG2(DBG_CFG, " remote:"); + log_auth(auth); + } + enumerator->destroy(enumerator); +} + +/** + * Clean up peer config data + */ +static void free_peer_data(peer_data_t *data) +{ + data->local->destroy_offset(data->local, + offsetof(auth_cfg_t, destroy)); + data->remote->destroy_offset(data->remote, + offsetof(auth_cfg_t, destroy)); + data->children->destroy_offset(data->children, + offsetof(child_cfg_t, destroy)); + data->proposals->destroy_offset(data->proposals, + offsetof(proposal_t, destroy)); + data->vips->destroy_offset(data->vips, offsetof(host_t, destroy)); + free(data->local_addrs); + free(data->remote_addrs); +} + +/** + * CHILD config data + */ +typedef struct { + request_data_t *request; + lifetime_cfg_t lft; + char* updown; + bool hostaccess; + bool ipcomp; + ipsec_mode_t mode; + action_t dpd_action; + u_int32_t reqid; + u_int32_t tfc; + mark_t mark_in; + mark_t mark_out; + u_int64_t inactivity; + linked_list_t *proposals; + linked_list_t *local_ts; + linked_list_t *remote_ts; +} child_data_t; + +/** + * Log parsed CHILD config data + */ +static void log_child_data(child_data_t *data, char *name) +{ + DBG2(DBG_CFG, " child %s:", name); + DBG2(DBG_CFG, " rekey_time = %llu", data->lft.time.rekey); + DBG2(DBG_CFG, " life_time = %llu", data->lft.time.life); + DBG2(DBG_CFG, " rand_time = %llu", data->lft.time.jitter); + DBG2(DBG_CFG, " rekey_bytes = %llu", data->lft.bytes.rekey); + DBG2(DBG_CFG, " life_bytes = %llu", data->lft.bytes.life); + DBG2(DBG_CFG, " rand_bytes = %llu", data->lft.bytes.jitter); + DBG2(DBG_CFG, " rekey_packets = %llu", data->lft.packets.rekey); + DBG2(DBG_CFG, " life_packets = %llu", data->lft.packets.life); + DBG2(DBG_CFG, " rand_packets = %llu", data->lft.packets.jitter); + DBG2(DBG_CFG, " updown = %s", data->updown); + DBG2(DBG_CFG, " hostaccess = %u", data->hostaccess); + DBG2(DBG_CFG, " ipcomp = %u", data->ipcomp); + DBG2(DBG_CFG, " mode = %N", ipsec_mode_names, data->mode); + DBG2(DBG_CFG, " dpd_action = %N", action_names, data->dpd_action); + DBG2(DBG_CFG, " reqid = %u", data->reqid); + DBG2(DBG_CFG, " tfc = %u", data->tfc); + DBG2(DBG_CFG, " mark_in = %u/%u", + data->mark_in.value, data->mark_in.mask); + DBG2(DBG_CFG, " mark_out = %u/%u", + data->mark_out.value, data->mark_out.mask); + DBG2(DBG_CFG, " inactivity = %llu", data->inactivity); + DBG2(DBG_CFG, " proposals = %#P", data->proposals); + DBG2(DBG_CFG, " local_ts = %#R", data->local_ts); + DBG2(DBG_CFG, " remote_ts = %#R", data->remote_ts); +} + +/** + * Clean up CHILD config data + */ +static void free_child_data(child_data_t *data) +{ + data->proposals->destroy_offset(data->proposals, + offsetof(proposal_t, destroy)); + data->local_ts->destroy_offset(data->local_ts, + offsetof(traffic_selector_t, destroy)); + data->remote_ts->destroy_offset(data->remote_ts, + offsetof(traffic_selector_t, destroy)); + free(data->updown); +} + +/** + * Auth config data + */ +typedef struct { + request_data_t *request; + auth_cfg_t *cfg; +} auth_data_t; + +/** + * Common proposal parsing + */ +static bool parse_proposal(linked_list_t *list, protocol_id_t proto, chunk_t v) +{ + char buf[128]; + proposal_t *proposal; + + if (!vici_stringify(v, buf, sizeof(buf))) + { + return FALSE; + } + if (strcaseeq("default", buf)) + { + proposal = proposal_create_default(proto); + } + else + { + proposal = proposal_create_from_string(proto, buf); + } + if (!proposal) + { + return FALSE; + } + list->insert_last(list, proposal); + return TRUE; +} + +/** + * Parse IKE proposal + */ +CALLBACK(parse_ike_proposal, bool, + linked_list_t *out, chunk_t v) +{ + return parse_proposal(out, PROTO_IKE, v); +} + +/** + * Parse ESP proposal + */ +CALLBACK(parse_esp_proposal, bool, + linked_list_t *out, chunk_t v) +{ + return parse_proposal(out, PROTO_ESP, v); +} + +/** + * Parse AH proposal + */ +CALLBACK(parse_ah_proposal, bool, + linked_list_t *out, chunk_t v) +{ + return parse_proposal(out, PROTO_AH, v); +} + +/** + * Parse a traffic selector + */ +CALLBACK(parse_ts, bool, + linked_list_t *out, chunk_t v) +{ + char buf[128], *protoport, *sep, *port = "", *end; + traffic_selector_t *ts; + struct protoent *protoent; + struct servent *svc; + long int p; + u_int16_t from = 0, to = 0xffff; + u_int8_t proto = 0; + + if (!vici_stringify(v, buf, sizeof(buf))) + { + return FALSE; + } + + protoport = strchr(buf, '['); + if (protoport) + { + *(protoport++) = '\0'; + + sep = strrchr(protoport, ']'); + if (!sep) + { + return FALSE; + } + *sep = '\0'; + + sep = strchr(protoport, '/'); + if (sep) + { /* protocol/port */ + *sep = '\0'; + port = sep + 1; + } + + if (streq(protoport, "any")) + { + proto = 0; + } + else + { + protoent = getprotobyname(protoport); + if (protoent) + { + proto = protoent->p_proto; + } + else + { + p = strtol(protoport, &end, 0); + if ((*protoport && *end) || p < 0 || p > 0xff) + { + return FALSE; + } + proto = (u_int8_t)p; + } + } + if (streq(port, "opaque")) + { + from = 0xffff; + to = 0; + } + else if (*port && !streq(port, "any")) + { + svc = getservbyname(port, NULL); + if (svc) + { + from = to = ntohs(svc->s_port); + } + else + { + p = strtol(port, &end, 0); + if (p < 0 || p > 0xffff) + { + return FALSE; + } + from = p; + if (*end == '-') + { + port = end + 1; + p = strtol(port, &end, 0); + if (p < 0 || p > 0xffff) + { + return FALSE; + } + } + to = p; + if (*end) + { + return FALSE; + } + } + } + } + if (streq(buf, "dynamic")) + { + ts = traffic_selector_create_dynamic(proto, from, to); + } + else + { + ts = traffic_selector_create_from_cidr(buf, proto, from, to); + } + if (!ts) + { + return FALSE; + } + out->insert_last(out, ts); + return TRUE; +} + +/** + * Parse a string + */ +CALLBACK(parse_string, bool, + char **out, chunk_t v) +{ + if (!chunk_printable(v, NULL, ' ')) + { + return FALSE; + } + free(*out); + *out = NULL; + if (asprintf(out, "%.*s", (int)v.len, v.ptr) == -1) + { + return FALSE; + } + return TRUE; +} + +/** + * Map a string to an integer + */ +typedef struct { + char *str; + int d; +} enum_map_t; + +/** + * Parse a string to an integer mapping + */ +static bool parse_map(enum_map_t *map, int count, int *out, chunk_t v) +{ + char buf[128]; + int i; + + if (!vici_stringify(v, buf, sizeof(buf))) + { + return FALSE; + } + for (i = 0; i < count; i++) + { + if (strcaseeq(map[i].str, buf)) + { + *out = map[i].d; + return TRUE; + } + } + return FALSE; +} + +/** + * Parse a boolean + */ +CALLBACK(parse_bool, bool, + bool *out, chunk_t v) +{ + enum_map_t map[] = { + { "yes", TRUE }, + { "true", TRUE }, + { "enabled", TRUE }, + { "1", TRUE }, + { "no", FALSE }, + { "false", FALSE }, + { "disabled", FALSE }, + { "0", FALSE }, + }; + int d; + + if (parse_map(map, countof(map), &d, v)) + { + *out = d; + return TRUE; + } + return FALSE; +} + +/** + * Parse a ipsec_mode_t + */ +CALLBACK(parse_mode, bool, + ipsec_mode_t *out, chunk_t v) +{ + enum_map_t map[] = { + { "tunnel", MODE_TUNNEL }, + { "transport", MODE_TRANSPORT }, + { "beet", MODE_BEET }, + { "drop", MODE_DROP }, + { "pass", MODE_PASS }, + }; + int d; + + if (parse_map(map, countof(map), &d, v)) + { + *out = d; + return TRUE; + } + return FALSE; +} + +/** + * Parse an action_t + */ +CALLBACK(parse_action, bool, + action_t *out, chunk_t v) +{ + enum_map_t map[] = { + { "start", ACTION_RESTART }, + { "restart", ACTION_RESTART }, + { "route", ACTION_ROUTE }, + { "none", ACTION_NONE }, + { "clear", ACTION_NONE }, + }; + int d; + + if (parse_map(map, countof(map), &d, v)) + { + *out = d; + return TRUE; + } + return FALSE; +} + +/** + * Parse a u_int32_t + */ +CALLBACK(parse_uint32, bool, + u_int32_t *out, chunk_t v) +{ + char buf[16], *end; + u_long l; + + if (!vici_stringify(v, buf, sizeof(buf))) + { + return FALSE; + } + l = strtoul(buf, &end, 0); + if (*end == 0) + { + *out = l; + return TRUE; + } + return FALSE; +} + +/** + * Parse a u_int64_t + */ +CALLBACK(parse_uint64, bool, + u_int64_t *out, chunk_t v) +{ + char buf[16], *end; + unsigned long long l; + + if (!vici_stringify(v, buf, sizeof(buf))) + { + return FALSE; + } + l = strtoull(buf, &end, 0); + if (*end == 0) + { + *out = l; + return TRUE; + } + return FALSE; +} + +/** + * Parse a relative time + */ +CALLBACK(parse_time, bool, + u_int64_t *out, chunk_t v) +{ + char buf[16], *end; + u_long l; + + if (!vici_stringify(v, buf, sizeof(buf))) + { + return FALSE; + } + + l = strtoul(buf, &end, 0); + while (*end == ' ') + { + end++; + } + switch (*end) + { + case 'd': + case 'D': + l *= 24; + /* fall */ + case 'h': + case 'H': + l *= 60; + /* fall */ + case 'm': + case 'M': + l *= 60; + /* fall */ + case 's': + case 'S': + end++; + break; + case '\0': + break; + default: + return FALSE; + } + if (*end) + { + return FALSE; + } + *out = l; + return TRUE; +} + +/** + * Parse byte volume + */ +CALLBACK(parse_bytes, bool, + u_int64_t *out, chunk_t v) +{ + char buf[16], *end; + unsigned long long l; + + if (!vici_stringify(v, buf, sizeof(buf))) + { + return FALSE; + } + + l = strtoull(buf, &end, 0); + while (*end == ' ') + { + end++; + } + switch (*end) + { + case 'g': + case 'G': + l *= 1024; + /* fall */ + case 'm': + case 'M': + l *= 1024; + /* fall */ + case 'k': + case 'K': + l *= 1024; + end++; + break; + case '\0': + break; + default: + return FALSE; + } + if (*end) + { + return FALSE; + } + *out = l; + return TRUE; +} + +/** + * Parse a mark_t + */ +CALLBACK(parse_mark, bool, + mark_t *out, chunk_t v) +{ + char buf[32]; + + if (!vici_stringify(v, buf, sizeof(buf))) + { + return FALSE; + } + return mark_from_string(buf, out); +} + +/** + * Parse authentication config + */ +CALLBACK(parse_auth, bool, + auth_cfg_t *cfg, chunk_t v) +{ + char buf[64], *pos; + eap_vendor_type_t *type; + + if (!vici_stringify(v, buf, sizeof(buf))) + { + return FALSE; + } + if (strcaseeq(buf, "pubkey")) + { + cfg->add(cfg, AUTH_RULE_AUTH_CLASS, AUTH_CLASS_PUBKEY); + return TRUE; + } + if (strcaseeq(buf, "psk")) + { + cfg->add(cfg, AUTH_RULE_AUTH_CLASS, AUTH_CLASS_PSK); + return TRUE; + } + if (strcasepfx(buf, "xauth")) + { + pos = strchr(buf, '-'); + if (pos) + { + cfg->add(cfg, AUTH_RULE_XAUTH_BACKEND, strdup(++pos)); + } + cfg->add(cfg, AUTH_RULE_AUTH_CLASS, AUTH_CLASS_XAUTH); + return TRUE; + } + if (strcasepfx(buf, "eap")) + { + cfg->add(cfg, AUTH_RULE_AUTH_CLASS, AUTH_CLASS_EAP); + + type = eap_vendor_type_from_string(buf); + if (type) + { + cfg->add(cfg, AUTH_RULE_EAP_TYPE, type->type); + if (type->vendor) + { + cfg->add(cfg, AUTH_RULE_EAP_VENDOR, type->vendor); + } + free(type); + } + return TRUE; + } + return FALSE; +} + +/** + * Parse identity; add as auth rule to config + */ +static bool parse_id(auth_cfg_t *cfg, auth_rule_t rule, chunk_t v) +{ + char buf[256]; + + if (!vici_stringify(v, buf, sizeof(buf))) + { + return FALSE; + } + cfg->add(cfg, rule, identification_create_from_string(buf)); + return TRUE; +} + +/** + * Parse IKE identity + */ +CALLBACK(parse_ike_id, bool, + auth_cfg_t *cfg, chunk_t v) +{ + return parse_id(cfg, AUTH_RULE_IDENTITY, v); +} + +/** + * Parse AAA identity + */ +CALLBACK(parse_aaa_id, bool, + auth_cfg_t *cfg, chunk_t v) +{ + return parse_id(cfg, AUTH_RULE_AAA_IDENTITY, v); +} + +/** + * Parse EAP identity + */ +CALLBACK(parse_eap_id, bool, + auth_cfg_t *cfg, chunk_t v) +{ + return parse_id(cfg, AUTH_RULE_EAP_IDENTITY, v); +} + +/** + * Parse XAuth identity + */ +CALLBACK(parse_xauth_id, bool, + auth_cfg_t *cfg, chunk_t v) +{ + return parse_id(cfg, AUTH_RULE_XAUTH_IDENTITY, v); +} + +/** + * Parse revocation status + */ +CALLBACK(parse_revocation, bool, + auth_cfg_t *cfg, chunk_t v) +{ + enum_map_t map[] = { + { "strict", VALIDATION_GOOD }, + { "ifuri", VALIDATION_SKIPPED }, + }; + int d; + + if (parse_map(map, countof(map), &d, v)) + { + cfg->add(cfg, AUTH_RULE_CRL_VALIDATION, d); + return TRUE; + } + return FALSE; +} + +/** + * Parse list items to comma separated strings + */ +CALLBACK(parse_stringlist, bool, + char **out, chunk_t v) +{ + char *current; + + if (!chunk_printable(v, NULL, ' ')) + { + return FALSE; + } + current = *out; + if (current) + { + if (asprintf(out, "%s, %.*s", current, (int)v.len, v.ptr) == -1) + { + return FALSE; + } + free(current); + } + else + { + if (asprintf(out, "%.*s", (int)v.len, v.ptr) == -1) + { + return FALSE; + } + } + return TRUE; +} + +/** + * Parse an fragmentation_t + */ +CALLBACK(parse_frag, bool, + fragmentation_t *out, chunk_t v) +{ + enum_map_t map[] = { + { "yes", FRAGMENTATION_YES }, + { "no", FRAGMENTATION_NO }, + { "force", FRAGMENTATION_FORCE }, + }; + int d; + + if (parse_map(map, countof(map), &d, v)) + { + *out = d; + return TRUE; + } + return FALSE; +} + +/** + * Parse a cert_policy_t + */ +CALLBACK(parse_send_cert, bool, + cert_policy_t *out, chunk_t v) +{ + enum_map_t map[] = { + { "ifasked", CERT_SEND_IF_ASKED }, + { "always", CERT_ALWAYS_SEND }, + { "never", CERT_NEVER_SEND }, + }; + int d; + + if (parse_map(map, countof(map), &d, v)) + { + *out = d; + return TRUE; + } + return FALSE; +} + +/** + * Parse a unique_policy_t + */ +CALLBACK(parse_unique, bool, + unique_policy_t *out, chunk_t v) +{ + enum_map_t map[] = { + { "never", UNIQUE_NEVER }, + { "no", UNIQUE_NO }, + { "replace", UNIQUE_REPLACE }, + { "keep", UNIQUE_KEEP }, + }; + int d; + + if (parse_map(map, countof(map), &d, v)) + { + *out = d; + return TRUE; + } + return FALSE; +} + +/** + * Parse host_t into a list + */ +CALLBACK(parse_hosts, bool, + linked_list_t *list, chunk_t v) +{ + char buf[64]; + host_t *host; + + if (!vici_stringify(v, buf, sizeof(buf))) + { + return FALSE; + } + host = host_create_from_string(buf, 0); + if (!host) + { + return FALSE; + } + list->insert_last(list, host); + return TRUE; +} + +CALLBACK(child_li, bool, + child_data_t *child, vici_message_t *message, char *name, chunk_t value) +{ + parse_rule_t rules[] = { + { "ah_proposals", parse_ah_proposal, child->proposals }, + { "esp_proposals", parse_esp_proposal, child->proposals }, + { "local_ts", parse_ts, child->local_ts }, + { "remote_ts", parse_ts, child->remote_ts }, + }; + + return parse_rules(rules, countof(rules), name, value, + &child->request->reply); +} + +CALLBACK(child_kv, bool, + child_data_t *child, vici_message_t *message, char *name, chunk_t value) +{ + parse_rule_t rules[] = { + { "updown", parse_string, &child->updown }, + { "hostaccess", parse_bool, &child->hostaccess }, + { "mode", parse_mode, &child->mode }, + { "rekey_time", parse_time, &child->lft.time.rekey }, + { "life_time", parse_time, &child->lft.time.life }, + { "rand_time", parse_time, &child->lft.time.jitter }, + { "rekey_bytes", parse_bytes, &child->lft.bytes.rekey }, + { "life_bytes", parse_bytes, &child->lft.bytes.life }, + { "rand_bytes", parse_bytes, &child->lft.bytes.jitter }, + { "rekey_packets", parse_uint64, &child->lft.packets.rekey }, + { "life_packets", parse_uint64, &child->lft.packets.life }, + { "rand_packets", parse_uint64, &child->lft.packets.jitter }, + { "dpd_action", parse_action, &child->dpd_action }, + { "ipcomp", parse_bool, &child->ipcomp }, + { "inactivity", parse_time, &child->inactivity }, + { "reqid", parse_uint32, &child->reqid }, + { "mark_in", parse_mark, &child->mark_in }, + { "mark_out", parse_mark, &child->mark_out }, + { "tfc_padding", parse_uint32, &child->tfc }, + }; + + return parse_rules(rules, countof(rules), name, value, + &child->request->reply); +} + +CALLBACK(auth_kv, bool, + auth_data_t *auth, vici_message_t *message, char *name, chunk_t value) +{ + parse_rule_t rules[] = { + { "auth", parse_auth, auth->cfg }, + { "id", parse_ike_id, auth->cfg }, + { "aaa_id", parse_aaa_id, auth->cfg }, + { "eap_id", parse_eap_id, auth->cfg }, + { "xauth_id", parse_xauth_id, auth->cfg }, + { "revocation", parse_revocation, auth->cfg }, + }; + + return parse_rules(rules, countof(rules), name, value, + &auth->request->reply); +} + +CALLBACK(peer_li, bool, + peer_data_t *peer, vici_message_t *message, char *name, chunk_t value) +{ + parse_rule_t rules[] = { + { "local_addrs", parse_stringlist, &peer->local_addrs }, + { "remote_addrs", parse_stringlist, &peer->remote_addrs }, + { "proposals", parse_ike_proposal, peer->proposals }, + { "vips", parse_hosts, peer->vips }, + }; + + return parse_rules(rules, countof(rules), name, value, + &peer->request->reply); +} + +CALLBACK(peer_kv, bool, + peer_data_t *peer, vici_message_t *message, char *name, chunk_t value) +{ + parse_rule_t rules[] = { + { "version", parse_uint32, &peer->version }, + { "aggressive", parse_bool, &peer->aggressive }, + { "pull", parse_bool, &peer->pull }, + { "encap", parse_bool, &peer->encap }, + { "mobike", parse_bool, &peer->mobike }, + { "dpd_delay", parse_time, &peer->dpd_delay }, + { "dpd_timeout", parse_time, &peer->dpd_timeout }, + { "fragmentation", parse_frag, &peer->fragmentation }, + { "send_certreq", parse_bool, &peer->send_certreq }, + { "send_cert", parse_send_cert, &peer->send_cert }, + { "keyingtries", parse_uint32, &peer->keyingtries }, + { "unique", parse_unique, &peer->unique }, + { "local_port", parse_uint32, &peer->local_port }, + { "remote_port", parse_uint32, &peer->remote_port }, + { "reauth_time", parse_time, &peer->reauth_time }, + { "rekey_time", parse_time, &peer->rekey_time }, + { "over_time", parse_time, &peer->over_time }, + { "rand_time", parse_time, &peer->rand_time }, + }; + + return parse_rules(rules, countof(rules), name, value, + &peer->request->reply); +} + +CALLBACK(children_sn, bool, + peer_data_t *peer, vici_message_t *message, vici_parse_context_t *ctx, + char *name) +{ + child_data_t child = { + .request = peer->request, + .proposals = linked_list_create(), + .local_ts = linked_list_create(), + .remote_ts = linked_list_create(), + .mode = MODE_TUNNEL, + .dpd_action = ACTION_NONE, + }; + child_cfg_t *cfg; + proposal_t *proposal; + traffic_selector_t *ts; + + if (!message->parse(message, ctx, NULL, child_kv, child_li, &child)) + { + free_child_data(&child); + return FALSE; + } + + if (child.local_ts->get_count(child.local_ts) == 0) + { + child.local_ts->insert_last(child.local_ts, + traffic_selector_create_dynamic(0, 0, 65535)); + } + if (child.remote_ts->get_count(child.remote_ts) == 0) + { + child.remote_ts->insert_last(child.remote_ts, + traffic_selector_create_dynamic(0, 0, 65535)); + } + if (child.proposals->get_count(child.proposals) == 0) + { + child.proposals->insert_last(child.proposals, + proposal_create_default(PROTO_ESP)); + } + + log_child_data(&child, name); + + cfg = child_cfg_create(name, &child.lft, child.updown, + child.hostaccess, child.mode, ACTION_NONE, + child.dpd_action, ACTION_NONE, child.ipcomp, + child.inactivity, child.reqid, &child.mark_in, + &child.mark_out, child.tfc); + + while (child.local_ts->remove_first(child.local_ts, + (void**)&ts) == SUCCESS) + { + cfg->add_traffic_selector(cfg, TRUE, ts); + } + while (child.remote_ts->remove_first(child.remote_ts, + (void**)&ts) == SUCCESS) + { + cfg->add_traffic_selector(cfg, FALSE, ts); + } + while (child.proposals->remove_first(child.proposals, + (void**)&proposal) == SUCCESS) + { + cfg->add_proposal(cfg, proposal); + } + + peer->children->insert_last(peer->children, cfg); + + free_child_data(&child); + + return TRUE; +} + +CALLBACK(peer_sn, bool, + peer_data_t *peer, vici_message_t *message, vici_parse_context_t *ctx, + char *name) +{ + if (strcaseeq(name, "children")) + { + return message->parse(message, ctx, children_sn, NULL, NULL, peer); + } + if (strcasepfx(name, "local") || + strcasepfx(name, "remote")) + { + auth_data_t auth = { + .request = peer->request, + .cfg = auth_cfg_create(), + }; + + if (!message->parse(message, ctx, NULL, auth_kv, NULL, &auth)) + { + auth.cfg->destroy(auth.cfg); + return FALSE; + } + + if (strcasepfx(name, "local")) + { + peer->local->insert_last(peer->local, auth.cfg); + } + else + { + peer->remote->insert_last(peer->remote, auth.cfg); + } + return TRUE; + } + peer->request->reply = create_reply("invalid section: %s", name); + return FALSE; +} + +/** + * Replace children of a peer config by a new config + */ +static void replace_children(private_vici_config_t *this, + peer_cfg_t *from, peer_cfg_t *to) +{ + enumerator_t *enumerator; + child_cfg_t *child; + + enumerator = to->create_child_cfg_enumerator(to); + while (enumerator->enumerate(enumerator, &child)) + { + to->remove_child_cfg(to, enumerator); + child->destroy(child); + } + enumerator->destroy(enumerator); + + enumerator = from->create_child_cfg_enumerator(from); + while (enumerator->enumerate(enumerator, &child)) + { + from->remove_child_cfg(from, enumerator); + to->add_child_cfg(to, child); + } + enumerator->destroy(enumerator); +} + +/** + * Merge/replace a peer config with existing configs + */ +static void merge_config(private_vici_config_t *this, peer_cfg_t *peer_cfg) +{ + enumerator_t *enumerator; + peer_cfg_t *current; + ike_cfg_t *ike_cfg; + bool merged = FALSE; + + this->lock->write_lock(this->lock); + + enumerator = this->conns->create_enumerator(this->conns); + while (enumerator->enumerate(enumerator, ¤t)) + { + if (streq(peer_cfg->get_name(peer_cfg), current->get_name(current))) + { + ike_cfg = current->get_ike_cfg(current); + if (peer_cfg->equals(peer_cfg, current) && + ike_cfg->equals(ike_cfg, peer_cfg->get_ike_cfg(peer_cfg))) + { + DBG1(DBG_CFG, "updated vici connection: %s", + peer_cfg->get_name(peer_cfg)); + replace_children(this, peer_cfg, current); + peer_cfg->destroy(peer_cfg); + } + else + { + DBG1(DBG_CFG, "replaced vici connection: %s", + peer_cfg->get_name(peer_cfg)); + this->conns->remove_at(this->conns, enumerator); + current->destroy(current); + this->conns->insert_last(this->conns, peer_cfg); + } + merged = TRUE; + break; + } + + } + enumerator->destroy(enumerator); + + if (!merged) + { + DBG1(DBG_CFG, "added vici connection: %s", peer_cfg->get_name(peer_cfg)); + this->conns->insert_last(this->conns, peer_cfg); + } + + this->lock->unlock(this->lock); +} + +CALLBACK(config_sn, bool, + request_data_t *request, vici_message_t *message, + vici_parse_context_t *ctx, char *name) +{ + peer_data_t peer = { + .request = request, + .local = linked_list_create(), + .remote = linked_list_create(), + .vips = linked_list_create(), + .children = linked_list_create(), + .proposals = linked_list_create(), + .mobike = TRUE, + .send_certreq = TRUE, + .pull = TRUE, + .send_cert = CERT_SEND_IF_ASKED, + .version = IKE_ANY, + .local_port = IKEV2_UDP_PORT, + .remote_port = IKEV2_UDP_PORT, + .fragmentation = FRAGMENTATION_NO, + .unique = UNIQUE_NO, + .keyingtries = 1, + }; + peer_cfg_t *peer_cfg; + ike_cfg_t *ike_cfg; + child_cfg_t *child_cfg; + auth_cfg_t *auth_cfg; + proposal_t *proposal; + + DBG2(DBG_CFG, " conn %s:", name); + + if (!message->parse(message, ctx, peer_sn, peer_kv, peer_li, &peer)) + { + free_peer_data(&peer); + return FALSE; + } + + if (peer.local->get_count(peer.local) == 0) + { + free_peer_data(&peer); + peer.request->reply = create_reply("missing local auth config"); + return FALSE; + } + if (peer.remote->get_count(peer.remote) == 0) + { + auth_cfg = auth_cfg_create(); + peer.remote->insert_last(peer.remote, auth_cfg); + } + if (peer.proposals->get_count(peer.proposals) == 0) + { + peer.proposals->insert_last(peer.proposals, + proposal_create_default(PROTO_IKE)); + } + if (!peer.local_addrs) + { + peer.local_addrs = strdup("%any"); + } + if (!peer.remote_addrs) + { + peer.remote_addrs = strdup("%any"); + } + + log_peer_data(&peer); + + ike_cfg = ike_cfg_create(peer.version, peer.send_certreq, peer.encap, + peer.local_addrs, peer.local_port, + peer.remote_addrs, peer.remote_port, + peer.fragmentation, 0); + peer_cfg = peer_cfg_create(name, ike_cfg, peer.send_cert, peer.unique, + peer.keyingtries, peer.rekey_time, peer.reauth_time, + peer.rand_time, peer.over_time, peer.mobike, + peer.aggressive, peer.pull, + peer.dpd_delay, peer.dpd_timeout, + FALSE, NULL, NULL); + + while (peer.local->remove_first(peer.local, + (void**)&auth_cfg) == SUCCESS) + { + peer_cfg->add_auth_cfg(peer_cfg, auth_cfg, TRUE); + } + while (peer.remote->remove_first(peer.remote, + (void**)&auth_cfg) == SUCCESS) + { + peer_cfg->add_auth_cfg(peer_cfg, auth_cfg, FALSE); + } + while (peer.children->remove_first(peer.children, + (void**)&child_cfg) == SUCCESS) + { + peer_cfg->add_child_cfg(peer_cfg, child_cfg); + } + while (peer.proposals->remove_first(peer.proposals, + (void**)&proposal) == SUCCESS) + { + ike_cfg->add_proposal(ike_cfg, proposal); + } + + free_peer_data(&peer); + + merge_config(request->this, peer_cfg); + + return TRUE; +} + +CALLBACK(load_conn, vici_message_t*, + private_vici_config_t *this, char *name, u_int id, vici_message_t *message) +{ + request_data_t request = { + .this = this, + }; + + if (!message->parse(message, NULL, config_sn, NULL, NULL, &request)) + { + if (request.reply) + { + return request.reply; + } + return create_reply("parsing request failed"); + } + return create_reply(NULL); +} + +static void manage_command(private_vici_config_t *this, + char *name, vici_command_cb_t cb, bool reg) +{ + this->dispatcher->manage_command(this->dispatcher, name, + reg ? cb : NULL, this); +} + +/** + * (Un-)register dispatcher functions + */ +static void manage_commands(private_vici_config_t *this, bool reg) +{ + manage_command(this, "load-conn", load_conn, reg); +} + +METHOD(vici_config_t, destroy, void, + private_vici_config_t *this) +{ + manage_commands(this, FALSE); + this->conns->destroy_offset(this->conns, offsetof(peer_cfg_t, destroy)); + this->lock->destroy(this->lock); + free(this); +} + +/** + * See header + */ +vici_config_t *vici_config_create(vici_dispatcher_t *dispatcher) +{ + private_vici_config_t *this; + + INIT(this, + .public = { + .backend = { + .create_peer_cfg_enumerator = _create_peer_cfg_enumerator, + .create_ike_cfg_enumerator = _create_ike_cfg_enumerator, + .get_peer_cfg_by_name = _get_peer_cfg_by_name, + }, + .destroy = _destroy, + }, + .dispatcher = dispatcher, + .conns = linked_list_create(), + .lock = rwlock_create(RWLOCK_TYPE_DEFAULT), + ); + + manage_commands(this, TRUE); + + return &this->public; +} diff --git a/src/libcharon/plugins/vici/vici_config.h b/src/libcharon/plugins/vici/vici_config.h new file mode 100644 index 000000000..820d5f300 --- /dev/null +++ b/src/libcharon/plugins/vici/vici_config.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2014 Martin Willi + * Copyright (C) 2014 revosec 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 . + * + * 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. + */ + +/** + * @defgroup vici_config vici_config + * @{ @ingroup vici + */ + +#ifndef VICI_CONFIG_H_ +#define VICI_CONFIG_H_ + +#include "vici_dispatcher.h" + +#include + +typedef struct vici_config_t vici_config_t; + +/** + * In-memory configuration backend, managed by VICI. + */ +struct vici_config_t { + + /** + * Implements a configuraiton backend. + */ + backend_t backend; + + /** + * Destroy a vici_config_t. + */ + void (*destroy)(vici_config_t *this); +}; +/** + * Create a vici_config instance. + * + * @param dispatcher dispatcher to receive requests from + * @return config backend + */ +vici_config_t *vici_config_create(vici_dispatcher_t *dispatcher); + +#endif /** VICI_CONFIG_H_ @}*/ diff --git a/src/libcharon/plugins/vici/vici_plugin.c b/src/libcharon/plugins/vici/vici_plugin.c index 0bceefca1..c447f2136 100644 --- a/src/libcharon/plugins/vici/vici_plugin.c +++ b/src/libcharon/plugins/vici/vici_plugin.c @@ -17,6 +17,7 @@ #include "vici_dispatcher.h" #include "vici_query.h" #include "vici_control.h" +#include "vici_config.h" #include #include @@ -47,6 +48,11 @@ struct private_vici_plugin_t { * Control commands */ vici_control_t *control; + + /** + * Configuration backend + */ + vici_config_t *config; }; METHOD(plugin_t, get_name, char*, @@ -72,12 +78,20 @@ static bool register_vici(private_vici_plugin_t *this, { this->query = vici_query_create(this->dispatcher); this->control = vici_control_create(this->dispatcher); + this->config = vici_config_create(this->dispatcher); + + charon->backends->add_backend(charon->backends, + &this->config->backend); return TRUE; } return FALSE; } else { + charon->backends->remove_backend(charon->backends, + &this->config->backend); + + this->config->destroy(this->config); this->control->destroy(this->control); this->query->destroy(this->query); this->dispatcher->destroy(this->dispatcher); From 37aa250cad2c9e52beae57bfbb0b2f1632362512 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Tue, 18 Feb 2014 11:58:28 +0100 Subject: [PATCH 32/72] vici: Make dispatcher a little more verbose --- src/libcharon/plugins/vici/vici_dispatcher.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/libcharon/plugins/vici/vici_dispatcher.c b/src/libcharon/plugins/vici/vici_dispatcher.c index 3bb5eda26..4245898cb 100644 --- a/src/libcharon/plugins/vici/vici_dispatcher.c +++ b/src/libcharon/plugins/vici/vici_dispatcher.c @@ -138,10 +138,12 @@ static void register_event(private_vici_dispatcher_t *this, char *name, if (event) { + DBG1(DBG_CFG, "vici client %u registered for: %s", id, name); send_op(this, id, VICI_EVENT_CONFIRM, NULL, NULL); } else { + DBG1(DBG_CFG, "vici client %u invalid registration: %s", id, name); send_op(this, id, VICI_EVENT_UNKNOWN, NULL, NULL); } } @@ -175,6 +177,8 @@ static void unregister_event(private_vici_dispatcher_t *this, char *name, } this->mutex->unlock(this->mutex); + DBG1(DBG_CFG, "vici client %u unregistered for: %s", id, name); + if (found) { send_op(this, id, VICI_EVENT_CONFIRM, NULL, NULL); @@ -237,6 +241,8 @@ void process_request(private_vici_dispatcher_t *this, char *name, u_int id, .cmd = cmd, ); + DBG1(DBG_CFG, "vici client %u requests: %s", id, name); + thread_cleanup_push(release_command, release); release->request = vici_message_create_from_data(data, FALSE); @@ -252,6 +258,7 @@ void process_request(private_vici_dispatcher_t *this, char *name, u_int id, } else { + DBG1(DBG_CFG, "vici client %u invalid request: %s", id, name); send_op(this, id, VICI_CMD_UNKNOWN, NULL, NULL); } } @@ -323,6 +330,7 @@ CALLBACK(inbound, void, CALLBACK(connect_, void, private_vici_dispatcher_t *this, u_int id) { + DBG1(DBG_CFG, "vici client %u connected", id); } CALLBACK(disconnect, void, @@ -349,6 +357,8 @@ CALLBACK(disconnect, void, } events->destroy(events); this->mutex->unlock(this->mutex); + + DBG1(DBG_CFG, "vici client %u disconnected", id); } METHOD(vici_dispatcher_t, manage_command, void, From 501ddf127b0ace4e4a94eaf4f127867a92ffed96 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Tue, 18 Feb 2014 15:32:43 +0100 Subject: [PATCH 33/72] vici: Add unload-conn and get-conns commands to manage loaded connections --- src/libcharon/plugins/vici/vici_config.c | 62 ++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/src/libcharon/plugins/vici/vici_config.c b/src/libcharon/plugins/vici/vici_config.c index 8daa78b85..62170a91f 100644 --- a/src/libcharon/plugins/vici/vici_config.c +++ b/src/libcharon/plugins/vici/vici_config.c @@ -1488,6 +1488,66 @@ CALLBACK(load_conn, vici_message_t*, return create_reply(NULL); } +CALLBACK(unload_conn, vici_message_t*, + private_vici_config_t *this, char *name, u_int id, vici_message_t *message) +{ + enumerator_t *enumerator; + peer_cfg_t *cfg; + bool found = FALSE; + char *conn; + + conn = message->get_str(message, NULL, "name"); + if (!conn) + { + return create_reply("missing connection name to unload"); + } + + this->lock->write_lock(this->lock); + enumerator = this->conns->create_enumerator(this->conns); + while (enumerator->enumerate(enumerator, &cfg)) + { + if (streq(cfg->get_name(cfg), conn)) + { + this->conns->remove_at(this->conns, enumerator); + cfg->destroy(cfg); + found = TRUE; + break; + } + } + enumerator->destroy(enumerator); + this->lock->unlock(this->lock); + + if (!found) + { + return create_reply("connection '%s' not found for unloading", conn); + } + return create_reply(NULL); +} + +CALLBACK(get_conns, vici_message_t*, + private_vici_config_t *this, char *name, u_int id, vici_message_t *message) +{ + vici_builder_t *builder; + enumerator_t *enumerator; + peer_cfg_t *cfg; + + builder = vici_builder_create(); + builder->begin_list(builder, "conns"); + + this->lock->read_lock(this->lock); + enumerator = this->conns->create_enumerator(this->conns); + while (enumerator->enumerate(enumerator, &cfg)) + { + builder->add_li(builder, "%s", cfg->get_name(cfg)); + } + enumerator->destroy(enumerator); + this->lock->unlock(this->lock); + + builder->end_list(builder); + + return builder->finalize(builder); +} + static void manage_command(private_vici_config_t *this, char *name, vici_command_cb_t cb, bool reg) { @@ -1501,6 +1561,8 @@ static void manage_command(private_vici_config_t *this, static void manage_commands(private_vici_config_t *this, bool reg) { manage_command(this, "load-conn", load_conn, reg); + manage_command(this, "unload-conn", unload_conn, reg); + manage_command(this, "get-conns", get_conns, reg); } METHOD(vici_config_t, destroy, void, From e1b65630b25fa52f7828329e2a5171d703dd07e4 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Tue, 18 Feb 2014 15:34:13 +0100 Subject: [PATCH 34/72] vici: Add a command listing all or specific loaded connections using events --- src/libcharon/plugins/vici/vici_query.c | 167 ++++++++++++++++++++++++ 1 file changed, 167 insertions(+) diff --git a/src/libcharon/plugins/vici/vici_query.c b/src/libcharon/plugins/vici/vici_query.c index 2ecc65d10..6656abe38 100644 --- a/src/libcharon/plugins/vici/vici_query.c +++ b/src/libcharon/plugins/vici/vici_query.c @@ -482,6 +482,171 @@ CALLBACK(list_policies, vici_message_t*, return b->finalize(b); } +/** + * Build sections for auth configs, local or remote + */ +static void build_auth_cfgs(peer_cfg_t *peer_cfg, bool local, vici_builder_t *b) +{ + enumerator_t *enumerator, *rules; + auth_rule_t rule; + auth_cfg_t *auth; + union { + uintptr_t u; + identification_t *id; + char *str; + } v; + + enumerator = peer_cfg->create_auth_cfg_enumerator(peer_cfg, local); + while (enumerator->enumerate(enumerator, &auth)) + { + b->begin_section(b, local ? "local" : "remote"); + + rules = auth->create_enumerator(auth); + while (rules->enumerate(rules, &rule, &v)) + { + switch (rule) + { + case AUTH_RULE_AUTH_CLASS: + b->add_kv(b, "class", "%N", auth_class_names, v.u); + break; + case AUTH_RULE_EAP_TYPE: + b->add_kv(b, "eap-type", "%N", eap_type_names, v.u); + break; + case AUTH_RULE_EAP_VENDOR: + b->add_kv(b, "eap-vendor", "%u", v.u); + break; + case AUTH_RULE_XAUTH_BACKEND: + b->add_kv(b, "xauth", "%s", v.str); + break; + case AUTH_RULE_CRL_VALIDATION: + b->add_kv(b, "revocation", "%N", cert_validation_names, v.u); + break; + case AUTH_RULE_IDENTITY: + b->add_kv(b, "id", "%Y", v.id); + break; + case AUTH_RULE_AAA_IDENTITY: + b->add_kv(b, "aaa_id", "%Y", v.id); + break; + case AUTH_RULE_EAP_IDENTITY: + b->add_kv(b, "eap_id", "%Y", v.id); + break; + case AUTH_RULE_XAUTH_IDENTITY: + b->add_kv(b, "xauth_id", "%Y", v.id); + break; + default: + break; + } + } + rules->destroy(rules); + + b->end_section(b); + } + enumerator->destroy(enumerator); +} + +CALLBACK(list_conns, vici_message_t*, + private_vici_query_t *this, char *name, u_int id, vici_message_t *request) +{ + enumerator_t *enumerator, *tokens, *selectors, *children; + peer_cfg_t *peer_cfg; + ike_cfg_t *ike_cfg; + child_cfg_t *child_cfg; + char *ike, *str; + linked_list_t *list; + traffic_selector_t *ts; + vici_builder_t *b; + + ike = request->get_str(request, NULL, "ike"); + + enumerator = charon->backends->create_peer_cfg_enumerator(charon->backends, + NULL, NULL, NULL, NULL, IKE_ANY); + while (enumerator->enumerate(enumerator, &peer_cfg)) + { + if (ike && !streq(ike, peer_cfg->get_name(peer_cfg))) + { + continue; + } + + b = vici_builder_create(); + b->begin_section(b, peer_cfg->get_name(peer_cfg)); + + ike_cfg = peer_cfg->get_ike_cfg(peer_cfg); + + b->begin_list(b, "local_addrs"); + str = ike_cfg->get_my_addr(ike_cfg); + tokens = enumerator_create_token(str, ",", " "); + while (tokens->enumerate(tokens, &str)) + { + b->add_li(b, "%s", str); + } + tokens->destroy(tokens); + b->end_list(b); + + b->begin_list(b, "remote_addrs"); + str = ike_cfg->get_other_addr(ike_cfg); + tokens = enumerator_create_token(str, ",", " "); + while (tokens->enumerate(tokens, &str)) + { + b->add_li(b, "%s", str); + } + tokens->destroy(tokens); + b->end_list(b); + + b->add_kv(b, "version", "%N", ike_version_names, + peer_cfg->get_ike_version(peer_cfg)); + + build_auth_cfgs(peer_cfg, TRUE, b); + build_auth_cfgs(peer_cfg, FALSE, b); + + b->begin_section(b, "children"); + + children = peer_cfg->create_child_cfg_enumerator(peer_cfg); + while (children->enumerate(children, &child_cfg)) + { + b->begin_section(b, child_cfg->get_name(child_cfg)); + + b->add_kv(b, "mode", "%N", ipsec_mode_names, + child_cfg->get_mode(child_cfg)); + + b->begin_list(b, "local-ts"); + list = child_cfg->get_traffic_selectors(child_cfg, TRUE, NULL, NULL); + selectors = list->create_enumerator(list); + while (selectors->enumerate(selectors, &ts)) + { + b->add_li(b, "%R", ts); + } + selectors->destroy(selectors); + list->destroy_offset(list, offsetof(traffic_selector_t, destroy)); + b->end_list(b /* local-ts */); + + b->begin_list(b, "remote-ts"); + list = child_cfg->get_traffic_selectors(child_cfg, FALSE, NULL, NULL); + selectors = list->create_enumerator(list); + while (selectors->enumerate(selectors, &ts)) + { + b->add_li(b, "%R", ts); + } + selectors->destroy(selectors); + list->destroy_offset(list, offsetof(traffic_selector_t, destroy)); + b->end_list(b /* remote-ts */); + + b->end_section(b); + } + children->destroy(children); + + b->end_section(b); /* children */ + + b->end_section(b); /* name */ + + this->dispatcher->raise_event(this->dispatcher, "list-conn", id, + b->finalize(b)); + } + enumerator->destroy(enumerator); + + b = vici_builder_create(); + return b->finalize(b); +} + CALLBACK(version, vici_message_t*, private_vici_query_t *this, char *name, u_int id, vici_message_t *request) { @@ -517,8 +682,10 @@ static void manage_commands(private_vici_query_t *this, bool reg) { this->dispatcher->manage_event(this->dispatcher, "list-sa", reg); this->dispatcher->manage_event(this->dispatcher, "list-policy", reg); + this->dispatcher->manage_event(this->dispatcher, "list-conn", reg); manage_command(this, "list-sas", list_sas, reg); manage_command(this, "list-policies", list_policies, reg); + manage_command(this, "list-conns", list_conns, reg); manage_command(this, "version", version, reg); } From de190f62c2e6978950e84501f7ee698a15916eac Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Wed, 19 Feb 2014 10:20:19 +0100 Subject: [PATCH 35/72] vici: Add a credential backend --- src/libcharon/plugins/vici/Makefile.am | 1 + src/libcharon/plugins/vici/vici_cred.c | 103 +++++++++++++++++++++++ src/libcharon/plugins/vici/vici_cred.h | 47 +++++++++++ src/libcharon/plugins/vici/vici_plugin.c | 8 ++ 4 files changed, 159 insertions(+) create mode 100644 src/libcharon/plugins/vici/vici_cred.c create mode 100644 src/libcharon/plugins/vici/vici_cred.h diff --git a/src/libcharon/plugins/vici/Makefile.am b/src/libcharon/plugins/vici/Makefile.am index ded5263f0..7c6d144f0 100644 --- a/src/libcharon/plugins/vici/Makefile.am +++ b/src/libcharon/plugins/vici/Makefile.am @@ -21,6 +21,7 @@ libstrongswan_vici_la_SOURCES = \ vici_query.h vici_query.c \ vici_control.h vici_control.c \ vici_config.h vici_config.c \ + vici_cred.h vici_cred.c \ vici_plugin.h vici_plugin.c libstrongswan_vici_la_LDFLAGS = -module -avoid-version diff --git a/src/libcharon/plugins/vici/vici_cred.c b/src/libcharon/plugins/vici/vici_cred.c new file mode 100644 index 000000000..da5e955a1 --- /dev/null +++ b/src/libcharon/plugins/vici/vici_cred.c @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2014 Martin Willi + * Copyright (C) 2014 revosec 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 . + * + * 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 "vici_cred.h" +#include "vici_builder.h" + +#include +#include +#include +#include + +typedef struct private_vici_cred_t private_vici_cred_t; + +/** + * Private data of an vici_cred_t object. + */ +struct private_vici_cred_t { + + /** + * Public vici_cred_t interface. + */ + vici_cred_t public; + + /** + * Dispatcher + */ + vici_dispatcher_t *dispatcher; + + /** + * credentials + */ + mem_cred_t *creds; +}; + +CALLBACK(clear_creds, vici_message_t*, + private_vici_cred_t *this, char *name, u_int id, vici_message_t *message) +{ + vici_builder_t *builder; + + this->creds->clear(this->creds); + + builder = vici_builder_create(); + return builder->finalize(builder); +} + +static void manage_command(private_vici_cred_t *this, + char *name, vici_command_cb_t cb, bool reg) +{ + this->dispatcher->manage_command(this->dispatcher, name, + reg ? cb : NULL, this); +} + +/** + * (Un-)register dispatcher functions + */ +static void manage_commands(private_vici_cred_t *this, bool reg) +{ + manage_command(this, "clear-creds", clear_creds, reg); +} + +METHOD(vici_cred_t, destroy, void, + private_vici_cred_t *this) +{ + manage_commands(this, FALSE); + + lib->credmgr->remove_set(lib->credmgr, &this->creds->set); + this->creds->destroy(this->creds); + free(this); +} + +/** + * See header + */ +vici_cred_t *vici_cred_create(vici_dispatcher_t *dispatcher) +{ + private_vici_cred_t *this; + + INIT(this, + .public = { + .destroy = _destroy, + }, + .dispatcher = dispatcher, + .creds = mem_cred_create(), + ); + + lib->credmgr->add_set(lib->credmgr, &this->creds->set); + + manage_commands(this, TRUE); + + return &this->public; +} diff --git a/src/libcharon/plugins/vici/vici_cred.h b/src/libcharon/plugins/vici/vici_cred.h new file mode 100644 index 000000000..e109a27da --- /dev/null +++ b/src/libcharon/plugins/vici/vici_cred.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2014 Martin Willi + * Copyright (C) 2014 revosec 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 . + * + * 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. + */ + +/** + * @defgroup vici_cred vici_cred + * @{ @ingroup vici + */ + +#ifndef VICI_CRED_H_ +#define VICI_CRED_H_ + +#include "vici_dispatcher.h" + +typedef struct vici_cred_t vici_cred_t; + +/** + * In-memory credential backend, managed by VICI. + */ +struct vici_cred_t { + + /** + * Destroy a vici_cred_t. + */ + void (*destroy)(vici_cred_t *this); +}; + +/** + * Create a vici_cred instance. + * + * @param dispatcher dispatcher to receive requests from + * @return credential backend + */ +vici_cred_t *vici_cred_create(vici_dispatcher_t *dispatcher); + +#endif /** VICI_CRED_H_ @}*/ diff --git a/src/libcharon/plugins/vici/vici_plugin.c b/src/libcharon/plugins/vici/vici_plugin.c index c447f2136..30e676c36 100644 --- a/src/libcharon/plugins/vici/vici_plugin.c +++ b/src/libcharon/plugins/vici/vici_plugin.c @@ -17,6 +17,7 @@ #include "vici_dispatcher.h" #include "vici_query.h" #include "vici_control.h" +#include "vici_cred.h" #include "vici_config.h" #include @@ -49,6 +50,11 @@ struct private_vici_plugin_t { */ vici_control_t *control; + /** + * Credential backend + */ + vici_cred_t *cred; + /** * Configuration backend */ @@ -78,6 +84,7 @@ static bool register_vici(private_vici_plugin_t *this, { this->query = vici_query_create(this->dispatcher); this->control = vici_control_create(this->dispatcher); + this->cred = vici_cred_create(this->dispatcher); this->config = vici_config_create(this->dispatcher); charon->backends->add_backend(charon->backends, @@ -92,6 +99,7 @@ static bool register_vici(private_vici_plugin_t *this, &this->config->backend); this->config->destroy(this->config); + this->cred->destroy(this->cred); this->control->destroy(this->control); this->query->destroy(this->query); this->dispatcher->destroy(this->dispatcher); From c12edb2a275edb975ad4eb6760fd6166cd3e3642 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Wed, 19 Feb 2014 10:51:09 +0100 Subject: [PATCH 36/72] vici: Support loading of different certificate types --- src/libcharon/plugins/vici/vici_cred.c | 93 ++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/src/libcharon/plugins/vici/vici_cred.c b/src/libcharon/plugins/vici/vici_cred.c index da5e955a1..95446e5f4 100644 --- a/src/libcharon/plugins/vici/vici_cred.c +++ b/src/libcharon/plugins/vici/vici_cred.c @@ -44,6 +44,98 @@ struct private_vici_cred_t { mem_cred_t *creds; }; +/** + * Create a (error) reply message + */ +static vici_message_t* create_reply(char *fmt, ...) +{ + vici_builder_t *builder; + va_list args; + + builder = vici_builder_create(); + builder->add_kv(builder, "success", fmt ? "no" : "yes"); + if (fmt) + { + va_start(args, fmt); + builder->vadd_kv(builder, "errmsg", fmt, args); + va_end(args); + } + return builder->finalize(builder); +} + +CALLBACK(load_cert, vici_message_t*, + private_vici_cred_t *this, char *name, u_int id, vici_message_t *message) +{ + certificate_type_t type; + x509_flag_t required_flags = 0, additional_flags = 0; + certificate_t *cert; + x509_t *x509; + chunk_t data; + char *str; + + str = message->get_str(message, NULL, "type"); + if (!str) + { + return create_reply("certificate type missing"); + } + if (strcaseeq(str, "x509")) + { + type = CERT_X509; + } + else if (strcaseeq(str, "x509ca")) + { + type = CERT_X509; + required_flags = X509_CA; + } + else if (strcaseeq(str, "x509aa")) + { + type = CERT_X509; + additional_flags = X509_AA; + } + else if (strcaseeq(str, "x509crl")) + { + type = CERT_X509_CRL; + } + else if (strcaseeq(str, "x509ac")) + { + type = CERT_X509_AC; + } + else + { + return create_reply("invalid certificate type: %s", str); + } + data = message->get_value(message, chunk_empty, "data"); + if (!data.len) + { + return create_reply("certificate data missing"); + } + cert = lib->creds->create(lib->creds, CRED_CERTIFICATE, type, + BUILD_BLOB_PEM, data, + BUILD_X509_FLAG, additional_flags, + BUILD_END); + if (!cert) + { + return create_reply("parsing %N certificate failed", + certificate_type_names, type); + } + if (cert->get_type(cert) == CERT_X509) + { + x509 = (x509_t*)cert; + + if ((required_flags & x509->get_flags(x509)) != required_flags) + { + cert->destroy(cert); + return create_reply("certificate misses required flag, rejected"); + } + } + + DBG1(DBG_CFG, "loaded certificate '%Y'", cert->get_subject(cert)); + + this->creds->add_cert(this->creds, TRUE, cert); + + return create_reply(NULL); +} + CALLBACK(clear_creds, vici_message_t*, private_vici_cred_t *this, char *name, u_int id, vici_message_t *message) { @@ -68,6 +160,7 @@ static void manage_command(private_vici_cred_t *this, static void manage_commands(private_vici_cred_t *this, bool reg) { manage_command(this, "clear-creds", clear_creds, reg); + manage_command(this, "load-cert", load_cert, reg); } METHOD(vici_cred_t, destroy, void, From 559ef7de4801f5761c9fee5237d78e35fd76ee72 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Wed, 19 Feb 2014 13:08:10 +0100 Subject: [PATCH 37/72] vici: Add a load-key command to load private keys --- src/libcharon/plugins/vici/vici_cred.c | 50 ++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/src/libcharon/plugins/vici/vici_cred.c b/src/libcharon/plugins/vici/vici_cred.c index 95446e5f4..dc5887566 100644 --- a/src/libcharon/plugins/vici/vici_cred.c +++ b/src/libcharon/plugins/vici/vici_cred.c @@ -136,6 +136,55 @@ CALLBACK(load_cert, vici_message_t*, return create_reply(NULL); } +CALLBACK(load_key, vici_message_t*, + private_vici_cred_t *this, char *name, u_int id, vici_message_t *message) +{ + key_type_t type; + private_key_t *key; + chunk_t data; + char *str; + + str = message->get_str(message, NULL, "type"); + if (!str) + { + return create_reply("key type missing"); + } + if (strcaseeq(str, "any")) + { + type = KEY_ANY; + } + else if (strcaseeq(str, "rsa")) + { + type = KEY_RSA; + } + else if (strcaseeq(str, "ecdsa")) + { + type = KEY_ECDSA; + } + else + { + return create_reply("invalid key type: %s", str); + } + data = message->get_value(message, chunk_empty, "data"); + if (!data.len) + { + return create_reply("key data missing"); + } + key = lib->creds->create(lib->creds, CRED_PRIVATE_KEY, type, + BUILD_BLOB_PEM, data, BUILD_END); + if (!key) + { + return create_reply("parsing %N private key failed", + key_type_names, type); + } + + DBG1(DBG_CFG, "loaded %N private key", key_type_names, type); + + this->creds->add_key(this->creds, key); + + return create_reply(NULL); +} + CALLBACK(clear_creds, vici_message_t*, private_vici_cred_t *this, char *name, u_int id, vici_message_t *message) { @@ -161,6 +210,7 @@ static void manage_commands(private_vici_cred_t *this, bool reg) { manage_command(this, "clear-creds", clear_creds, reg); manage_command(this, "load-cert", load_cert, reg); + manage_command(this, "load-key", load_key, reg); } METHOD(vici_cred_t, destroy, void, From 6efa792d22cebab24dba0b61181392bb7ef88609 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Wed, 19 Feb 2014 15:08:11 +0100 Subject: [PATCH 38/72] vici: Add a load-shared command to load shared IKE and EAP secrets --- src/libcharon/plugins/vici/vici_cred.c | 68 ++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/src/libcharon/plugins/vici/vici_cred.c b/src/libcharon/plugins/vici/vici_cred.c index dc5887566..3557cf879 100644 --- a/src/libcharon/plugins/vici/vici_cred.c +++ b/src/libcharon/plugins/vici/vici_cred.c @@ -185,12 +185,79 @@ CALLBACK(load_key, vici_message_t*, return create_reply(NULL); } +CALLBACK(shared_owners, bool, + linked_list_t *owners, vici_message_t *message, char *name, chunk_t value) +{ + if (streq(name, "owners")) + { + char buf[256]; + + if (!vici_stringify(value, buf, sizeof(buf))) + { + return FALSE; + } + owners->insert_last(owners, identification_create_from_string(buf)); + } + return TRUE; +} + +CALLBACK(load_shared, vici_message_t*, + private_vici_cred_t *this, char *name, u_int id, vici_message_t *message) +{ + shared_key_type_t type; + linked_list_t *owners; + chunk_t data; + char *str; + + str = message->get_str(message, NULL, "type"); + if (!str) + { + return create_reply("shared key type missing"); + } + if (strcaseeq(str, "ike")) + { + type = SHARED_IKE; + } + else if (strcaseeq(str, "eap")) + { + type = SHARED_EAP; + } + else + { + return create_reply("invalid shared key type: %s", str); + } + data = message->get_value(message, chunk_empty, "data"); + if (!data.len) + { + return create_reply("shared key data missing"); + } + + owners = linked_list_create(); + if (!message->parse(message, NULL, NULL, NULL, shared_owners, owners)) + { + owners->destroy_offset(owners, offsetof(identification_t, destroy)); + return create_reply("parsing shared key owners failed"); + } + if (owners->get_count(owners) == 0) + { + owners->insert_last(owners, identification_create_from_string("%any")); + } + + DBG1(DBG_CFG, "loaded %N shared key", shared_key_type_names, type); + + this->creds->add_shared_list(this->creds, + shared_key_create(type, chunk_clone(data)), owners); + + return create_reply(NULL); +} + CALLBACK(clear_creds, vici_message_t*, private_vici_cred_t *this, char *name, u_int id, vici_message_t *message) { vici_builder_t *builder; this->creds->clear(this->creds); + lib->credmgr->flush_cache(lib->credmgr, CERT_ANY); builder = vici_builder_create(); return builder->finalize(builder); @@ -211,6 +278,7 @@ static void manage_commands(private_vici_cred_t *this, bool reg) manage_command(this, "clear-creds", clear_creds, reg); manage_command(this, "load-cert", load_cert, reg); manage_command(this, "load-key", load_key, reg); + manage_command(this, "load-shared", load_shared, reg); } METHOD(vici_cred_t, destroy, void, From e6e975ff9d0938b86f71e234372f58a0da002906 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Wed, 19 Feb 2014 15:25:10 +0100 Subject: [PATCH 39/72] vici: Support missing groups option in auth config --- src/libcharon/plugins/vici/vici_config.c | 25 +++++++++++++++++++++++- src/libcharon/plugins/vici/vici_query.c | 12 ++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/libcharon/plugins/vici/vici_config.c b/src/libcharon/plugins/vici/vici_config.c index 62170a91f..b08d1b002 100644 --- a/src/libcharon/plugins/vici/vici_config.c +++ b/src/libcharon/plugins/vici/vici_config.c @@ -243,6 +243,9 @@ static void log_auth(auth_cfg_t *auth) case AUTH_RULE_XAUTH_IDENTITY: DBG2(DBG_CFG, " xauth_id = %Y", v.id); break; + case AUTH_RULE_GROUP: + DBG2(DBG_CFG, " group = %Y", v.id); + break; default: break; } @@ -949,6 +952,15 @@ CALLBACK(parse_xauth_id, bool, return parse_id(cfg, AUTH_RULE_XAUTH_IDENTITY, v); } +/** + * Parse group membership + */ +CALLBACK(parse_group, bool, + auth_cfg_t *cfg, chunk_t v) +{ + return parse_id(cfg, AUTH_RULE_GROUP, v); +} + /** * Parse revocation status */ @@ -1129,6 +1141,17 @@ CALLBACK(child_kv, bool, &child->request->reply); } +CALLBACK(auth_li, bool, + auth_data_t *auth, vici_message_t *message, char *name, chunk_t value) +{ + parse_rule_t rules[] = { + { "groups", parse_group, auth->cfg }, + }; + + return parse_rules(rules, countof(rules), name, value, + &auth->request->reply); +} + CALLBACK(auth_kv, bool, auth_data_t *auth, vici_message_t *message, char *name, chunk_t value) { @@ -1272,7 +1295,7 @@ CALLBACK(peer_sn, bool, .cfg = auth_cfg_create(), }; - if (!message->parse(message, ctx, NULL, auth_kv, NULL, &auth)) + if (!message->parse(message, ctx, NULL, auth_kv, auth_li, &auth)) { auth.cfg->destroy(auth.cfg); return FALSE; diff --git a/src/libcharon/plugins/vici/vici_query.c b/src/libcharon/plugins/vici/vici_query.c index 6656abe38..59037b622 100644 --- a/src/libcharon/plugins/vici/vici_query.c +++ b/src/libcharon/plugins/vici/vici_query.c @@ -539,6 +539,18 @@ static void build_auth_cfgs(peer_cfg_t *peer_cfg, bool local, vici_builder_t *b) } rules->destroy(rules); + b->begin_list(b, "groups"); + rules = auth->create_enumerator(auth); + while (rules->enumerate(rules, &rule, &v)) + { + if (rule == AUTH_RULE_GROUP) + { + b->add_li(b, "%Y", v.id); + } + } + rules->destroy(rules); + b->end_list(b); + b->end_section(b); } enumerator->destroy(enumerator); From b57739f72112d468b664c84f6af6d1f499a61151 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Wed, 19 Feb 2014 15:45:24 +0100 Subject: [PATCH 40/72] vici: Support pinning end entity and CA certificates to connections --- src/libcharon/plugins/vici/vici_config.c | 37 ++++++++++++++++++++++++ src/libcharon/plugins/vici/vici_query.c | 25 ++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/src/libcharon/plugins/vici/vici_config.c b/src/libcharon/plugins/vici/vici_config.c index b08d1b002..6f24378e7 100644 --- a/src/libcharon/plugins/vici/vici_config.c +++ b/src/libcharon/plugins/vici/vici_config.c @@ -961,6 +961,41 @@ CALLBACK(parse_group, bool, return parse_id(cfg, AUTH_RULE_GROUP, v); } +/** + * Parse a certificate; add as auth rule to config + */ +static bool parse_cert(auth_cfg_t *cfg, auth_rule_t rule, chunk_t v) +{ + certificate_t *cert; + + cert = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509, + BUILD_BLOB_PEM, v, BUILD_END); + if (cert) + { + cfg->add(cfg, rule, cert); + return TRUE; + } + return FALSE; +} + +/** + * Parse subject certificates + */ +CALLBACK(parse_certs, bool, + auth_cfg_t *cfg, chunk_t v) +{ + return parse_cert(cfg, AUTH_RULE_SUBJECT_CERT, v); +} + +/** + * Parse CA certificates + */ +CALLBACK(parse_cacerts, bool, + auth_cfg_t *cfg, chunk_t v) +{ + return parse_cert(cfg, AUTH_RULE_CA_CERT, v); +} + /** * Parse revocation status */ @@ -1146,6 +1181,8 @@ CALLBACK(auth_li, bool, { parse_rule_t rules[] = { { "groups", parse_group, auth->cfg }, + { "certs", parse_certs, auth->cfg }, + { "cacerts", parse_cacerts, auth->cfg }, }; return parse_rules(rules, countof(rules), name, value, diff --git a/src/libcharon/plugins/vici/vici_query.c b/src/libcharon/plugins/vici/vici_query.c index 59037b622..aff937e7d 100644 --- a/src/libcharon/plugins/vici/vici_query.c +++ b/src/libcharon/plugins/vici/vici_query.c @@ -493,6 +493,7 @@ static void build_auth_cfgs(peer_cfg_t *peer_cfg, bool local, vici_builder_t *b) union { uintptr_t u; identification_t *id; + certificate_t *cert; char *str; } v; @@ -551,6 +552,30 @@ static void build_auth_cfgs(peer_cfg_t *peer_cfg, bool local, vici_builder_t *b) rules->destroy(rules); b->end_list(b); + b->begin_list(b, "certs"); + rules = auth->create_enumerator(auth); + while (rules->enumerate(rules, &rule, &v)) + { + if (rule == AUTH_RULE_SUBJECT_CERT) + { + b->add_li(b, "%Y", v.cert->get_subject(v.cert)); + } + } + rules->destroy(rules); + b->end_list(b); + + b->begin_list(b, "cacerts"); + rules = auth->create_enumerator(auth); + while (rules->enumerate(rules, &rule, &v)) + { + if (rule == AUTH_RULE_CA_CERT) + { + b->add_li(b, "%Y", v.cert->get_subject(v.cert)); + } + } + rules->destroy(rules); + b->end_list(b); + b->end_section(b); } enumerator->destroy(enumerator); From 101dba01ce7cbbb6959269c99871382e400fd0bd Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Mon, 24 Feb 2014 17:21:48 +0100 Subject: [PATCH 41/72] vici: Add a list-certs command to query different certificate types --- src/libcharon/plugins/vici/vici_query.c | 102 ++++++++++++++++++++++++ 1 file changed, 102 insertions(+) diff --git a/src/libcharon/plugins/vici/vici_query.c b/src/libcharon/plugins/vici/vici_query.c index aff937e7d..c03956133 100644 --- a/src/libcharon/plugins/vici/vici_query.c +++ b/src/libcharon/plugins/vici/vici_query.c @@ -684,6 +684,106 @@ CALLBACK(list_conns, vici_message_t*, return b->finalize(b); } +/** + * Do we have a private key for given certificate + */ +static bool has_privkey(private_vici_query_t *this, certificate_t *cert) +{ + private_key_t *private; + public_key_t *public; + identification_t *keyid; + chunk_t chunk; + bool found = FALSE; + + public = cert->get_public_key(cert); + if (public) + { + if (public->get_fingerprint(public, KEYID_PUBKEY_SHA1, &chunk)) + { + keyid = identification_create_from_encoding(ID_KEY_ID, chunk); + private = lib->credmgr->get_private(lib->credmgr, + public->get_type(public), keyid, NULL); + if (private) + { + found = TRUE; + private->destroy(private); + } + keyid->destroy(keyid); + } + public->destroy(public); + } + return found; +} + +CALLBACK(list_certs, vici_message_t*, + private_vici_query_t *this, char *name, u_int id, vici_message_t *request) +{ + enumerator_t *enumerator, *added; + linked_list_t *list; + certificate_t *cert, *current; + chunk_t encoding; + identification_t *subject = NULL; + certificate_type_t type; + vici_builder_t *b; + bool found; + char *str; + + str = request->get_str(request, "ANY", "type"); + type = enum_from_name(certificate_type_names, str); + if (type == -1) + { + b = vici_builder_create(); + return b->finalize(b); + } + str = request->get_str(request, NULL, "subject"); + if (str) + { + subject = identification_create_from_string(str); + } + + list = linked_list_create(); + enumerator = lib->credmgr->create_cert_enumerator(lib->credmgr, + type, KEY_ANY, subject, FALSE); + while (enumerator->enumerate(enumerator, &cert)) + { + found = FALSE; + added = list->create_enumerator(list); + while (added->enumerate(added, ¤t)) + { + if (current->equals(current, cert)) + { + found = TRUE; + break; + } + } + added->destroy(added); + + if (!found && cert->get_encoding(cert, CERT_ASN1_DER, &encoding)) + { + b = vici_builder_create(); + b->add_kv(b, "type", "%N", + certificate_type_names, cert->get_type(cert)); + if (has_privkey(this, cert)) + { + b->add_kv(b, "has_privkey", "yes"); + } + b->add(b, VICI_KEY_VALUE, "data", encoding); + free(encoding.ptr); + + this->dispatcher->raise_event(this->dispatcher, "list-cert", id, + b->finalize(b)); + list->insert_last(list, cert->get_ref(cert)); + } + } + enumerator->destroy(enumerator); + + list->destroy_offset(list, offsetof(certificate_t, destroy)); + DESTROY_IF(subject); + + b = vici_builder_create(); + return b->finalize(b); +} + CALLBACK(version, vici_message_t*, private_vici_query_t *this, char *name, u_int id, vici_message_t *request) { @@ -720,9 +820,11 @@ static void manage_commands(private_vici_query_t *this, bool reg) this->dispatcher->manage_event(this->dispatcher, "list-sa", reg); this->dispatcher->manage_event(this->dispatcher, "list-policy", reg); this->dispatcher->manage_event(this->dispatcher, "list-conn", reg); + this->dispatcher->manage_event(this->dispatcher, "list-cert", reg); manage_command(this, "list-sas", list_sas, reg); manage_command(this, "list-policies", list_policies, reg); manage_command(this, "list-conns", list_conns, reg); + manage_command(this, "list-certs", list_certs, reg); manage_command(this, "version", version, reg); } From 2676ffdb9fe3ccfd5befe244643e693bdc590775 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Thu, 6 Mar 2014 10:27:11 +0100 Subject: [PATCH 42/72] vici: Be less verbose about client connections Instead, log the explicit commands at a higher level. --- src/libcharon/plugins/vici/vici_control.c | 26 ++++++++++++++++++++ src/libcharon/plugins/vici/vici_dispatcher.c | 10 ++++---- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/src/libcharon/plugins/vici/vici_control.c b/src/libcharon/plugins/vici/vici_control.c index f74f1d8c1..c4a74000b 100644 --- a/src/libcharon/plugins/vici/vici_control.c +++ b/src/libcharon/plugins/vici/vici_control.c @@ -176,6 +176,9 @@ CALLBACK(initiate, vici_message_t*, { return send_reply(this, "missing configuration name"); } + + DBG1(DBG_CFG, "vici initiate '%s'", child); + child_cfg = find_child_cfg(child, &peer_cfg); if (!child_cfg) { @@ -222,6 +225,23 @@ CALLBACK(terminate, vici_message_t*, return send_reply(this, "missing terminate selector"); } + if (ike_id) + { + DBG1(DBG_CFG, "vici terminate IKE_SA #%d", ike_id); + } + if (child_id) + { + DBG1(DBG_CFG, "vici terminate CHILD_SA #%d", child_id); + } + if (ike) + { + DBG1(DBG_CFG, "vici terminate IKE_SA '%s'", ike); + } + if (child) + { + DBG1(DBG_CFG, "vici terminate CHILD_SA '%s'", child); + } + ids = array_create(sizeof(u_int), 0); isas = charon->controller->create_ike_sa_enumerator(charon->controller, TRUE); @@ -359,6 +379,9 @@ CALLBACK(install, vici_message_t*, { return send_reply(this, "missing configuration name"); } + + DBG1(DBG_CFG, "vici install '%s'", child); + child_cfg = find_child_cfg(child, &peer_cfg); if (!child_cfg) { @@ -394,6 +417,9 @@ CALLBACK(uninstall, vici_message_t*, { return send_reply(this, "missing configuration name"); } + + DBG1(DBG_CFG, "vici uninstall '%s'", child); + if (charon->shunts->uninstall(charon->shunts, child)) { return send_reply(this, NULL); diff --git a/src/libcharon/plugins/vici/vici_dispatcher.c b/src/libcharon/plugins/vici/vici_dispatcher.c index 4245898cb..4fd8028a6 100644 --- a/src/libcharon/plugins/vici/vici_dispatcher.c +++ b/src/libcharon/plugins/vici/vici_dispatcher.c @@ -138,7 +138,7 @@ static void register_event(private_vici_dispatcher_t *this, char *name, if (event) { - DBG1(DBG_CFG, "vici client %u registered for: %s", id, name); + DBG2(DBG_CFG, "vici client %u registered for: %s", id, name); send_op(this, id, VICI_EVENT_CONFIRM, NULL, NULL); } else @@ -177,7 +177,7 @@ static void unregister_event(private_vici_dispatcher_t *this, char *name, } this->mutex->unlock(this->mutex); - DBG1(DBG_CFG, "vici client %u unregistered for: %s", id, name); + DBG2(DBG_CFG, "vici client %u unregistered for: %s", id, name); if (found) { @@ -241,7 +241,7 @@ void process_request(private_vici_dispatcher_t *this, char *name, u_int id, .cmd = cmd, ); - DBG1(DBG_CFG, "vici client %u requests: %s", id, name); + DBG2(DBG_CFG, "vici client %u requests: %s", id, name); thread_cleanup_push(release_command, release); @@ -330,7 +330,7 @@ CALLBACK(inbound, void, CALLBACK(connect_, void, private_vici_dispatcher_t *this, u_int id) { - DBG1(DBG_CFG, "vici client %u connected", id); + DBG2(DBG_CFG, "vici client %u connected", id); } CALLBACK(disconnect, void, @@ -358,7 +358,7 @@ CALLBACK(disconnect, void, events->destroy(events); this->mutex->unlock(this->mutex); - DBG1(DBG_CFG, "vici client %u disconnected", id); + DBG2(DBG_CFG, "vici client %u disconnected", id); } METHOD(vici_dispatcher_t, manage_command, void, From 96071fdb5567e1896b24d9fa75ec06c97cddf8dd Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Thu, 6 Mar 2014 10:44:55 +0100 Subject: [PATCH 43/72] vici: Add a generic log event to raise events for log messages --- src/libcharon/plugins/vici/Makefile.am | 1 + src/libcharon/plugins/vici/vici_logger.c | 130 +++++++++++++++++++++++ src/libcharon/plugins/vici/vici_logger.h | 54 ++++++++++ src/libcharon/plugins/vici/vici_plugin.c | 10 ++ 4 files changed, 195 insertions(+) create mode 100644 src/libcharon/plugins/vici/vici_logger.c create mode 100644 src/libcharon/plugins/vici/vici_logger.h diff --git a/src/libcharon/plugins/vici/Makefile.am b/src/libcharon/plugins/vici/Makefile.am index 7c6d144f0..370f8c58b 100644 --- a/src/libcharon/plugins/vici/Makefile.am +++ b/src/libcharon/plugins/vici/Makefile.am @@ -22,6 +22,7 @@ libstrongswan_vici_la_SOURCES = \ vici_control.h vici_control.c \ vici_config.h vici_config.c \ vici_cred.h vici_cred.c \ + vici_logger.h vici_logger.c \ vici_plugin.h vici_plugin.c libstrongswan_vici_la_LDFLAGS = -module -avoid-version diff --git a/src/libcharon/plugins/vici/vici_logger.c b/src/libcharon/plugins/vici/vici_logger.c new file mode 100644 index 000000000..cffd65bad --- /dev/null +++ b/src/libcharon/plugins/vici/vici_logger.c @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2014 Martin Willi + * Copyright (C) 2014 revosec 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 . + * + * 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 "vici_logger.h" +#include "vici_builder.h" + +#include +#include + +typedef struct private_vici_logger_t private_vici_logger_t; + +/** + * Private data of an vici_logger_t object. + */ +struct private_vici_logger_t { + + /** + * Public vici_logger_t interface. + */ + vici_logger_t public; + + /** + * Dispatcher + */ + vici_dispatcher_t *dispatcher; + + /** + * Recursiveness avoidance counter + */ + int recursive; + + /** + * Mutex to synchronize logging + */ + mutex_t *mutex; +}; + +METHOD(logger_t, log_, void, + private_vici_logger_t *this, debug_t group, level_t level, int thread, + ike_sa_t* ike_sa, const char *msg) +{ + this->mutex->lock(this->mutex); + + /* avoid recursive invocations by the vici subsystem */ + if (this->recursive++ == 0) + { + vici_message_t *message; + vici_builder_t *builder; + + builder = vici_builder_create(); + builder->add_kv(builder, "group", "%N", debug_names, group); + builder->add_kv(builder, "level", "%d", level); + builder->add_kv(builder, "thread", "%d", thread); + if (ike_sa) + { + builder->add_kv(builder, "ikesa-name", "%s", + ike_sa->get_name(ike_sa)); + builder->add_kv(builder, "ikesa-uniqueid", "%u", + ike_sa->get_unique_id(ike_sa)); + } + builder->add_kv(builder, "msg", "%s", msg); + + message = builder->finalize(builder); + if (message) + { + this->dispatcher->raise_event(this->dispatcher, "log", 0, message); + } + } + this->recursive--; + + this->mutex->unlock(this->mutex); +} + +METHOD(logger_t, get_level, level_t, + private_vici_logger_t *this, debug_t group) +{ + return LEVEL_CTRL; +} + +/** + * (Un-)register dispatcher functions/events + */ +static void manage_commands(private_vici_logger_t *this, bool reg) +{ + this->dispatcher->manage_event(this->dispatcher, "log", reg); +} + +METHOD(vici_logger_t, destroy, void, + private_vici_logger_t *this) +{ + manage_commands(this, FALSE); + this->mutex->destroy(this->mutex); + free(this); +} + +/** + * See header + */ +vici_logger_t *vici_logger_create(vici_dispatcher_t *dispatcher) +{ + private_vici_logger_t *this; + + INIT(this, + .public = { + .logger = { + .log = _log_, + .get_level = _get_level, + }, + .destroy = _destroy, + }, + .dispatcher = dispatcher, + .mutex = mutex_create(MUTEX_TYPE_RECURSIVE), + ); + + manage_commands(this, TRUE); + + return &this->public; +} diff --git a/src/libcharon/plugins/vici/vici_logger.h b/src/libcharon/plugins/vici/vici_logger.h new file mode 100644 index 000000000..7be1d60d4 --- /dev/null +++ b/src/libcharon/plugins/vici/vici_logger.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2014 Martin Willi + * Copyright (C) 2014 revosec 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 . + * + * 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. + */ + +/** + * @defgroup vici_logger vici_logger + * @{ @ingroup vici + */ + +#ifndef VICI_LOGGER_H_ +#define VICI_LOGGER_H_ + +#include "vici_dispatcher.h" + +#include + +typedef struct vici_logger_t vici_logger_t; + +/** + * Generic debugging logger over vici. + */ +struct vici_logger_t { + + /** + * Implements logger interface. + */ + logger_t logger; + + /** + * Destroy a vici_logger_t. + */ + void (*destroy)(vici_logger_t *this); +}; + +/** + * Create a vici_logger instance. + * + * @param dispatcher dispatcher to receive requests from + * @return loggerential backend + */ +vici_logger_t *vici_logger_create(vici_dispatcher_t *dispatcher); + +#endif /** VICI_LOGGER_H_ @}*/ diff --git a/src/libcharon/plugins/vici/vici_plugin.c b/src/libcharon/plugins/vici/vici_plugin.c index 30e676c36..fcd95ed6b 100644 --- a/src/libcharon/plugins/vici/vici_plugin.c +++ b/src/libcharon/plugins/vici/vici_plugin.c @@ -19,6 +19,7 @@ #include "vici_control.h" #include "vici_cred.h" #include "vici_config.h" +#include "vici_logger.h" #include #include @@ -59,6 +60,11 @@ struct private_vici_plugin_t { * Configuration backend */ vici_config_t *config; + + /** + * Generic debug logger + */ + vici_logger_t *logger; }; METHOD(plugin_t, get_name, char*, @@ -86,18 +92,22 @@ static bool register_vici(private_vici_plugin_t *this, this->control = vici_control_create(this->dispatcher); this->cred = vici_cred_create(this->dispatcher); this->config = vici_config_create(this->dispatcher); + this->logger = vici_logger_create(this->dispatcher); charon->backends->add_backend(charon->backends, &this->config->backend); + charon->bus->add_logger(charon->bus, &this->logger->logger); return TRUE; } return FALSE; } else { + charon->bus->remove_logger(charon->bus, &this->logger->logger); charon->backends->remove_backend(charon->backends, &this->config->backend); + this->logger->destroy(this->logger); this->config->destroy(this->config); this->cred->destroy(this->cred); this->control->destroy(this->control); From 7de35b7ff6062b5082b4ea92ef92f87ed56153eb Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Mon, 10 Mar 2014 14:21:50 +0100 Subject: [PATCH 44/72] vici: Perform specified start_action on connection load, undo it on unload --- src/libcharon/plugins/vici/vici_config.c | 187 ++++++++++++++++++++++- 1 file changed, 185 insertions(+), 2 deletions(-) diff --git a/src/libcharon/plugins/vici/vici_config.c b/src/libcharon/plugins/vici/vici_config.c index 6f24378e7..82c691f68 100644 --- a/src/libcharon/plugins/vici/vici_config.c +++ b/src/libcharon/plugins/vici/vici_config.c @@ -20,6 +20,7 @@ #include #include +#include #include #include @@ -338,8 +339,10 @@ typedef struct { char* updown; bool hostaccess; bool ipcomp; + bool route; ipsec_mode_t mode; action_t dpd_action; + action_t start_action; u_int32_t reqid; u_int32_t tfc; mark_t mark_in; @@ -1164,6 +1167,7 @@ CALLBACK(child_kv, bool, { "life_packets", parse_uint64, &child->lft.packets.life }, { "rand_packets", parse_uint64, &child->lft.packets.jitter }, { "dpd_action", parse_action, &child->dpd_action }, + { "start_action", parse_action, &child->start_action }, { "ipcomp", parse_bool, &child->ipcomp }, { "inactivity", parse_time, &child->inactivity }, { "reqid", parse_uint32, &child->reqid }, @@ -1258,6 +1262,7 @@ CALLBACK(children_sn, bool, .remote_ts = linked_list_create(), .mode = MODE_TUNNEL, .dpd_action = ACTION_NONE, + .start_action = ACTION_NONE, }; child_cfg_t *cfg; proposal_t *proposal; @@ -1288,7 +1293,7 @@ CALLBACK(children_sn, bool, log_child_data(&child, name); cfg = child_cfg_create(name, &child.lft, child.updown, - child.hostaccess, child.mode, ACTION_NONE, + child.hostaccess, child.mode, child.start_action, child.dpd_action, ACTION_NONE, child.ipcomp, child.inactivity, child.reqid, &child.mark_in, &child.mark_out, child.tfc); @@ -1352,6 +1357,180 @@ CALLBACK(peer_sn, bool, return FALSE; } +/** + * Find reqid of an existing CHILD_SA + */ +static u_int32_t find_reqid(child_cfg_t *cfg) +{ + enumerator_t *enumerator, *children; + child_sa_t *child_sa; + ike_sa_t *ike_sa; + u_int32_t reqid; + + reqid = charon->traps->find_reqid(charon->traps, cfg); + if (reqid) + { /* already trapped */ + return reqid; + } + + enumerator = charon->controller->create_ike_sa_enumerator( + charon->controller, TRUE); + while (!reqid && enumerator->enumerate(enumerator, &ike_sa)) + { + children = ike_sa->create_child_sa_enumerator(ike_sa); + while (children->enumerate(children, &child_sa)) + { + if (streq(cfg->get_name(cfg), child_sa->get_name(child_sa))) + { + reqid = child_sa->get_reqid(child_sa); + break; + } + } + children->destroy(children); + } + enumerator->destroy(enumerator); + return reqid; +} + +/** + * Perform start actions associated to a child config + */ +static void run_start_action(private_vici_config_t *this, peer_cfg_t *peer_cfg, + child_cfg_t *child_cfg) +{ + switch (child_cfg->get_start_action(child_cfg)) + { + case ACTION_RESTART: + DBG1(DBG_CFG, "initiating '%s'", child_cfg->get_name(child_cfg)); + charon->controller->initiate(charon->controller, + peer_cfg->get_ref(peer_cfg), child_cfg->get_ref(child_cfg), + NULL, NULL, 0); + break; + case ACTION_ROUTE: + DBG1(DBG_CFG, "installing '%s'", child_cfg->get_name(child_cfg)); + switch (child_cfg->get_mode(child_cfg)) + { + case MODE_PASS: + case MODE_DROP: + charon->shunts->install(charon->shunts, child_cfg); + break; + default: + charon->traps->install(charon->traps, peer_cfg, child_cfg, + find_reqid(child_cfg)); + break; + } + break; + default: + break; + } +} + +/** + * Undo start actions associated to a child config + */ +static void clear_start_action(private_vici_config_t *this, + child_cfg_t *child_cfg) +{ + enumerator_t *enumerator, *children; + child_sa_t *child_sa; + ike_sa_t *ike_sa; + u_int32_t reqid = 0, *del; + array_t *reqids = NULL; + char *name; + + name = child_cfg->get_name(child_cfg); + switch (child_cfg->get_start_action(child_cfg)) + { + case ACTION_RESTART: + enumerator = charon->controller->create_ike_sa_enumerator( + charon->controller, TRUE); + while (enumerator->enumerate(enumerator, &ike_sa)) + { + children = ike_sa->create_child_sa_enumerator(ike_sa); + while (children->enumerate(children, &child_sa)) + { + reqid = child_sa->get_reqid(child_sa); + array_insert_create(&reqids, ARRAY_TAIL, &reqid); + } + children->destroy(children); + } + enumerator->destroy(enumerator); + + if (array_count(reqids)) + { + while (array_remove(reqids, ARRAY_HEAD, &del)) + { + DBG1(DBG_CFG, "closing '%s' #%u", name, *del); + charon->controller->terminate_child(charon->controller, + *del, NULL, NULL, 0); + } + array_destroy(reqids); + } + break; + case ACTION_ROUTE: + DBG1(DBG_CFG, "uninstalling '%s'", name); + switch (child_cfg->get_mode(child_cfg)) + { + case MODE_PASS: + case MODE_DROP: + charon->shunts->uninstall(charon->shunts, name); + break; + default: + enumerator = charon->traps->create_enumerator(charon->traps); + while (enumerator->enumerate(enumerator, NULL, &child_sa)) + { + if (streq(name, child_sa->get_name(child_sa))) + { + reqid = child_sa->get_reqid(child_sa); + break; + } + } + enumerator->destroy(enumerator); + if (reqid) + { + charon->traps->uninstall(charon->traps, reqid); + } + break; + } + break; + default: + break; + } +} + +/** + * Run start actions associated to all child configs of a peer config + */ +static void run_start_actions(private_vici_config_t *this, peer_cfg_t *peer_cfg) +{ + enumerator_t *enumerator; + child_cfg_t *child_cfg; + + enumerator = peer_cfg->create_child_cfg_enumerator(peer_cfg); + while (enumerator->enumerate(enumerator, &child_cfg)) + { + run_start_action(this, peer_cfg, child_cfg); + } + enumerator->destroy(enumerator); +} + +/** + * Undo start actions associated to all child configs of a peer config + */ +static void clear_start_actions(private_vici_config_t *this, + peer_cfg_t *peer_cfg) +{ + enumerator_t *enumerator; + child_cfg_t *child_cfg; + + enumerator = peer_cfg->create_child_cfg_enumerator(peer_cfg); + while (enumerator->enumerate(enumerator, &child_cfg)) + { + clear_start_action(this, child_cfg); + } + enumerator->destroy(enumerator); +} + /** * Replace children of a peer config by a new config */ @@ -1365,6 +1544,7 @@ static void replace_children(private_vici_config_t *this, while (enumerator->enumerate(enumerator, &child)) { to->remove_child_cfg(to, enumerator); + clear_start_action(this, child); child->destroy(child); } enumerator->destroy(enumerator); @@ -1374,6 +1554,7 @@ static void replace_children(private_vici_config_t *this, { from->remove_child_cfg(from, enumerator); to->add_child_cfg(to, child); + run_start_action(this, to, child); } enumerator->destroy(enumerator); } @@ -1409,13 +1590,14 @@ static void merge_config(private_vici_config_t *this, peer_cfg_t *peer_cfg) DBG1(DBG_CFG, "replaced vici connection: %s", peer_cfg->get_name(peer_cfg)); this->conns->remove_at(this->conns, enumerator); + clear_start_actions(this, current); current->destroy(current); this->conns->insert_last(this->conns, peer_cfg); + run_start_actions(this, peer_cfg); } merged = TRUE; break; } - } enumerator->destroy(enumerator); @@ -1423,6 +1605,7 @@ static void merge_config(private_vici_config_t *this, peer_cfg_t *peer_cfg) { DBG1(DBG_CFG, "added vici connection: %s", peer_cfg->get_name(peer_cfg)); this->conns->insert_last(this->conns, peer_cfg); + run_start_actions(this, peer_cfg); } this->lock->unlock(this->lock); From e00ce378fa6083a162bad505a35719fc25494573 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Wed, 9 Apr 2014 14:01:45 +0200 Subject: [PATCH 45/72] vici: Use non-blocking first read when receiving message during client on_read() As select() and finally the watcher may signal an FD even if it does not actually have data, we must make a non-block read to avoid hanging in the read callback. --- src/libcharon/plugins/vici/libvici.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/libcharon/plugins/vici/libvici.c b/src/libcharon/plugins/vici/libvici.c index 9b7ccb13e..becd88671 100644 --- a/src/libcharon/plugins/vici/libvici.c +++ b/src/libcharon/plugins/vici/libvici.c @@ -199,11 +199,25 @@ CALLBACK(on_read, bool, { u_int16_t len; u_int8_t op; + ssize_t hlen; - if (!stream->read_all(stream, &len, sizeof(len))) + hlen = stream->read(stream, &len, sizeof(len), FALSE); + if (hlen <= 0) { + if (errno == EWOULDBLOCK) + { + return TRUE; + } return read_error(conn, errno); } + if (hlen < sizeof(len)) + { + if (!stream->read_all(stream, ((void*)&len) + hlen, sizeof(len) - hlen)) + { + return read_error(conn, errno); + } + } + len = ntohs(len); if (len-- < sizeof(op)) { From 0963a9952c945a90a682df4d0ea8ffa82d02ae65 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Thu, 3 Apr 2014 16:41:07 +0200 Subject: [PATCH 46/72] vici: Don't compare unsigned certificate_type_t to -1 --- src/libcharon/plugins/vici/vici_query.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libcharon/plugins/vici/vici_query.c b/src/libcharon/plugins/vici/vici_query.c index c03956133..f89ae01a2 100644 --- a/src/libcharon/plugins/vici/vici_query.c +++ b/src/libcharon/plugins/vici/vici_query.c @@ -723,7 +723,7 @@ CALLBACK(list_certs, vici_message_t*, certificate_t *cert, *current; chunk_t encoding; identification_t *subject = NULL; - certificate_type_t type; + int type; vici_builder_t *b; bool found; char *str; From 93d60c479a343e3451a8f61a043e3a4fe8d0c027 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Thu, 3 Apr 2014 16:41:45 +0200 Subject: [PATCH 47/72] vici: Make unit-tests independent from libcharon and libhydra Fixes monolithic build, as we can't depend on the not yet built libcharon. --- src/libcharon/plugins/vici/Makefile.am | 2 -- src/libcharon/plugins/vici/vici_socket.c | 1 - src/libcharon/plugins/vici/vici_tests.c | 9 +-------- 3 files changed, 1 insertion(+), 11 deletions(-) diff --git a/src/libcharon/plugins/vici/Makefile.am b/src/libcharon/plugins/vici/Makefile.am index 370f8c58b..df241dcab 100644 --- a/src/libcharon/plugins/vici/Makefile.am +++ b/src/libcharon/plugins/vici/Makefile.am @@ -61,7 +61,5 @@ vici_tests_CFLAGS = \ vici_tests_LDFLAGS = @COVERAGE_LDFLAGS@ vici_tests_LDADD = \ - $(top_builddir)/src/libcharon/libcharon.la \ - $(top_builddir)/src/libhydra/libhydra.la \ $(top_builddir)/src/libstrongswan/libstrongswan.la \ $(top_builddir)/src/libstrongswan/tests/libtest.la diff --git a/src/libcharon/plugins/vici/vici_socket.c b/src/libcharon/plugins/vici/vici_socket.c index fa44c7061..5a0fc81ec 100644 --- a/src/libcharon/plugins/vici/vici_socket.c +++ b/src/libcharon/plugins/vici/vici_socket.c @@ -15,7 +15,6 @@ #include "vici_socket.h" -#include #include #include #include diff --git a/src/libcharon/plugins/vici/vici_tests.c b/src/libcharon/plugins/vici/vici_tests.c index ec5afa45d..434aa5e18 100644 --- a/src/libcharon/plugins/vici/vici_tests.c +++ b/src/libcharon/plugins/vici/vici_tests.c @@ -32,17 +32,10 @@ static test_configuration_t tests[] = { static bool test_runner_init(bool init) { - if (init) - { - libhydra_init("vici-tests"); - libcharon_init("vici-tests"); - } - else + if (!init) { lib->processor->set_threads(lib->processor, 0); lib->processor->cancel(lib->processor); - libcharon_deinit(); - libhydra_deinit(); } return TRUE; } From c5205105080f90507a70720e9b46cff5d4bc467d Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Tue, 15 Apr 2014 10:54:47 +0200 Subject: [PATCH 48/72] vici: Use a default hard lifetime of 110% of the soft lifetime --- src/libcharon/plugins/vici/vici_config.c | 37 ++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/libcharon/plugins/vici/vici_config.c b/src/libcharon/plugins/vici/vici_config.c index 82c691f68..9e8b8d94e 100644 --- a/src/libcharon/plugins/vici/vici_config.c +++ b/src/libcharon/plugins/vici/vici_config.c @@ -26,6 +26,11 @@ #include #include +/** + * Magic value for an undefined lifetime + */ +#define LFT_UNDEFINED (~(u_int64_t)0) + typedef struct private_vici_config_t private_vici_config_t; /** @@ -1263,6 +1268,17 @@ CALLBACK(children_sn, bool, .mode = MODE_TUNNEL, .dpd_action = ACTION_NONE, .start_action = ACTION_NONE, + .lft = { + .time = { + .life = LFT_UNDEFINED, + }, + .bytes = { + .life = LFT_UNDEFINED, + }, + .packets = { + .life = LFT_UNDEFINED, + }, + } }; child_cfg_t *cfg; proposal_t *proposal; @@ -1290,6 +1306,20 @@ CALLBACK(children_sn, bool, proposal_create_default(PROTO_ESP)); } + /* if no hard lifetime specified, add one at soft lifetime + 10% */ + if (child.lft.time.life == LFT_UNDEFINED) + { + child.lft.time.life = child.lft.time.rekey * 110 / 100; + } + if (child.lft.bytes.life == LFT_UNDEFINED) + { + child.lft.bytes.life = child.lft.bytes.rekey * 110 / 100; + } + if (child.lft.packets.life == LFT_UNDEFINED) + { + child.lft.packets.life = child.lft.packets.rekey * 110 / 100; + } + log_child_data(&child, name); cfg = child_cfg_create(name, &child.lft, child.updown, @@ -1632,6 +1662,7 @@ CALLBACK(config_sn, bool, .fragmentation = FRAGMENTATION_NO, .unique = UNIQUE_NO, .keyingtries = 1, + .over_time = LFT_UNDEFINED, }; peer_cfg_t *peer_cfg; ike_cfg_t *ike_cfg; @@ -1672,6 +1703,12 @@ CALLBACK(config_sn, bool, peer.remote_addrs = strdup("%any"); } + if (peer.over_time == LFT_UNDEFINED) + { + /* default over_time to 10% of rekey/reauth time if not given */ + peer.over_time = max(peer.rekey_time, peer.reauth_time) / 10; + } + log_peer_data(&peer); ike_cfg = ike_cfg_create(peer.version, peer.send_certreq, peer.encap, From e651afe67b33e01f5f42e9163b5f20f3862897b5 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Tue, 15 Apr 2014 11:24:45 +0200 Subject: [PATCH 49/72] vici: Use a default rand_time of the difference between hard and soft lifetimes --- src/libcharon/plugins/vici/vici_config.c | 26 ++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/libcharon/plugins/vici/vici_config.c b/src/libcharon/plugins/vici/vici_config.c index 9e8b8d94e..2d2f531f7 100644 --- a/src/libcharon/plugins/vici/vici_config.c +++ b/src/libcharon/plugins/vici/vici_config.c @@ -1271,12 +1271,15 @@ CALLBACK(children_sn, bool, .lft = { .time = { .life = LFT_UNDEFINED, + .jitter = LFT_UNDEFINED, }, .bytes = { .life = LFT_UNDEFINED, + .jitter = LFT_UNDEFINED, }, .packets = { .life = LFT_UNDEFINED, + .jitter = LFT_UNDEFINED, }, } }; @@ -1319,6 +1322,22 @@ CALLBACK(children_sn, bool, { child.lft.packets.life = child.lft.packets.rekey * 110 / 100; } + /* if no rand time defined, use difference of hard and soft */ + if (child.lft.time.jitter == LFT_UNDEFINED) + { + child.lft.time.jitter = child.lft.time.life - + min(child.lft.time.life, child.lft.time.rekey); + } + if (child.lft.bytes.jitter == LFT_UNDEFINED) + { + child.lft.bytes.jitter = child.lft.bytes.life - + min(child.lft.bytes.life, child.lft.bytes.rekey); + } + if (child.lft.packets.jitter == LFT_UNDEFINED) + { + child.lft.packets.jitter = child.lft.packets.life - + min(child.lft.packets.life, child.lft.packets.rekey); + } log_child_data(&child, name); @@ -1663,6 +1682,7 @@ CALLBACK(config_sn, bool, .unique = UNIQUE_NO, .keyingtries = 1, .over_time = LFT_UNDEFINED, + .rand_time = LFT_UNDEFINED, }; peer_cfg_t *peer_cfg; ike_cfg_t *ike_cfg; @@ -1708,6 +1728,12 @@ CALLBACK(config_sn, bool, /* default over_time to 10% of rekey/reauth time if not given */ peer.over_time = max(peer.rekey_time, peer.reauth_time) / 10; } + if (peer.rand_time == LFT_UNDEFINED) + { + /* default rand_time to over_time if not given */ + peer.rand_time = min(peer.over_time, + max(peer.rekey_time, peer.reauth_time) / 2); + } log_peer_data(&peer); From 3ad9c34c920c5ff8cb89127c9c1ff3f4a2c4b225 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Tue, 15 Apr 2014 13:21:11 +0200 Subject: [PATCH 50/72] vici: Actually add configured virtual IPs to peer config --- src/libcharon/plugins/vici/vici_config.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/libcharon/plugins/vici/vici_config.c b/src/libcharon/plugins/vici/vici_config.c index 2d2f531f7..ee18349a6 100644 --- a/src/libcharon/plugins/vici/vici_config.c +++ b/src/libcharon/plugins/vici/vici_config.c @@ -1689,6 +1689,7 @@ CALLBACK(config_sn, bool, child_cfg_t *child_cfg; auth_cfg_t *auth_cfg; proposal_t *proposal; + host_t *host; DBG2(DBG_CFG, " conn %s:", name); @@ -1768,6 +1769,10 @@ CALLBACK(config_sn, bool, { ike_cfg->add_proposal(ike_cfg, proposal); } + while (peer.vips->remove_first(peer.vips, (void**)&host) == SUCCESS) + { + peer_cfg->add_virtual_ip(peer_cfg, host); + } free_peer_data(&peer); From afb8f492ef92db9d59a05c1cce6cd8eaec3bbc7b Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Tue, 15 Apr 2014 13:29:06 +0200 Subject: [PATCH 51/72] vici: Support referencing external named pools for peer configs --- src/libcharon/plugins/vici/vici_config.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/libcharon/plugins/vici/vici_config.c b/src/libcharon/plugins/vici/vici_config.c index ee18349a6..7940e1382 100644 --- a/src/libcharon/plugins/vici/vici_config.c +++ b/src/libcharon/plugins/vici/vici_config.c @@ -198,6 +198,7 @@ typedef struct { linked_list_t *proposals; linked_list_t *children; linked_list_t *vips; + char *pools; u_int64_t reauth_time; u_int64_t rekey_time; u_int64_t over_time; @@ -331,6 +332,7 @@ static void free_peer_data(peer_data_t *data) data->proposals->destroy_offset(data->proposals, offsetof(proposal_t, destroy)); data->vips->destroy_offset(data->vips, offsetof(host_t, destroy)); + free(data->pools); free(data->local_addrs); free(data->remote_addrs); } @@ -1222,6 +1224,7 @@ CALLBACK(peer_li, bool, { "remote_addrs", parse_stringlist, &peer->remote_addrs }, { "proposals", parse_ike_proposal, peer->proposals }, { "vips", parse_hosts, peer->vips }, + { "pools", parse_stringlist, &peer->pools }, }; return parse_rules(rules, countof(rules), name, value, @@ -1684,12 +1687,14 @@ CALLBACK(config_sn, bool, .over_time = LFT_UNDEFINED, .rand_time = LFT_UNDEFINED, }; + enumerator_t *enumerator; peer_cfg_t *peer_cfg; ike_cfg_t *ike_cfg; child_cfg_t *child_cfg; auth_cfg_t *auth_cfg; proposal_t *proposal; host_t *host; + char *str; DBG2(DBG_CFG, " conn %s:", name); @@ -1773,6 +1778,15 @@ CALLBACK(config_sn, bool, { peer_cfg->add_virtual_ip(peer_cfg, host); } + if (peer.pools) + { + enumerator = enumerator_create_token(peer.pools, ",", " "); + while (enumerator->enumerate(enumerator, &str)) + { + peer_cfg->add_pool(peer_cfg, str); + } + enumerator->destroy(enumerator); + } free_peer_data(&peer); From eacf864c2101a9af36f967b26fc39698b0ae021e Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Wed, 16 Apr 2014 10:55:40 +0200 Subject: [PATCH 52/72] vici: Add an IKE virtual IP and attribute backend --- src/libcharon/plugins/vici/Makefile.am | 1 + src/libcharon/plugins/vici/vici_attribute.c | 713 ++++++++++++++++++++ src/libcharon/plugins/vici/vici_attribute.h | 54 ++ src/libcharon/plugins/vici/vici_plugin.c | 13 + 4 files changed, 781 insertions(+) create mode 100644 src/libcharon/plugins/vici/vici_attribute.c create mode 100644 src/libcharon/plugins/vici/vici_attribute.h diff --git a/src/libcharon/plugins/vici/Makefile.am b/src/libcharon/plugins/vici/Makefile.am index df241dcab..2a99b1391 100644 --- a/src/libcharon/plugins/vici/Makefile.am +++ b/src/libcharon/plugins/vici/Makefile.am @@ -22,6 +22,7 @@ libstrongswan_vici_la_SOURCES = \ vici_control.h vici_control.c \ vici_config.h vici_config.c \ vici_cred.h vici_cred.c \ + vici_attribute.h vici_attribute.c \ vici_logger.h vici_logger.c \ vici_plugin.h vici_plugin.c diff --git a/src/libcharon/plugins/vici/vici_attribute.c b/src/libcharon/plugins/vici/vici_attribute.c new file mode 100644 index 000000000..2178116c9 --- /dev/null +++ b/src/libcharon/plugins/vici/vici_attribute.c @@ -0,0 +1,713 @@ +/* + * Copyright (C) 2014 Martin Willi + * Copyright (C) 2014 revosec 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 . + * + * 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 "vici_attribute.h" +#include "vici_builder.h" + +#include +#include +#include +#include +#include + +typedef struct private_vici_attribute_t private_vici_attribute_t; + +/** + * private data of vici_attribute + */ +struct private_vici_attribute_t { + + /** + * public functions + */ + vici_attribute_t public; + + /** + * vici connection dispatcher + */ + vici_dispatcher_t *dispatcher; + + /** + * Configured pools, as char* => pool_t + */ + hashtable_t *pools; + + /** + * rwlock to lock access to pools + */ + rwlock_t *lock; +}; + +/** + * Single configuration attribute with type + */ +typedef struct { + /** type of attribute */ + configuration_attribute_type_t type; + /** attribute value */ + chunk_t value; +} attribute_t; + +/** + * Clean up an attribute + */ +static void attribute_destroy(attribute_t *attr) +{ + free(attr->value.ptr); + free(attr); +} + +/** + * Pool instances with associated attributes + */ +typedef struct { + /** in-memory virtual IP pool */ + mem_pool_t *vips; + /** configuration attributes, as attribute_t */ + array_t *attrs; +} pool_t; + +/** + * Clean up a pool instance + */ +static void pool_destroy(pool_t *pool) +{ + DESTROY_IF(pool->vips); + array_destroy_function(pool->attrs, (void*)attribute_destroy, NULL); + free(pool); +} + +/** + * Find an existing or not yet existing lease + */ +static host_t *find_addr(private_vici_attribute_t *this, linked_list_t *pools, + identification_t *id, host_t *requested, mem_pool_op_t op) +{ + enumerator_t *enumerator; + host_t *addr = NULL; + pool_t *pool; + char *name; + + enumerator = pools->create_enumerator(pools); + while (enumerator->enumerate(enumerator, &name)) + { + pool = this->pools->get(this->pools, name); + if (pool) + { + addr = pool->vips->acquire_address(pool->vips, id, requested, op); + if (addr) + { + break; + } + } + } + enumerator->destroy(enumerator); + + return addr; +} + +METHOD(attribute_provider_t, acquire_address, host_t*, + private_vici_attribute_t *this, linked_list_t *pools, identification_t *id, + host_t *requested) +{ + host_t *addr; + + this->lock->read_lock(this->lock); + + addr = find_addr(this, pools, id, requested, MEM_POOL_EXISTING); + if (!addr) + { + addr = find_addr(this, pools, id, requested, MEM_POOL_NEW); + if (!addr) + { + addr = find_addr(this, pools, id, requested, MEM_POOL_REASSIGN); + } + } + + this->lock->unlock(this->lock); + + return addr; +} + +METHOD(attribute_provider_t, release_address, bool, + private_vici_attribute_t *this, linked_list_t *pools, host_t *address, + identification_t *id) +{ + enumerator_t *enumerator; + bool found = FALSE; + pool_t *pool; + char *name; + + this->lock->read_lock(this->lock); + + enumerator = pools->create_enumerator(pools); + while (enumerator->enumerate(enumerator, &name)) + { + pool = this->pools->get(this->pools, name); + if (pool) + { + found = pool->vips->release_address(pool->vips, address, id); + if (found) + { + break; + } + } + } + enumerator->destroy(enumerator); + + this->lock->unlock(this->lock); + + return found; +} + +/** + * Filter mapping attribute_t to enumerated type/value arguments + */ +static bool attr_filter(void *data, attribute_t **attr, + configuration_attribute_type_t *type, + void *in, chunk_t *value) +{ + *type = (*attr)->type; + *value = (*attr)->value; + return TRUE; +} + +/** + * Create nested inner enumerator over pool attributes + */ +CALLBACK(create_nested, enumerator_t*, + pool_t *pool, void *this) +{ + return enumerator_create_filter(array_create_enumerator(pool->attrs), + (void*)attr_filter, NULL, NULL); +} + +/** + * Data associated to nested enumerator cleanup + */ +typedef struct { + private_vici_attribute_t *this; + linked_list_t *list; +} nested_data_t; + +/** + * Clean up nested enumerator data + */ +CALLBACK(nested_cleanup, void, + nested_data_t *data) +{ + data->this->lock->unlock(data->this->lock); + data->list->destroy(data->list); + free(data); +} + +/** + * Check if any of vips is from pool + */ +static bool have_vips_from_pool(mem_pool_t *pool, linked_list_t *vips) +{ + enumerator_t *enumerator; + host_t *host; + chunk_t start, end, current; + u_int32_t size; + bool found = FALSE; + + host = pool->get_base(pool); + start = host->get_address(host); + + if (start.len >= sizeof(size)) + { + end = chunk_clone(start); + + /* mem_pool is currenty limited to 2^31 addresses, so 32-bit + * calculations should be sufficient. */ + size = untoh32(start.ptr + start.len - sizeof(size)); + htoun32(end.ptr + end.len - sizeof(size), size + pool->get_size(pool)); + + enumerator = vips->create_enumerator(vips); + while (enumerator->enumerate(enumerator, &host)) + { + current = host->get_address(host); + if (chunk_compare(current, start) >= 0 && + chunk_compare(current, end) < 0) + { + found = TRUE; + break; + } + } + enumerator->destroy(enumerator); + + free(end.ptr); + } + return found; +} + +METHOD(attribute_provider_t, create_attribute_enumerator, enumerator_t*, + private_vici_attribute_t *this, linked_list_t *pools, + identification_t *id, linked_list_t *vips) +{ + enumerator_t *enumerator; + nested_data_t *data; + pool_t *pool; + char *name; + + INIT(data, + .this = this, + .list = linked_list_create(), + ); + + this->lock->read_lock(this->lock); + + enumerator = pools->create_enumerator(pools); + while (enumerator->enumerate(enumerator, &name)) + { + pool = this->pools->get(this->pools, name); + if (pool && have_vips_from_pool(pool->vips, vips)) + { + data->list->insert_last(data->list, pool); + } + } + enumerator->destroy(enumerator); + + return enumerator_create_nested(data->list->create_enumerator(data->list), + create_nested, data, nested_cleanup); +} + +/** + * Merge a pool configuration with existing ones + */ +static bool merge_pool(private_vici_attribute_t *this, pool_t *new) +{ + mem_pool_t *tmp; + host_t *base; + pool_t *old; + const char *name; + u_int size; + + name = new->vips->get_name(new->vips); + base = new->vips->get_base(new->vips); + size = new->vips->get_size(new->vips); + + old = this->pools->remove(this->pools, name); + if (!old) + { + this->pools->put(this->pools, name, new); + DBG1(DBG_CFG, "added vici pool %s: %H, %u entries", name, base, size); + return TRUE; + } + + if (base->ip_equals(base, old->vips->get_base(old->vips)) && + size == old->vips->get_size(old->vips)) + { + /* no changes in pool, so keep existing, but use new attributes */ + DBG1(DBG_CFG, "updated vici pool %s: %H, %u entries", name, base, size); + tmp = new->vips; + new->vips = old->vips; + old->vips = tmp; + this->pools->put(this->pools, new->vips->get_name(new->vips), new); + pool_destroy(old); + return TRUE; + } + if (old->vips->get_online(old->vips) == 0) + { + /* can replace old pool, no online leases */ + DBG1(DBG_CFG, "replaced vici pool %s: %H, %u entries", name, base, size); + this->pools->put(this->pools, name, new); + pool_destroy(old); + return TRUE; + } + /* have online leases, unable to replace, TODO: migrate leases? */ + DBG1(DBG_CFG, "vici pool %s has %u online leases, unable to replace", + name, old->vips->get_online(old->vips)); + this->pools->put(this->pools, old->vips->get_name(old->vips), old); + return FALSE; +} + +/** + * Create a (error) reply message + */ +static vici_message_t* create_reply(char *fmt, ...) +{ + vici_builder_t *builder; + va_list args; + + builder = vici_builder_create(); + builder->add_kv(builder, "success", fmt ? "no" : "yes"); + if (fmt) + { + va_start(args, fmt); + builder->vadd_kv(builder, "errmsg", fmt, args); + va_end(args); + } + return builder->finalize(builder); +} + +/** + * Parse callback data, passed to each callback + */ +typedef struct { + private_vici_attribute_t *this; + vici_message_t *reply; +} request_data_t; + +/** + * Data associated to a pool load + */ +typedef struct { + request_data_t *request; + char *name; + pool_t *pool; +} load_data_t; + +CALLBACK(pool_li, bool, + load_data_t *data, vici_message_t *message, char *name, chunk_t value) +{ + struct { + char *name; + configuration_attribute_type_t v4; + configuration_attribute_type_t v6; + } keys[] = { + {"address", INTERNAL_IP4_ADDRESS, INTERNAL_IP6_ADDRESS }, + {"dns", INTERNAL_IP4_DNS, INTERNAL_IP6_DNS }, + {"nbns", INTERNAL_IP4_NBNS, INTERNAL_IP6_NBNS }, + {"dhcp", INTERNAL_IP4_DHCP, INTERNAL_IP6_DHCP }, + {"netmask", INTERNAL_IP4_NETMASK, INTERNAL_IP6_NETMASK }, + {"server", INTERNAL_IP4_SERVER, INTERNAL_IP6_SERVER }, + {"subnet", INTERNAL_IP4_SUBNET, INTERNAL_IP6_SUBNET }, + {"split_include", UNITY_SPLIT_INCLUDE, UNITY_SPLIT_INCLUDE }, + {"split_exclude", UNITY_LOCAL_LAN, UNITY_LOCAL_LAN }, + }; + char buf[256]; + int i, index = -1, mask = -1, type = 0; + chunk_t encoding; + attribute_t *attr; + host_t *host = NULL; + + for (i = 0; i < countof(keys); i++) + { + if (streq(name, keys[i].name)) + { + index = i; + break; + } + } + if (index == -1) + { + type = atoi(name); + if (!type) + { + data->request->reply = create_reply("invalid attribute: %s", name); + return FALSE; + } + } + + if (vici_stringify(value, buf, sizeof(buf))) + { + if (strchr(buf, '/')) + { + host = host_create_from_subnet(buf, &mask); + } + else + { + host = host_create_from_string(buf, 0); + } + } + if (host) + { + if (index != -1) + { + switch (host->get_family(host)) + { + case AF_INET: + type = keys[index].v4; + break; + case AF_INET6: + default: + type = keys[index].v6; + break; + } + } + if (mask == -1) + { + encoding = chunk_clone(host->get_address(host)); + } + else + { + if (host->get_family(host) == AF_INET) + { /* IPv4 attributes contain a subnet mask */ + u_int32_t netmask = 0; + + if (mask) + { /* shifting u_int32_t by 32 or more is undefined */ + mask = 32 - mask; + netmask = htonl((0xFFFFFFFF >> mask) << mask); + } + encoding = chunk_cat("cc", host->get_address(host), + chunk_from_thing(netmask)); + } + else + { /* IPv6 addresses the prefix only */ + encoding = chunk_cat("cc", host->get_address(host), + chunk_from_chars(mask)); + } + } + host->destroy(host); + } + else + { + if (index != -1) + { + data->request->reply = create_reply("invalid attribute value " + "for %s", name); + return FALSE; + } + /* use raw binary data for numbered attributes */ + encoding = chunk_clone(value); + } + INIT(attr, + .type = type, + .value = encoding, + ); + array_insert_create(&data->pool->attrs, ARRAY_TAIL, attr); + return TRUE; +} + +CALLBACK(pool_kv, bool, + load_data_t *data, vici_message_t *message, char *name, chunk_t value) +{ + if (streq(name, "addrs")) + { + char buf[128]; + host_t *base; + int bits; + + if (data->pool->vips) + { + data->request->reply = create_reply("multiple addrs defined"); + return FALSE; + } + if (!vici_stringify(value, buf, sizeof(buf))) + { + data->request->reply = create_reply("invalid addrs value"); + return FALSE; + } + base = host_create_from_subnet(buf, &bits); + if (!base) + { + data->request->reply = create_reply("invalid addrs value: %s", buf); + return FALSE; + } + data->pool->vips = mem_pool_create(data->name, base, bits); + base->destroy(base); + return TRUE; + } + data->request->reply = create_reply("invalid attribute: %s", name); + return FALSE; +} + +CALLBACK(pool_sn, bool, + request_data_t *request, vici_message_t *message, + vici_parse_context_t *ctx, char *name) +{ + load_data_t data = { + .request = request, + .name = name, + }; + bool merged; + + INIT(data.pool); + + if (!message->parse(message, ctx, NULL, pool_kv, pool_li, &data)) + { + pool_destroy(data.pool); + return FALSE; + } + + if (!data.pool->vips) + { + request->reply = create_reply("missing addrs for pool '%s'", name); + pool_destroy(data.pool); + return FALSE; + } + + request->this->lock->write_lock(request->this->lock); + merged = merge_pool(request->this, data.pool); + request->this->lock->unlock(request->this->lock); + + if (!merged) + { + request->reply = create_reply("vici pool %s has online leases, " + "unable to replace", name); + pool_destroy(data.pool); + } + return merged; +} + +CALLBACK(load_pool, vici_message_t*, + private_vici_attribute_t *this, char *name, u_int id, + vici_message_t *message) +{ + request_data_t request = { + .this = this, + }; + + if (!message->parse(message, NULL, pool_sn, NULL, NULL, &request)) + { + if (request.reply) + { + return request.reply; + } + return create_reply("parsing request failed"); + } + return create_reply(NULL); +} + +CALLBACK(unload_pool, vici_message_t*, + private_vici_attribute_t *this, char *name, u_int id, + vici_message_t *message) +{ + vici_message_t *reply; + u_int online; + pool_t *pool; + + name = message->get_str(message, NULL, "name"); + if (!name) + { + return create_reply("missing pool name to unload"); + } + + this->lock->write_lock(this->lock); + + pool = this->pools->remove(this->pools, name); + if (pool) + { + online = pool->vips->get_online(pool->vips); + if (online) + { + DBG1(DBG_CFG, "vici pool %s has %u online leases, unable to unload", + name, online); + reply = create_reply("%s has online leases, unable to unload", name); + this->pools->put(this->pools, pool->vips->get_name(pool->vips), pool); + } + else + { + DBG1(DBG_CFG, "unloaded vici pool %s", name); + reply = create_reply(NULL); + pool_destroy(pool); + } + } + else + { + reply = create_reply("%s not found", name); + } + + this->lock->unlock(this->lock); + + return reply; +} + +CALLBACK(get_pools, vici_message_t*, + private_vici_attribute_t *this, char *name, u_int id, + vici_message_t *message) +{ + vici_builder_t *builder; + enumerator_t *enumerator; + mem_pool_t *vips; + pool_t *pool; + + builder = vici_builder_create(); + + this->lock->read_lock(this->lock); + enumerator = this->pools->create_enumerator(this->pools); + while (enumerator->enumerate(enumerator, &name, &pool)) + { + vips = pool->vips; + + builder->begin_section(builder, name); + + builder->add_kv(builder, "base", "%H", vips->get_base(vips)); + builder->add_kv(builder, "size", "%u", vips->get_size(vips)); + builder->add_kv(builder, "online", "%u", vips->get_online(vips)); + builder->add_kv(builder, "offline", "%u", vips->get_offline(vips)); + + builder->end_section(builder); + } + enumerator->destroy(enumerator); + this->lock->unlock(this->lock); + + return builder->finalize(builder); +} + +static void manage_command(private_vici_attribute_t *this, + char *name, vici_command_cb_t cb, bool reg) +{ + this->dispatcher->manage_command(this->dispatcher, name, + reg ? cb : NULL, this); +} + +/** + * (Un-)register dispatcher functions + */ +static void manage_commands(private_vici_attribute_t *this, bool reg) +{ + manage_command(this, "load-pool", load_pool, reg); + manage_command(this, "unload-pool", unload_pool, reg); + manage_command(this, "get-pools", get_pools, reg); +} + +METHOD(vici_attribute_t, destroy, void, + private_vici_attribute_t *this) +{ + enumerator_t *enumerator; + pool_t *pool; + + manage_commands(this, FALSE); + + enumerator = this->pools->create_enumerator(this->pools); + while (enumerator->enumerate(enumerator, NULL, &pool)) + { + pool_destroy(pool); + } + enumerator->destroy(enumerator); + this->pools->destroy(this->pools); + this->lock->destroy(this->lock); + free(this); +} + +/* + * see header file + */ +vici_attribute_t *vici_attribute_create(vici_dispatcher_t *dispatcher) +{ + private_vici_attribute_t *this; + + INIT(this, + .public = { + .provider = { + .acquire_address = _acquire_address, + .release_address = _release_address, + .create_attribute_enumerator = _create_attribute_enumerator, + }, + .destroy = _destroy, + }, + .dispatcher = dispatcher, + .pools = hashtable_create(hashtable_hash_str, hashtable_equals_str, 4), + .lock = rwlock_create(RWLOCK_TYPE_DEFAULT), + ); + + manage_commands(this, TRUE); + + return &this->public; +} diff --git a/src/libcharon/plugins/vici/vici_attribute.h b/src/libcharon/plugins/vici/vici_attribute.h new file mode 100644 index 000000000..652a96d39 --- /dev/null +++ b/src/libcharon/plugins/vici/vici_attribute.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2014 Martin Willi + * Copyright (C) 2014 revosec 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 . + * + * 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. + */ + +/** + * @defgroup vici_attribute vici_attribute + * @{ @ingroup vici + */ + +#ifndef VICI_ATTRIBUTE_H_ +#define VICI_ATTRIBUTE_H_ + +#include "vici_dispatcher.h" + +#include + +typedef struct vici_attribute_t vici_attribute_t; + +/** + * IKE configuration attribute backend for vici. + */ +struct vici_attribute_t { + + /** + * Implements attribute provider interface + */ + attribute_provider_t provider; + + /** + * Destroy a vici_attribute_t. + */ + void (*destroy)(vici_attribute_t *this); +}; + +/** + * Create a vici_attribute instance. + * + * @param dispatcher vici dispatcher context + * @return vici attribute handler + */ +vici_attribute_t *vici_attribute_create(vici_dispatcher_t *dispatcher); + +#endif /** VICI_ATTRIBUTE_H_ @}*/ diff --git a/src/libcharon/plugins/vici/vici_plugin.c b/src/libcharon/plugins/vici/vici_plugin.c index fcd95ed6b..8881feca9 100644 --- a/src/libcharon/plugins/vici/vici_plugin.c +++ b/src/libcharon/plugins/vici/vici_plugin.c @@ -19,9 +19,11 @@ #include "vici_control.h" #include "vici_cred.h" #include "vici_config.h" +#include "vici_attribute.h" #include "vici_logger.h" #include +#include #include typedef struct private_vici_plugin_t private_vici_plugin_t; @@ -61,6 +63,11 @@ struct private_vici_plugin_t { */ vici_config_t *config; + /** + * IKE attribute backend + */ + vici_attribute_t *attrs; + /** * Generic debug logger */ @@ -92,10 +99,13 @@ static bool register_vici(private_vici_plugin_t *this, this->control = vici_control_create(this->dispatcher); this->cred = vici_cred_create(this->dispatcher); this->config = vici_config_create(this->dispatcher); + this->attrs = vici_attribute_create(this->dispatcher); this->logger = vici_logger_create(this->dispatcher); charon->backends->add_backend(charon->backends, &this->config->backend); + hydra->attributes->add_provider(hydra->attributes, + &this->attrs->provider); charon->bus->add_logger(charon->bus, &this->logger->logger); return TRUE; } @@ -104,10 +114,13 @@ static bool register_vici(private_vici_plugin_t *this, else { charon->bus->remove_logger(charon->bus, &this->logger->logger); + hydra->attributes->remove_provider(hydra->attributes, + &this->attrs->provider); charon->backends->remove_backend(charon->backends, &this->config->backend); this->logger->destroy(this->logger); + this->attrs->destroy(this->attrs); this->config->destroy(this->config); this->cred->destroy(this->cred); this->control->destroy(this->control); From c193732162775a25af8116105c10bd7f0ffa2269 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Wed, 16 Apr 2014 14:28:27 +0200 Subject: [PATCH 53/72] vici: Fix descending into non-matching sections during key find --- src/libcharon/plugins/vici/vici_message.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libcharon/plugins/vici/vici_message.c b/src/libcharon/plugins/vici/vici_message.c index 078c33ead..d3c1b23e2 100644 --- a/src/libcharon/plugins/vici/vici_message.c +++ b/src/libcharon/plugins/vici/vici_message.c @@ -265,7 +265,7 @@ static bool find_value(private_vici_message_t *this, chunk_t *value, { break; } - break; + continue; case VICI_SECTION_END: section--; continue; From ff3217db4bf9b3cdacb54c3e52a78d39a62802f1 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Tue, 11 Mar 2014 18:36:22 +0100 Subject: [PATCH 54/72] vici: Add low-level IPC protocol description --- src/libcharon/plugins/vici/Makefile.am | 3 + src/libcharon/plugins/vici/README.md | 176 +++++++++++++++++++++++++ 2 files changed, 179 insertions(+) create mode 100644 src/libcharon/plugins/vici/README.md diff --git a/src/libcharon/plugins/vici/Makefile.am b/src/libcharon/plugins/vici/Makefile.am index 2a99b1391..162827a73 100644 --- a/src/libcharon/plugins/vici/Makefile.am +++ b/src/libcharon/plugins/vici/Makefile.am @@ -29,6 +29,9 @@ libstrongswan_vici_la_SOURCES = \ libstrongswan_vici_la_LDFLAGS = -module -avoid-version +EXTRA_DIST = README.md + + lib_LTLIBRARIES = libvici.la libvici_la_SOURCES = \ diff --git a/src/libcharon/plugins/vici/README.md b/src/libcharon/plugins/vici/README.md new file mode 100644 index 000000000..ff5031e6e --- /dev/null +++ b/src/libcharon/plugins/vici/README.md @@ -0,0 +1,176 @@ +# The Versatile IKE Control Interface (VICI) protocol # + +The vici plugin implements the server side of an IPC protocol to configure, +monitor and control the IKE daemon charon. It uses request/response and event +messages to communicate over a reliable stream based transport. + +## Transport protocol ## + +To provide the service, the plugin opens a listening socket using a reliable, +stream based transport. charon relies on the different stream service +abstractions provided by libstrongswan, such as TCP and UNIX sockets. + +A client connects to this service to access functionality. It may send an +arbitrary number of packets over the connection before closing it. + +To exchange data, the transport protocol is segmented into byte sequences. +Each byte sequence is prefixed by a 16-bit length header in network order, +followed by the data. The maximum segment length is 65535 bytes, and the length +field contains the length of the data only, not including the length field +itself. + +The order of byte sequences must be strict, byte sequences must arrive in the +same order as sent. + +## Packet layer ## + +Within the byte sequences defined by the transport layer, both the client +and the server can exchange packets. The type of packet defines its structure +and purpose. The packet type is a 8-bit identifier, and is the first byte +in a transport layer byte sequence. The length of the packet is given by the +transport layer. + +While a packet type may define the format of the wrapped data freely, currently +all types either contain a name, a message or both. The following packet types +are currently defined: + +* _CMD_REQUEST = 0_: A named request message +* _CMD_RESPONSE = 1_: An unnamed response message for a request +* _CMD_UNKNOWN = 2_: An unnamed response if requested command is unknown +* _EVENT_REGISTER = 3_: A named event registration request +* _EVENT_UNREGISTER = 4_: A named event deregistration request +* _EVENT_CONFIRM = 5_: An unnamed response for successful event (de-)registration +* _EVENT_UNKNOWN = 6_: A unnamed response if event (de-)registration failed +* _EVENT = 7_: A named event message + +For packets having a named type, after the packet type an 8-bit length header +of the name follows, indicating the string length in bytes of the name tag, not +including the length field itself. The name is an ASCII string that is not +null-terminated. + +The rest of the packet forms the exchanged message, the length is determined +by the transport byte sequence length, subtracting the packet type and +the optional name tag in some messages. + +### Commands ### + +Commands are currently always requested by the client. The server replies with +a response, or with a CMD_UNKNOWN failure message to let the client know +that it does not have a handler for such a command. There is no sequence number +to associate responses to requests, so only one command can be active at +a time on a single connection. + +### Events ### + +To receive event messages, the client explicitly registers for events by name, +and also unregisters if it does not want to receive events of the named kind +anymore. The server confirms event registration using EVENT_CONFIRM, or +indicates that there is no such event source with EVENT_UNKNOWN. + +Events may get raised at any time while registered, even during an active +request command. This mechanism is used to feed continous data during a request, +for example. + +## Message format ## + +The defined packet types optionally wrap a message with additional data. +Messages are currently used in CMD_REQUEST/CMD_RESPONSE, and in EVENT packets. +A message uses a hierarchial tree of sections. Each section (or the implicit +root section) contains an arbitrary set of key/value pairs, lists and +sub-sections. The length of a message is not part of the message itself, but +the wrapping layer, usually calculated from the transport byte sequence length. + +The message encoding consists of a sequence of elements. Each element starts +with the element type, optionally followed by an element name and/or an element +value. Currently the following message element types are defined: + +* _SECTION_START = 0_: Begin a new section having a name +* _SECTION_END = 1_: End a previously started section +* _KEY_VALUE = 2_: Define a value for a named key in the current section +* _LIST_START = 3_: Begin a named list for list items +* _LIST_ITEM = 4_: Define an unnamed item value in the current list +* _LIST_END = 5_: End a previously started list + +Types are encoded as 8-bit values. Types having a name (SECTION_START, +KEY_VALUE and LIST_START) have an ASCII string following the type, which itself +uses an 8-bit length header. The string must not be null-terminated, the string +length does not include the length field itself. + +Types having a value (KEY_VALUE and LIST_ITEM) have a raw blob sequence, +prefixed with a 16-bit network order length. The blob follows the type or the +name tag if available, the length defined by the length field does not include +the length field itself. + +The interpretation of any value is not defined by the message format; it can +take arbitrary blobs. The application may specify types for specific keys, such +as strings or integer representations. + +### Sections ### + +Sections may be opened in the implicit root section, or any previously section. +They can be nested to arbitrary levels. A SECTION_END marker always closes +the last opened section; SECTION_START and SECTION_END items must be balanced +in a valid message. + +### Key/Values ### + +Key/Value pair elements may appear in the implicit root section or any explicit +sub-section at any level. Key names must be unique in the current section, use +lists to define multiple values for a key. Key/values may not appear in lists, +use a sub-section instead. + +### Lists ### + +Lists may appear at the same locations as Key/Values, and may not be nested. +Only a single list may be opened at the same time, and all lists must be closed +in valid messages. After opening a list, only list items may appear before the +list closing element. Empty lists are allowed, list items may appear within +lists only. + +### Encoding example ### + +Consider the following structure using pseudo-markup for this example: + + key1 = value1 + section1 = { + sub-section = { + key2 = value2 + } + list1 = [ item1, item2 ] + } + +The example above reprensents a valid tree structure, that gets encoded as +the following C array: + + char msg[] = { + /* key1 = value1 */ + 2, 4,'k','e','y','1', 0,6,'v','a','l','u','e','1', + /* section1 */ + 0, 8,'s','e','c','t','i','o','n','1', + /* sub-section */ + 0, 11,'s','u','b','-','s','e','c','t','i','o','n', + /* key2 = value2 */ + 2, 4,'k','e','y','2', 0,6,'v','a','l','u','e','2', + /* sub-section end */ + 1, + /* list1 */ + 3, 5, 'l','i','s','t','1', + /* item1 */ + 4, 0,5,'i','t','e','m','1', + /* item2 */ + 4, 0,5,'i','t','e','m','2', + /* list1 end */ + 5, + /* section1 end */ + 1, + }; + +# libvici C client library # + +libvici is the reference implementation of a C client library implementing +the vici protocol. It builds upon libstrongswan, but provides a stable API +to implement client applications in the C programming language. libvici uses +the libstrongswan thread pool to deliver event messages asynchronously. + +More information about the libvici API is available in the libvici.h header +file. From 046befeca5932e7e318fffae2ff2d40b6821478a Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Tue, 29 Apr 2014 11:18:21 +0200 Subject: [PATCH 55/72] vici: Use a default IKE rekey time of 4 hours --- src/libcharon/plugins/vici/vici_config.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/libcharon/plugins/vici/vici_config.c b/src/libcharon/plugins/vici/vici_config.c index 7940e1382..d0b95b861 100644 --- a/src/libcharon/plugins/vici/vici_config.c +++ b/src/libcharon/plugins/vici/vici_config.c @@ -31,6 +31,11 @@ */ #define LFT_UNDEFINED (~(u_int64_t)0) +/** + * Default IKE rekey time + */ +#define LFT_DEFAULT_IKE_REKEY (4 * 60 * 60) + typedef struct private_vici_config_t private_vici_config_t; /** @@ -1684,6 +1689,7 @@ CALLBACK(config_sn, bool, .fragmentation = FRAGMENTATION_NO, .unique = UNIQUE_NO, .keyingtries = 1, + .rekey_time = LFT_DEFAULT_IKE_REKEY, .over_time = LFT_UNDEFINED, .rand_time = LFT_UNDEFINED, }; From 585814470d65608a57c1d30c259a7f58ab67b947 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Tue, 29 Apr 2014 11:18:42 +0200 Subject: [PATCH 56/72] vici: Use a default child rekey time of 1 hour --- src/libcharon/plugins/vici/vici_config.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/libcharon/plugins/vici/vici_config.c b/src/libcharon/plugins/vici/vici_config.c index d0b95b861..bf6f88f1b 100644 --- a/src/libcharon/plugins/vici/vici_config.c +++ b/src/libcharon/plugins/vici/vici_config.c @@ -36,6 +36,11 @@ */ #define LFT_DEFAULT_IKE_REKEY (4 * 60 * 60) +/** + * Default CHILD rekey time + */ +#define LFT_DEFAULT_CHILD_REKEY (1 * 60 * 60) + typedef struct private_vici_config_t private_vici_config_t; /** @@ -1278,6 +1283,7 @@ CALLBACK(children_sn, bool, .start_action = ACTION_NONE, .lft = { .time = { + .rekey = LFT_DEFAULT_CHILD_REKEY, .life = LFT_UNDEFINED, .jitter = LFT_UNDEFINED, }, From f3e1ec4a85dfb94d9774dc60f6a19d659712f8f6 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Tue, 29 Apr 2014 12:11:09 +0200 Subject: [PATCH 57/72] vici: Have an explicit "relaxed" keyword for the default revocation policy --- src/libcharon/plugins/vici/vici_config.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/libcharon/plugins/vici/vici_config.c b/src/libcharon/plugins/vici/vici_config.c index bf6f88f1b..08cf92666 100644 --- a/src/libcharon/plugins/vici/vici_config.c +++ b/src/libcharon/plugins/vici/vici_config.c @@ -1025,12 +1025,16 @@ CALLBACK(parse_revocation, bool, enum_map_t map[] = { { "strict", VALIDATION_GOOD }, { "ifuri", VALIDATION_SKIPPED }, + { "relaxed", VALIDATION_FAILED }, }; int d; if (parse_map(map, countof(map), &d, v)) { - cfg->add(cfg, AUTH_RULE_CRL_VALIDATION, d); + if (d != VALIDATION_FAILED) + { + cfg->add(cfg, AUTH_RULE_CRL_VALIDATION, d); + } return TRUE; } return FALSE; From 7dbf9e1574f534e2724964f8e418711e830fee04 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Tue, 29 Apr 2014 16:15:01 +0200 Subject: [PATCH 58/72] vici: Document strongswan.conf options --- conf/Makefile.am | 1 + conf/plugins/vici.opt | 2 ++ 2 files changed, 3 insertions(+) create mode 100644 conf/plugins/vici.opt diff --git a/conf/Makefile.am b/conf/Makefile.am index 61a0add4d..2cb101cc0 100644 --- a/conf/Makefile.am +++ b/conf/Makefile.am @@ -78,6 +78,7 @@ plugins = \ plugins/tnccs-20.opt \ plugins/unbound.opt \ plugins/updown.opt \ + plugins/vici.opt \ plugins/whitelist.opt \ plugins/xauth-eap.opt \ plugins/xauth-pam.opt diff --git a/conf/plugins/vici.opt b/conf/plugins/vici.opt new file mode 100644 index 000000000..0fca8739b --- /dev/null +++ b/conf/plugins/vici.opt @@ -0,0 +1,2 @@ +charon.plugins.vici.socket = unix://${piddir}/charon.vici + Socket the vici plugin serves clients. From 3a9a46c20fb82d338ecf231b517083f9a53d9bbd Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Tue, 29 Apr 2014 17:08:50 +0200 Subject: [PATCH 59/72] vici: Increase vici message length header from 16 to 32 bits While we currently have no need for messages larger than 65KB, we should design the protocol to be future-proof, as we plan to keep at least to lowest protocol layer stable. To avoid any allocation issues, we currently keep the message size limit at 512KB. --- src/libcharon/plugins/vici/README.md | 8 +++---- src/libcharon/plugins/vici/libvici.c | 21 ++++++++++++------- .../plugins/vici/suites/test_socket.c | 12 +++++------ src/libcharon/plugins/vici/vici_socket.c | 21 +++++++++++++------ src/libcharon/plugins/vici/vici_socket.h | 5 +++++ 5 files changed, 43 insertions(+), 24 deletions(-) diff --git a/src/libcharon/plugins/vici/README.md b/src/libcharon/plugins/vici/README.md index ff5031e6e..46fa5b528 100644 --- a/src/libcharon/plugins/vici/README.md +++ b/src/libcharon/plugins/vici/README.md @@ -14,10 +14,10 @@ A client connects to this service to access functionality. It may send an arbitrary number of packets over the connection before closing it. To exchange data, the transport protocol is segmented into byte sequences. -Each byte sequence is prefixed by a 16-bit length header in network order, -followed by the data. The maximum segment length is 65535 bytes, and the length -field contains the length of the data only, not including the length field -itself. +Each byte sequence is prefixed by a 32-bit length header in network order, +followed by the data. The maximum segment length is currently limited to 512KB +of data, and the length field contains the length of the data only, not +including the length field itself. The order of byte sequences must be strict, byte sequences must arrive in the same order as sent. diff --git a/src/libcharon/plugins/vici/libvici.c b/src/libcharon/plugins/vici/libvici.c index becd88671..211fefd57 100644 --- a/src/libcharon/plugins/vici/libvici.c +++ b/src/libcharon/plugins/vici/libvici.c @@ -16,6 +16,7 @@ #include "libvici.h" #include "vici_builder.h" #include "vici_dispatcher.h" +#include "vici_socket.h" #include #include @@ -122,7 +123,7 @@ static bool read_error(vici_conn_t *conn, int err) /** * Handle a command response message */ -static bool handle_response(vici_conn_t *conn, u_int16_t len) +static bool handle_response(vici_conn_t *conn, u_int32_t len) { chunk_t buf; @@ -139,7 +140,7 @@ static bool handle_response(vici_conn_t *conn, u_int16_t len) /** * Dispatch received event message */ -static bool handle_event(vici_conn_t *conn, u_int16_t len) +static bool handle_event(vici_conn_t *conn, u_int32_t len) { vici_message_t *message; event_t *event; @@ -197,7 +198,7 @@ static bool handle_event(vici_conn_t *conn, u_int16_t len) CALLBACK(on_read, bool, vici_conn_t *conn, stream_t *stream) { - u_int16_t len; + u_int32_t len; u_int8_t op; ssize_t hlen; @@ -218,7 +219,11 @@ CALLBACK(on_read, bool, } } - len = ntohs(len); + len = ntohl(len); + if (len > VICI_MESSAGE_SIZE_MAX) + { + return read_error(conn, EBADMSG); + } if (len-- < sizeof(op)) { return read_error(conn, EBADMSG); @@ -353,7 +358,7 @@ vici_res_t* vici_submit(vici_req_t *req, vici_conn_t *conn) vici_message_t *message; vici_res_t *res; chunk_t data; - u_int16_t len; + u_int32_t len; u_int8_t namelen, op; message = req->b->finalize(req->b); @@ -366,7 +371,7 @@ vici_res_t* vici_submit(vici_req_t *req, vici_conn_t *conn) op = VICI_CMD_REQUEST; namelen = strlen(req->name); data = message->get_encoding(message); - len = htons(sizeof(op) + sizeof(namelen) + namelen + data.len); + len = htonl(sizeof(op) + sizeof(namelen) + namelen + data.len); if (!conn->stream->write_all(conn->stream, &len, sizeof(len)) || !conn->stream->write_all(conn->stream, &op, sizeof(op)) || @@ -679,13 +684,13 @@ void vici_free_res(vici_res_t *res) int vici_register(vici_conn_t *conn, char *name, vici_event_cb_t cb, void *user) { event_t *event; - u_int16_t len; + u_int32_t len; u_int8_t namelen, op; int ret = 1; op = cb ? VICI_EVENT_REGISTER : VICI_EVENT_UNREGISTER; namelen = strlen(name); - len = htons(sizeof(op) + sizeof(namelen) + namelen); + len = htonl(sizeof(op) + sizeof(namelen) + namelen); if (!conn->stream->write_all(conn->stream, &len, sizeof(len)) || !conn->stream->write_all(conn->stream, &op, sizeof(op)) || !conn->stream->write_all(conn->stream, &namelen, sizeof(namelen)) || diff --git a/src/libcharon/plugins/vici/suites/test_socket.c b/src/libcharon/plugins/vici/suites/test_socket.c index a9ebdbfca..032445bb0 100644 --- a/src/libcharon/plugins/vici/suites/test_socket.c +++ b/src/libcharon/plugins/vici/suites/test_socket.c @@ -32,7 +32,7 @@ static void echo_inbound(void *user, u_int id, chunk_t buf) ck_assert_int_eq(data->id, id); /* count number of bytes, including the header */ - data->bytes += buf.len + sizeof(u_int16_t); + data->bytes += buf.len + sizeof(u_int32_t); /* echo back data chunk */ data->s->send(data->s, id, chunk_clone(buf)); } @@ -73,13 +73,13 @@ START_TEST(test_echo) stream_t *c; test_data_t data = {}; chunk_t x, m = chunk_from_chars( - 0x00,0x00, - 0x00,0x01, 0x01, - 0x00,0x05, 0x11,0x12,0x13,0x14,0x15, - 0x00,0x0A, 0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x02A, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x01, 0x01, + 0x00,0x00,0x00,0x05, 0x11,0x12,0x13,0x14,0x15, + 0x00,0x00,0x00,0x0A, 0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x02A, ); char buf[m.len]; - u_int16_t len; + u_int32_t len; lib->processor->set_threads(lib->processor, 4); diff --git a/src/libcharon/plugins/vici/vici_socket.c b/src/libcharon/plugins/vici/vici_socket.c index 5a0fc81ec..05c237497 100644 --- a/src/libcharon/plugins/vici/vici_socket.c +++ b/src/libcharon/plugins/vici/vici_socket.c @@ -95,11 +95,11 @@ typedef struct { /** bytes of length header sent/received */ u_char hdrlen; /** bytes of length header */ - char hdr[sizeof(u_int16_t)]; + char hdr[sizeof(u_int32_t)]; /** send/receive buffer on heap */ chunk_t buf; /** bytes sent/received in buffer */ - u_int16_t done; + u_int32_t done; } msg_buf_t; /** @@ -397,6 +397,7 @@ CALLBACK(on_write, bool, static bool do_read(private_vici_socket_t *this, entry_t *entry, stream_t *stream) { + u_int32_t msglen; ssize_t len; /* assemble the length header first */ @@ -420,8 +421,15 @@ static bool do_read(private_vici_socket_t *this, entry_t *entry, entry->in.hdrlen += len; if (entry->in.hdrlen == sizeof(entry->in.hdr)) { + msglen = untoh32(entry->in.hdr); + if (msglen > VICI_MESSAGE_SIZE_MAX) + { + DBG1(DBG_CFG, "vici message length %u exceeds %u bytes limit, " + "ignored", msglen, VICI_MESSAGE_SIZE_MAX); + return FALSE; + } /* header complete, continue with data */ - entry->in.buf = chunk_alloc(untoh16(entry->in.hdr)); + entry->in.buf = chunk_alloc(msglen); } } @@ -584,7 +592,7 @@ CALLBACK(enable_writer, job_requeue_t, METHOD(vici_socket_t, send_, void, private_vici_socket_t *this, u_int id, chunk_t msg) { - if (msg.len <= (u_int16_t)~0) + if (msg.len <= VICI_MESSAGE_SIZE_MAX) { entry_selector_t *sel; msg_buf_t *out; @@ -596,7 +604,7 @@ METHOD(vici_socket_t, send_, void, INIT(out, .buf = msg, ); - htoun16(out->hdr, msg.len); + htoun32(out->hdr, msg.len); array_insert(entry->out, ARRAY_TAIL, out); if (array_count(entry->out) == 1) @@ -619,7 +627,8 @@ METHOD(vici_socket_t, send_, void, } else { - DBG1(DBG_CFG, "vici message exceeds maximum size, discarded"); + DBG1(DBG_CFG, "vici message size %zu exceeds maximum size of %u, " + "discarded", msg.len, VICI_MESSAGE_SIZE_MAX); chunk_clear(&msg); } } diff --git a/src/libcharon/plugins/vici/vici_socket.h b/src/libcharon/plugins/vici/vici_socket.h index f59199855..872783665 100644 --- a/src/libcharon/plugins/vici/vici_socket.h +++ b/src/libcharon/plugins/vici/vici_socket.h @@ -23,6 +23,11 @@ #include +/** + * Maximum size of a single message exchanged. + */ +#define VICI_MESSAGE_SIZE_MAX (512 * 1024) + typedef struct vici_socket_t vici_socket_t; /** From 65cc8f55813a008d587a4d9210aa48c27616673b Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Thu, 1 May 2014 10:26:49 +0200 Subject: [PATCH 60/72] vici: Properly skip raise_event() for unknown event names --- src/libcharon/plugins/vici/vici_dispatcher.c | 26 ++++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/libcharon/plugins/vici/vici_dispatcher.c b/src/libcharon/plugins/vici/vici_dispatcher.c index 4fd8028a6..0df867ad8 100644 --- a/src/libcharon/plugins/vici/vici_dispatcher.c +++ b/src/libcharon/plugins/vici/vici_dispatcher.c @@ -437,23 +437,23 @@ METHOD(vici_dispatcher_t, raise_event, void, if (event) { event->uses++; - } - this->mutex->unlock(this->mutex); + this->mutex->unlock(this->mutex); - enumerator = array_create_enumerator(event->clients); - while (enumerator->enumerate(enumerator, ¤t)) - { - if (id == 0 || id == *current) + enumerator = array_create_enumerator(event->clients); + while (enumerator->enumerate(enumerator, ¤t)) { - send_op(this, *current, VICI_EVENT, name, message); + if (id == 0 || id == *current) + { + send_op(this, *current, VICI_EVENT, name, message); + } } - } - enumerator->destroy(enumerator); + enumerator->destroy(enumerator); - this->mutex->lock(this->mutex); - if (--event->uses == 0) - { - this->cond->broadcast(this->cond); + this->mutex->lock(this->mutex); + if (--event->uses == 0) + { + this->cond->broadcast(this->cond); + } } this->mutex->unlock(this->mutex); From 374511c52cb19bd8eb187c2f2aba2c86f396cb95 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Thu, 1 May 2014 10:57:38 +0200 Subject: [PATCH 61/72] vici: Ensure we have no active users before mangling event client registrations --- src/libcharon/plugins/vici/vici_dispatcher.c | 52 ++++++++++++++------ 1 file changed, 37 insertions(+), 15 deletions(-) diff --git a/src/libcharon/plugins/vici/vici_dispatcher.c b/src/libcharon/plugins/vici/vici_dispatcher.c index 0df867ad8..6db36fbe0 100644 --- a/src/libcharon/plugins/vici/vici_dispatcher.c +++ b/src/libcharon/plugins/vici/vici_dispatcher.c @@ -129,10 +129,19 @@ static void register_event(private_vici_dispatcher_t *this, char *name, event_t *event; this->mutex->lock(this->mutex); - event = this->events->get(this->events, name); - if (event) + while (TRUE) { - array_insert(event->clients, ARRAY_TAIL, &id); + event = this->events->get(this->events, name); + if (!event) + { + break; + } + if (!event->uses) + { + array_insert(event->clients, ARRAY_TAIL, &id); + break; + } + this->cond->wait(this->cond, this->mutex); } this->mutex->unlock(this->mutex); @@ -160,20 +169,29 @@ static void unregister_event(private_vici_dispatcher_t *this, char *name, bool found = FALSE; this->mutex->lock(this->mutex); - event = this->events->get(this->events, name); - if (event) + while (TRUE) { - enumerator = array_create_enumerator(event->clients); - while (enumerator->enumerate(enumerator, ¤t)) + event = this->events->get(this->events, name); + if (!event) { - if (*current == id) - { - array_remove_at(event->clients, enumerator); - found = TRUE; - break; - } + break; } - enumerator->destroy(enumerator); + if (!event->uses) + { + enumerator = array_create_enumerator(event->clients); + while (enumerator->enumerate(enumerator, ¤t)) + { + if (*current == id) + { + array_remove_at(event->clients, enumerator); + found = TRUE; + break; + } + } + enumerator->destroy(enumerator); + break; + } + this->cond->wait(this->cond, this->mutex); } this->mutex->unlock(this->mutex); @@ -340,11 +358,15 @@ CALLBACK(disconnect, void, event_t *event; u_int *current; - /* deregister all clients */ + /* deregister client from all events */ this->mutex->lock(this->mutex); events = this->events->create_enumerator(this->events); while (events->enumerate(events, NULL, &event)) { + while (event->uses) + { + this->cond->wait(this->cond, this->mutex); + } ids = array_create_enumerator(event->clients); while (ids->enumerate(ids, ¤t)) { From 021a14b7a49d15c680185073e5536c9f0f429a71 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Thu, 1 May 2014 11:07:10 +0200 Subject: [PATCH 62/72] vici: Complete libvici doxygen comments --- src/libcharon/plugins/vici/libvici.h | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/libcharon/plugins/vici/libvici.h b/src/libcharon/plugins/vici/libvici.h index 2689b39e1..04d8f4a8e 100644 --- a/src/libcharon/plugins/vici/libvici.h +++ b/src/libcharon/plugins/vici/libvici.h @@ -28,8 +28,8 @@ * - vici_connect(): Connect to a vici service * - vici_disconnect(): Disconnect from a vici service * - * Library initialization is basically required to set up libstrongswan and - * a small thread pool. Initialize libstrongswan manually instead. + * Library initialization implicitly initializes libstrongswan and a small + * thread pool. * * Connecting requires an uri, which is currently either a UNIX socket path * prefixed with unix://, or a hostname:port touple prefixed with tcp://. @@ -269,6 +269,8 @@ void vici_free_req(vici_req_t *req); /** * Dump a message text representation to a FILE stream. * + * On error, errno is set appropriately. + * * @param res response message to dump * @param label a label to print for this message * @param out FILE to dump to @@ -292,6 +294,8 @@ vici_parse_t vici_parse(vici_res_t *res); * * The string is valid until vici_free_res() is called. * + * On error, errno is set appropriately. + * * @param res response message to parse * @return name tag / key, NULL on error */ @@ -314,7 +318,13 @@ int vici_parse_name_eq(vici_res_t *res, char *name); * * This call is valid only after vici_parse() returned VICI_PARSE_KEY_VALUE or * VICI_PARSE_LIST_ITEM. + * * The string is valid until vici_free_res() is called. + * + * On error, errno is set appropriately. + * + * @param len pointer receiving value length + * @return pointer to value, NULL on error */ void* vici_parse_value(vici_res_t *res, int *len); @@ -323,9 +333,12 @@ void* vici_parse_value(vici_res_t *res, int *len); * * This call is valid only after vici_parse() returned VICI_PARSE_KEY_VALUE or * VICI_PARSE_LIST_ITEM. + * * This call is successful only if the value contains no non-printable * characters. The string is valid until vici_free_res() is called. * + * On error, errno is set appropriately. + * * @param res response message to parse * @return value as string, NULL on error */ @@ -338,6 +351,8 @@ char* vici_parse_value_str(vici_res_t *res); * invoked for the current section level only. To descent into sections, call * vici_parse_cb() from within a section callback. * + * On error, errno is set appropriately. + * * @param res message to parse * @param section callback invoked for each section * @param kv callback invoked for key/value pairs From bc006ac1f44f9ee1dad9d2b9eca5c88ed969721b Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Thu, 1 May 2014 11:28:56 +0200 Subject: [PATCH 63/72] vici: Return number of matching and closed SAs in terminate command --- src/libcharon/plugins/vici/vici_control.c | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/libcharon/plugins/vici/vici_control.c b/src/libcharon/plugins/vici/vici_control.c index c4a74000b..3cd008162 100644 --- a/src/libcharon/plugins/vici/vici_control.c +++ b/src/libcharon/plugins/vici/vici_control.c @@ -202,12 +202,12 @@ CALLBACK(terminate, vici_message_t*, private_vici_control_t *this, char *name, u_int id, vici_message_t *request) { enumerator_t *enumerator, *isas, *csas; - char *child, *ike; + char *child, *ike, *errmsg = NULL; u_int timeout, child_id, ike_id, current, *del, done = 0; ike_sa_t *ike_sa; child_sa_t *child_sa; array_t *ids; - vici_message_t *reply; + vici_builder_t *builder; log_info_t log = { .dispatcher = this->dispatcher, .id = id, @@ -307,28 +307,31 @@ CALLBACK(terminate, vici_message_t*, } enumerator->destroy(enumerator); + builder = vici_builder_create(); if (array_count(ids) == 0) { - reply = send_reply(this, "no matching SAs to terminate found"); + errmsg = "no matching SAs to terminate found"; } else if (done < array_count(ids)) { if (array_count(ids) == 1) { - reply = send_reply(this, "terminating SA failed"); + errmsg = "terminating SA failed"; } else { - reply = send_reply(this, "terminated %u of %u SAs", - done, array_count(ids)); + errmsg = "not all matching SAs could be terminated"; } } - else + builder->add_kv(builder, "success", errmsg ? "no" : "yes"); + builder->add_kv(builder, "matches", "%u", array_count(ids)); + builder->add_kv(builder, "terminated", "%u", done); + if (errmsg) { - reply = send_reply(this, NULL); + builder->add_kv(builder, "errmsg", "%s", errmsg); } array_destroy(ids); - return reply; + return builder->finalize(builder); } /** From 41745e24f311411834a123f5f8627844c3d80faf Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Tue, 6 May 2014 10:59:55 +0200 Subject: [PATCH 64/72] vici: Handle "xauth" as an alias for "eap" secrets --- src/libcharon/plugins/vici/vici_cred.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libcharon/plugins/vici/vici_cred.c b/src/libcharon/plugins/vici/vici_cred.c index 3557cf879..2a9231dfe 100644 --- a/src/libcharon/plugins/vici/vici_cred.c +++ b/src/libcharon/plugins/vici/vici_cred.c @@ -218,7 +218,7 @@ CALLBACK(load_shared, vici_message_t*, { type = SHARED_IKE; } - else if (strcaseeq(str, "eap")) + else if (strcaseeq(str, "eap") || streq(str, "xauth")) { type = SHARED_EAP; } From c2b6402eb0b26dcf173b21389045ad72e00749d9 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Tue, 6 May 2014 11:10:05 +0200 Subject: [PATCH 65/72] vici: Log owners of a just loaded shared-secret --- src/libcharon/plugins/vici/vici_cred.c | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/libcharon/plugins/vici/vici_cred.c b/src/libcharon/plugins/vici/vici_cred.c index 2a9231dfe..cc6434b62 100644 --- a/src/libcharon/plugins/vici/vici_cred.c +++ b/src/libcharon/plugins/vici/vici_cred.c @@ -207,7 +207,10 @@ CALLBACK(load_shared, vici_message_t*, shared_key_type_t type; linked_list_t *owners; chunk_t data; - char *str; + char *str, buf[512] = ""; + enumerator_t *enumerator; + identification_t *owner; + int len; str = message->get_str(message, NULL, "type"); if (!str) @@ -243,7 +246,20 @@ CALLBACK(load_shared, vici_message_t*, owners->insert_last(owners, identification_create_from_string("%any")); } - DBG1(DBG_CFG, "loaded %N shared key", shared_key_type_names, type); + enumerator = owners->create_enumerator(owners); + while (enumerator->enumerate(enumerator, &owner)) + { + len = strlen(buf); + if (len < sizeof(buf)) + { + snprintf(buf + len, sizeof(buf) - len, "%s'%Y'", + len ? ", " : "", owner); + } + } + enumerator->destroy(enumerator); + + DBG1(DBG_CFG, "loaded %N shared key for: %s", + shared_key_type_names, type, buf); this->creds->add_shared_list(this->creds, shared_key_create(type, chunk_clone(data)), owners); From e0a34ee4591b4b86ba536f01512a47d238f722dc Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Tue, 6 May 2014 12:28:03 +0200 Subject: [PATCH 66/72] vici: Document errno values to expect from libvici API --- src/libcharon/plugins/vici/libvici.c | 2 +- src/libcharon/plugins/vici/libvici.h | 31 +++++++++++++++++++++------- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/src/libcharon/plugins/vici/libvici.c b/src/libcharon/plugins/vici/libvici.c index 211fefd57..6e3b85a72 100644 --- a/src/libcharon/plugins/vici/libvici.c +++ b/src/libcharon/plugins/vici/libvici.c @@ -532,7 +532,7 @@ char* vici_parse_value_str(vici_res_t *res) case VICI_KEY_VALUE: if (!chunk_printable(res->value, NULL, 0)) { - errno = EINVAL; + errno = EBADMSG; return NULL; } val = strndup(res->value.ptr, res->value.len); diff --git a/src/libcharon/plugins/vici/libvici.h b/src/libcharon/plugins/vici/libvici.h index 04d8f4a8e..6957a00b6 100644 --- a/src/libcharon/plugins/vici/libvici.h +++ b/src/libcharon/plugins/vici/libvici.h @@ -247,8 +247,12 @@ void vici_end_list(vici_req_t *req); /** * Submit a request message, and wait for response. * - * On error, NULL is returned an errno is set appropriately. The request - * messages gets cleaned up by this call and gets invalid. + * The request messages gets cleaned up by this call and gets invalid. + * On error, NULL is returned an errno is set to: + * - EINVAL if the request is invalid/incomplete + * - ENOENT if the command is unknown + * - EBADMSG if the response is invalid + * - Any other IO related errno * * @param req request message to send * @param conn connection context to send message over @@ -269,7 +273,8 @@ void vici_free_req(vici_req_t *req); /** * Dump a message text representation to a FILE stream. * - * On error, errno is set appropriately. + * On error, errno is set to: + * - EBADMSG if the message is invalid * * @param res response message to dump * @param label a label to print for this message @@ -294,7 +299,8 @@ vici_parse_t vici_parse(vici_res_t *res); * * The string is valid until vici_free_res() is called. * - * On error, errno is set appropriately. + * On error, errno is set to: + *- EINVAL if not in valid parser state * * @param res response message to parse * @return name tag / key, NULL on error @@ -321,7 +327,8 @@ int vici_parse_name_eq(vici_res_t *res, char *name); * * The string is valid until vici_free_res() is called. * - * On error, errno is set appropriately. + * On error, errno is set to: + * - EINVAL if not in valid parser state * * @param len pointer receiving value length * @return pointer to value, NULL on error @@ -337,7 +344,9 @@ void* vici_parse_value(vici_res_t *res, int *len); * This call is successful only if the value contains no non-printable * characters. The string is valid until vici_free_res() is called. * - * On error, errno is set appropriately. + * On error, errno is set to: + * - EBADMSG if value is not a printable string + * - EINVAL if not in valid parser state * * @param res response message to parse * @return value as string, NULL on error @@ -351,7 +360,9 @@ char* vici_parse_value_str(vici_res_t *res); * invoked for the current section level only. To descent into sections, call * vici_parse_cb() from within a section callback. * - * On error, errno is set appropriately. + * On error, errno is set to: + * - EBADMSG if message encoding invalid + * - Any other errno set by the invoked callbacks * * @param res message to parse * @param section callback invoked for each section @@ -417,7 +428,11 @@ void vici_free_res(vici_res_t *res); * (Un-)Register for events of a given kind. * * Events callbacks get invoked by a different thread from the libstrongswan - * thread pool. On failure, errno is set appropriately. + * thread pool. + * On failure, errno is set to: + * - ENOENT if the event name is unknown + * - EBADMSG if the response is invalid + * - Any other IO related errno * * @param conn connection context * @param name name of event messages to register to From 5619d40613844e293a7ca66c4cb21aa8d5fbbe08 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Tue, 6 May 2014 15:30:45 +0200 Subject: [PATCH 67/72] vici: Handle the "trap" action as an alias for "route" --- src/libcharon/plugins/vici/vici_config.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libcharon/plugins/vici/vici_config.c b/src/libcharon/plugins/vici/vici_config.c index 08cf92666..18d3f644e 100644 --- a/src/libcharon/plugins/vici/vici_config.c +++ b/src/libcharon/plugins/vici/vici_config.c @@ -700,6 +700,7 @@ CALLBACK(parse_action, bool, { "start", ACTION_RESTART }, { "restart", ACTION_RESTART }, { "route", ACTION_ROUTE }, + { "trap", ACTION_ROUTE }, { "none", ACTION_NONE }, { "clear", ACTION_NONE }, }; From dffd60083db66eaa5c9ae4480456902c36733d96 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Tue, 6 May 2014 15:36:14 +0200 Subject: [PATCH 68/72] vici: Support a "mtu" value for the tfc_padding option --- src/libcharon/plugins/vici/vici_config.c | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/libcharon/plugins/vici/vici_config.c b/src/libcharon/plugins/vici/vici_config.c index 18d3f644e..34a0f4670 100644 --- a/src/libcharon/plugins/vici/vici_config.c +++ b/src/libcharon/plugins/vici/vici_config.c @@ -391,7 +391,7 @@ static void log_child_data(child_data_t *data, char *name) DBG2(DBG_CFG, " mode = %N", ipsec_mode_names, data->mode); DBG2(DBG_CFG, " dpd_action = %N", action_names, data->dpd_action); DBG2(DBG_CFG, " reqid = %u", data->reqid); - DBG2(DBG_CFG, " tfc = %u", data->tfc); + DBG2(DBG_CFG, " tfc = %d", data->tfc); DBG2(DBG_CFG, " mark_in = %u/%u", data->mark_in.value, data->mark_in.mask); DBG2(DBG_CFG, " mark_out = %u/%u", @@ -870,6 +870,20 @@ CALLBACK(parse_mark, bool, return mark_from_string(buf, out); } +/** + * Parse TFC padding option + */ +CALLBACK(parse_tfc, bool, + u_int32_t *out, chunk_t v) +{ + if (chunk_equals(v, chunk_from_str("mtu"))) + { + *out = -1; + return TRUE; + } + return parse_uint32(out, v); +} + /** * Parse authentication config */ @@ -1195,7 +1209,7 @@ CALLBACK(child_kv, bool, { "reqid", parse_uint32, &child->reqid }, { "mark_in", parse_mark, &child->mark_in }, { "mark_out", parse_mark, &child->mark_out }, - { "tfc_padding", parse_uint32, &child->tfc }, + { "tfc_padding", parse_tfc, &child->tfc }, }; return parse_rules(rules, countof(rules), name, value, From 682c9966fa0cb8e6b066df5f403a80f1469108d4 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Tue, 6 May 2014 15:38:30 +0200 Subject: [PATCH 69/72] vici: Fallback to socket listening port if no explicit local port specified --- src/libcharon/plugins/vici/vici_config.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/libcharon/plugins/vici/vici_config.c b/src/libcharon/plugins/vici/vici_config.c index 34a0f4670..c180b0e7d 100644 --- a/src/libcharon/plugins/vici/vici_config.c +++ b/src/libcharon/plugins/vici/vici_config.c @@ -1709,7 +1709,6 @@ CALLBACK(config_sn, bool, .pull = TRUE, .send_cert = CERT_SEND_IF_ASKED, .version = IKE_ANY, - .local_port = IKEV2_UDP_PORT, .remote_port = IKEV2_UDP_PORT, .fragmentation = FRAGMENTATION_NO, .unique = UNIQUE_NO, @@ -1759,6 +1758,10 @@ CALLBACK(config_sn, bool, { peer.remote_addrs = strdup("%any"); } + if (!peer.local_port) + { + peer.local_port = charon->socket->get_port(charon->socket, FALSE); + } if (peer.over_time == LFT_UNDEFINED) { From afb7ef49087f26bb73d4e07402e9bdeb62ccfda1 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Tue, 6 May 2014 15:46:53 +0200 Subject: [PATCH 70/72] vici: Properly filter by CHILD_SA name while undoing start actions --- src/libcharon/plugins/vici/vici_config.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/libcharon/plugins/vici/vici_config.c b/src/libcharon/plugins/vici/vici_config.c index c180b0e7d..193859385 100644 --- a/src/libcharon/plugins/vici/vici_config.c +++ b/src/libcharon/plugins/vici/vici_config.c @@ -1531,8 +1531,11 @@ static void clear_start_action(private_vici_config_t *this, children = ike_sa->create_child_sa_enumerator(ike_sa); while (children->enumerate(children, &child_sa)) { - reqid = child_sa->get_reqid(child_sa); - array_insert_create(&reqids, ARRAY_TAIL, &reqid); + if (streq(name, child_sa->get_name(child_sa))) + { + reqid = child_sa->get_reqid(child_sa); + array_insert_create(&reqids, ARRAY_TAIL, &reqid); + } } children->destroy(children); } From 1e4ee168c80fdb9931190d511cb8bca604df8a7a Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Wed, 7 May 2014 12:55:30 +0200 Subject: [PATCH 71/72] vici: Check if header has been received before processing an empty message If do_read() returns with EWOULDBLOCK, we must ensure that we actually have processed the full length header before checking the zero-initialized buffer length. --- src/libcharon/plugins/vici/vici_socket.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libcharon/plugins/vici/vici_socket.c b/src/libcharon/plugins/vici/vici_socket.c index 05c237497..916772871 100644 --- a/src/libcharon/plugins/vici/vici_socket.c +++ b/src/libcharon/plugins/vici/vici_socket.c @@ -514,7 +514,8 @@ CALLBACK(on_read, bool, { disconnect(this, entry->id); } - else if (entry->in.buf.len == entry->in.done) + else if (entry->in.hdrlen == sizeof(entry->in.hdr) && + entry->in.buf.len == entry->in.done) { array_insert(entry->queue, ARRAY_TAIL, &entry->in.buf); entry->in.buf = chunk_empty; From 4787523cc32553dd569ba9f643532aaae64a8b24 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Wed, 7 May 2014 11:17:20 +0200 Subject: [PATCH 72/72] NEWS: Add vici plugin news --- NEWS | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/NEWS b/NEWS index 8390d8e4e..c0f3dbc46 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,12 @@ strongswan-5.2.0 ---------------- +- The new vici plugin provides a Versatile IKE Configuration Interface for + charon. Using the stable IPC interface, external applications can configure, + control and monitor the IKE daemon. Instead of scripting the ipsec tool + and generating ipsec.conf, third party applications can use the new interface + for more control and better reliability. + - The SWID IMC can extract all installed packages from the dpkg (Debian, Ubuntu, etc.) or rpm (Fedora, RedHat, etc) package managers, respectively, using the swidGenerator (https://github.com/tnc-ba/swidGenerator) which