diff --git a/Makefile.am b/Makefile.am index 7e3c72b3b..0703abc10 100644 --- a/Makefile.am +++ b/Makefile.am @@ -55,7 +55,7 @@ cov-report: lcov -r $(top_builddir)/coverage/coverage.info '*/tests/*' \ -o $(top_builddir)/coverage/coverage.cleaned.info \ --rc lcov_branch_coverage=1 - genhtml --num-spaces 4 --legend --branch-coverage \ + genhtml --num-spaces 4 --legend --branch-coverage --ignore-errors source \ -t "$(PACKAGE_STRING)" \ -o $(top_builddir)/coverage/html \ -p `readlink -m $(abs_top_srcdir)`/src \ diff --git a/scripts/Makefile.am b/scripts/Makefile.am index ed5147a3d..abc6d75dd 100644 --- a/scripts/Makefile.am +++ b/scripts/Makefile.am @@ -5,7 +5,7 @@ AM_CPPFLAGS = \ noinst_PROGRAMS = bin2array bin2sql id2sql key2keyid keyid2sql oid2der \ thread_analysis dh_speed pubkey_speed crypt_burn hash_burn fetch \ - dnssec malloc_speed aes-test + dnssec malloc_speed aes-test settings-test if USE_TLS noinst_PROGRAMS += tls_test @@ -40,6 +40,7 @@ malloc_speed_LDADD = $(top_builddir)/src/libstrongswan/libstrongswan.la $(RTLIB) fetch_LDADD = $(top_builddir)/src/libstrongswan/libstrongswan.la dnssec_LDADD = $(top_builddir)/src/libstrongswan/libstrongswan.la aes_test_LDADD = $(top_builddir)/src/libstrongswan/libstrongswan.la +settings_test_LDADD = $(top_builddir)/src/libstrongswan/libstrongswan.la key2keyid.o : $(top_builddir)/config.status diff --git a/scripts/settings-test.c b/scripts/settings-test.c new file mode 100644 index 000000000..452798aee --- /dev/null +++ b/scripts/settings-test.c @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2014 Tobias Brunner + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * 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 +#include +#include +#include + +#include +#include + +/** + * Defined in libstrongswan but not part of the public API + */ +bool settings_parser_parse_file(void *this, char *name); + +/** + * Recursively print the section and all subsections/settings + */ +static void print_section(section_t *section, int level) +{ + section_t *sub; + kv_t *kv; + int i; + char indent[256]; + + for (i = 0; i < level * 2 && i < sizeof(indent) - 2; i += 2) + { + indent[i ] = ' '; + indent[i+1] = ' '; + } + indent[i] = '\0'; + + for (i = 0; i < array_count(section->kv_order); i++) + { + array_get(section->kv_order, i, &kv); + printf("%s%s = %s\n", indent, kv->key, kv->value); + } + for (i = 0; i < array_count(section->sections_order); i++) + { + array_get(section->sections_order, i, &sub); + printf("%s%s {\n", indent, sub->name); + print_section(sub, level + 1); + printf("%s}\n", indent); + } +} + +static void usage(FILE *out, char *name) +{ + fprintf(out, "Test strongswan.conf parser\n\n"); + fprintf(out, "%s [OPTIONS]\n\n", name); + fprintf(out, "Options:\n"); + fprintf(out, " -h, --help print this help.\n"); + fprintf(out, " -d, --debug enables debugging of the parser.\n"); + fprintf(out, " -f, --file=FILE config file to load (default STDIN).\n"); + fprintf(out, "\n"); +} + +int main(int argc, char *argv[]) +{ + char *file = NULL; + + /* don't load strongswan.conf */ + library_init("", "settings-test"); + atexit(library_deinit); + + dbg_default_set_level(3); + + while (true) + { + struct option long_opts[] = { + {"help", no_argument, NULL, 'h' }, + {"debug", no_argument, NULL, 'd' }, + {"file", required_argument, NULL, 'f' }, + {0,0,0,0 }, + }; + switch (getopt_long(argc, argv, "hdf:", long_opts, NULL)) + { + case EOF: + break; + case 'h': + usage(stdout, argv[0]); + return 0; + case 'd': + setenv("DEBUG_SETTINGS_PARSER", "1", TRUE); + continue; + case 'f': + file = optarg; + continue; + default: + usage(stderr, argv[0]); + return 1; + } + break; + } + + if (file) + { + section_t *root = settings_section_create(strdup("root")); + + settings_parser_parse_file(root, file); + + print_section(root, 0); + + settings_section_destroy(root, NULL); + } + else + { + usage(stderr, argv[0]); + } + return 0; +} diff --git a/src/charon-cmd/charon-cmd.c b/src/charon-cmd/charon-cmd.c index a70d314af..b41cf467c 100644 --- a/src/charon-cmd/charon-cmd.c +++ b/src/charon-cmd/charon-cmd.c @@ -126,12 +126,15 @@ static int run() { DBG1(DBG_DMN, "signal of type SIGHUP received. Reloading " "configuration"); - if (lib->settings->load_files(lib->settings, NULL, FALSE)) +#ifdef STRONGSWAN_CONF + if (lib->settings->load_files(lib->settings, STRONGSWAN_CONF, + FALSE)) { charon->load_loggers(charon, levels, TRUE); lib->plugins->reload(lib->plugins, NULL); } else +#endif { DBG1(DBG_DMN, "reloading config failed, keeping old"); } diff --git a/src/charon/charon.c b/src/charon/charon.c index 089ac4570..a82aa4256 100644 --- a/src/charon/charon.c +++ b/src/charon/charon.c @@ -122,12 +122,15 @@ static void run() { DBG1(DBG_DMN, "signal of type SIGHUP received. Reloading " "configuration"); - if (lib->settings->load_files(lib->settings, NULL, FALSE)) +#ifdef STRONGSWAN_CONF + if (lib->settings->load_files(lib->settings, STRONGSWAN_CONF, + FALSE)) { charon->load_loggers(charon, levels, !use_syslog); lib->plugins->reload(lib->plugins, NULL); } else +#endif { DBG1(DBG_DMN, "reloading config failed, keeping old"); } diff --git a/src/libstrongswan/Android.mk b/src/libstrongswan/Android.mk index 24f0bc552..1840ad26e 100644 --- a/src/libstrongswan/Android.mk +++ b/src/libstrongswan/Android.mk @@ -32,12 +32,14 @@ networking/streams/stream_service.c networking/streams/stream_manager.c \ pen/pen.c plugins/plugin_loader.c plugins/plugin_feature.c processing/jobs/job.c \ processing/jobs/callback_job.c processing/processor.c processing/scheduler.c \ processing/watcher.c resolver/resolver_manager.c resolver/rr_set.c \ -selectors/traffic_selector.c threading/thread.c threading/thread_value.c \ -threading/mutex.c threading/semaphore.c threading/rwlock.c threading/spinlock.c \ +selectors/traffic_selector.c settings/settings.c settings/settings_types.c \ +settings/settings_parser.c settings/settings_lexer.c \ +threading/thread.c threading/thread_value.c threading/mutex.c \ +threading/semaphore.c threading/rwlock.c threading/spinlock.c \ utils/utils.c utils/chunk.c utils/debug.c utils/enum.c utils/identification.c \ utils/lexparser.c utils/optionsfrom.c utils/capabilities.c utils/backtrace.c \ -utils/printf_hook/printf_hook_builtin.c utils/settings.c utils/test.c \ -utils/utils/strerror.c +utils/parser_helper.c utils/test.c utils/utils/strerror.c \ +utils/printf_hook/printf_hook_builtin.c # adding the plugin source files diff --git a/src/libstrongswan/Makefile.am b/src/libstrongswan/Makefile.am index 3462d2ffc..c4d1a5802 100644 --- a/src/libstrongswan/Makefile.am +++ b/src/libstrongswan/Makefile.am @@ -30,12 +30,17 @@ networking/streams/stream_service.c networking/streams/stream_manager.c \ pen/pen.c plugins/plugin_loader.c plugins/plugin_feature.c processing/jobs/job.c \ processing/jobs/callback_job.c processing/processor.c processing/scheduler.c \ processing/watcher.c resolver/resolver_manager.c resolver/rr_set.c \ -selectors/traffic_selector.c threading/thread.c threading/thread_value.c \ -threading/mutex.c threading/semaphore.c threading/rwlock.c threading/spinlock.c \ +selectors/traffic_selector.c settings/settings.c settings/settings_types.c \ +settings/settings_parser.y settings/settings_lexer.l \ +threading/thread.c threading/thread_value.c threading/mutex.c \ +threading/semaphore.c threading/rwlock.c threading/spinlock.c \ utils/utils.c utils/chunk.c utils/debug.c utils/enum.c utils/identification.c \ utils/lexparser.c utils/optionsfrom.c utils/capabilities.c utils/backtrace.c \ -utils/settings.c utils/test.c \ -utils/utils/strerror.c +utils/parser_helper.c utils/test.c utils/utils/strerror.c + +# private header files +noinst_HEADERS = \ +settings/settings_types.h if USE_DEV_HEADERS strongswan_includedir = ${dev_headers} @@ -75,14 +80,14 @@ resolver/rr.h resolver/resolver_manager.h \ plugins/plugin_loader.h plugins/plugin.h plugins/plugin_feature.h \ processing/jobs/job.h processing/jobs/callback_job.h processing/processor.h \ processing/scheduler.h processing/watcher.h selectors/traffic_selector.h \ -threading/thread.h threading/thread_value.h \ +settings/settings.h threading/thread.h threading/thread_value.h \ threading/mutex.h threading/condvar.h threading/spinlock.h threading/semaphore.h \ threading/rwlock.h threading/rwlock_condvar.h threading/lock_profiler.h \ utils/utils.h utils/chunk.h utils/debug.h utils/enum.h utils/identification.h \ utils/lexparser.h utils/optionsfrom.h utils/capabilities.h utils/backtrace.h \ utils/leak_detective.h utils/printf_hook/printf_hook.h \ utils/printf_hook/printf_hook_vstr.h utils/printf_hook/printf_hook_builtin.h \ -utils/settings.h utils/test.h utils/integrity_checker.h \ +utils/parser_helper.h utils/test.h utils/integrity_checker.h \ utils/utils/strerror.h endif @@ -103,6 +108,8 @@ AM_CFLAGS = \ AM_LDFLAGS = \ -no-undefined +AM_YFLAGS = -v -d + if USE_LEAK_DETECTIVE AM_CPPFLAGS += -DLEAK_DETECTIVE libstrongswan_la_SOURCES += utils/leak_detective.c @@ -144,7 +151,8 @@ Android.mk AndroidConfigLocal.h BUILT_SOURCES = \ $(srcdir)/asn1/oid.c $(srcdir)/asn1/oid.h \ -$(srcdir)/crypto/proposal/proposal_keywords_static.c +$(srcdir)/crypto/proposal/proposal_keywords_static.c \ +settings/settings_parser.h MAINTAINERCLEANFILES = \ $(srcdir)/asn1/oid.c $(srcdir)/asn1/oid.h \ diff --git a/src/libstrongswan/collections/array.c b/src/libstrongswan/collections/array.c index 75efb85bf..8d619116a 100644 --- a/src/libstrongswan/collections/array.c +++ b/src/libstrongswan/collections/array.c @@ -168,7 +168,7 @@ array_t *array_create(u_int esize, u_int8_t reserve) ); if (array->tail) { - array->data = malloc(array->tail * array->esize); + array->data = malloc(get_size(array, array->tail)); } return array; } diff --git a/src/libstrongswan/collections/enumerator.c b/src/libstrongswan/collections/enumerator.c index 8049ac016..0c97f796f 100644 --- a/src/libstrongswan/collections/enumerator.c +++ b/src/libstrongswan/collections/enumerator.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008 Tobias Brunner + * Copyright (C) 2008-2013 Tobias Brunner * Copyright (C) 2007 Martin Willi * Hochschule fuer Technik Rapperswil * @@ -25,6 +25,10 @@ #include #include +#ifdef HAVE_GLOB_H +#include +#endif /* HAVE_GLOB_H */ + #include /** @@ -157,8 +161,106 @@ enumerator_t* enumerator_create_directory(const char *path) return &this->public; } +#ifdef HAVE_GLOB_H + /** - * Enumerator implementation for directory enumerator + * Enumerator implementation for glob enumerator + */ +typedef struct { + /** implements enumerator_t */ + enumerator_t public; + /** glob data */ + glob_t glob; + /** current match */ + char **match; + /** absolute path of current file */ + char full[PATH_MAX]; +} glob_enum_t; + +/** + * Implementation of enumerator_create_glob().destroy + */ +static void destroy_glob_enum(glob_enum_t *this) +{ + globfree(&this->glob); + free(this); +} + +/** + * Implementation of enumerator_create_glob().enumerate + */ +static bool enumerate_glob_enum(glob_enum_t *this, char **file, struct stat *st) +{ + char *match = *(++this->match); + + if (!match) + { + return FALSE; + } + if (file) + { + *file = match; + } + if (st) + { + if (stat(match, st)) + { + DBG1(DBG_LIB, "stat() on '%s' failed: %s", match, + strerror(errno)); + return FALSE; + } + } + return TRUE; +} + +/** + * See header + */ +enumerator_t* enumerator_create_glob(const char *pattern) +{ + glob_enum_t *this; + int status; + + if (!pattern) + { + return enumerator_create_empty(); + } + + INIT(this, + .public = { + .enumerate = (void*)enumerate_glob_enum, + .destroy = (void*)destroy_glob_enum, + }, + .glob = { + .gl_offs = 1, /* reserve one slot so we can enumerate easily */ + } + ); + + status = glob(pattern, GLOB_DOOFFS | GLOB_ERR, NULL, &this->glob); + if (status == GLOB_NOMATCH) + { + DBG1(DBG_LIB, "no files found matching '%s'", pattern); + } + else if (status != 0) + { + DBG1(DBG_LIB, "expanding file pattern '%s' failed: %s", pattern, + strerror(errno)); + } + this->match = this->glob.gl_pathv; + return &this->public; +} + +#else /* HAVE_GLOB_H */ + +enumerator_t* enumerator_create_glob(const char *pattern) +{ + return NULL; +} + +#endif /* HAVE_GLOB_H */ + +/** + * Enumerator implementation for token enumerator */ typedef struct { /** implements enumerator_t */ diff --git a/src/libstrongswan/collections/enumerator.h b/src/libstrongswan/collections/enumerator.h index 299373a3e..55f8d83e6 100644 --- a/src/libstrongswan/collections/enumerator.h +++ b/src/libstrongswan/collections/enumerator.h @@ -1,4 +1,5 @@ /* + * Copyright (C) 2013 Tobias Brunner * Copyright (C) 2007 Martin Willi * Hochschule fuer Technik Rapperswil * @@ -69,7 +70,9 @@ enumerator_t *enumerator_create_single(void *item, void (*cleanup)(void *item)); * This enumerator_t.enumerate() function returns a (to the directory) relative * filename (as a char*), an absolute filename (as a char*) and a file status * (to a struct stat), which all may be NULL. "." and ".." entries are - * skipped. Example: + * skipped. + * + * Example: * * @code char *rel, *abs; @@ -95,6 +98,38 @@ enumerator_t *enumerator_create_single(void *item, void (*cleanup)(void *item)); */ enumerator_t* enumerator_create_directory(const char *path); +/** + * Create an enumerator over files/directories matching a file pattern. + * + * This enumerator_t.enumerate() function returns the filename (as a char*), + * and a file status (to a struct stat), which both may be NULL. + * + * Example: + * + * @code + char *file; + struct stat st; + enumerator_t *e; + + e = enumerator_create_glob("/etc/ipsec.*.conf"); + if (e) + { + while (e->enumerate(e, &file, &st)) + { + if (S_ISREG(st.st_mode)) + { + printf("%s\n", file); + } + } + e->destroy(e); + } + @endcode + * + * @param pattern file pattern to match + * @return the enumerator, NULL if not supported + */ +enumerator_t* enumerator_create_glob(const char *pattern); + /** * Create an enumerator over tokens of a string. * diff --git a/src/libstrongswan/library.c b/src/libstrongswan/library.c index c5bb4cd93..f152a8c1f 100644 --- a/src/libstrongswan/library.c +++ b/src/libstrongswan/library.c @@ -298,6 +298,13 @@ bool library_init(char *settings, const char *namespace) this->objects = hashtable_create((hashtable_hash_t)hash, (hashtable_equals_t)equals, 4); + +#ifdef STRONGSWAN_CONF + if (!settings) + { + settings = STRONGSWAN_CONF; + } +#endif this->public.settings = settings_create(settings); /* all namespace settings may fall back to libstrongswan */ lib->settings->add_fallback(lib->settings, lib->ns, "libstrongswan"); diff --git a/src/libstrongswan/library.h b/src/libstrongswan/library.h index 4125328b7..37a83fa2f 100644 --- a/src/libstrongswan/library.h +++ b/src/libstrongswan/library.h @@ -113,8 +113,8 @@ #include "utils/capabilities.h" #include "utils/integrity_checker.h" #include "utils/leak_detective.h" -#include "utils/settings.h" #include "plugins/plugin_loader.h" +#include "settings/settings.h" typedef struct library_t library_t; diff --git a/src/libstrongswan/settings/.gitignore b/src/libstrongswan/settings/.gitignore new file mode 100644 index 000000000..a6e46817d --- /dev/null +++ b/src/libstrongswan/settings/.gitignore @@ -0,0 +1,3 @@ +settings_lexer.c +settings_parser.[ch] +settings_parser.output \ No newline at end of file diff --git a/src/libstrongswan/utils/settings.c b/src/libstrongswan/settings/settings.c similarity index 57% rename from src/libstrongswan/utils/settings.c rename to src/libstrongswan/settings/settings.c index cf34fd1cf..6c69782ec 100644 --- a/src/libstrongswan/utils/settings.c +++ b/src/libstrongswan/settings/settings.c @@ -24,11 +24,8 @@ #include #include -#ifdef HAVE_GLOB_H -#include -#endif /* HAVE_GLOB_H */ - #include "settings.h" +#include "settings_types.h" #include "collections/array.h" #include "collections/hashtable.h" @@ -36,188 +33,42 @@ #include "threading/rwlock.h" #include "utils/debug.h" -#define MAX_INCLUSION_LEVEL 10 - typedef struct private_settings_t private_settings_t; -typedef struct section_t section_t; -typedef struct kv_t kv_t; /** - * private data of settings + * Parse function provided by the generated parser. + */ +bool settings_parser_parse_file(section_t *root, char *name); + +/** + * Private data of settings */ struct private_settings_t { /** - * public functions + * Public interface */ settings_t public; /** - * top level section + * Top level section */ section_t *top; /** - * contents of loaded files and in-memory settings (char*) + * Contents of replaced settings (char*) + * + * FIXME: This is required because the pointer returned by get_str() + * is not refcounted. Might cause ever increasing usage stats. */ - linked_list_t *contents; + array_t *contents; /** - * lock to safely access the settings + * Lock to safely access the settings */ rwlock_t *lock; }; -/** - * section containing subsections and key value pairs - */ -struct section_t { - - /** - * name of the section - */ - char *name; - - /** - * fallback sections, as section_t - */ - array_t *fallbacks; - - /** - * subsections, as section_t - */ - array_t *sections; - - /** - * key value pairs, as kv_t - */ - array_t *kv; -}; - -/** - * Key value pair - */ -struct kv_t { - - /** - * key string, relative - */ - char *key; - - /** - * value as string - */ - char *value; -}; - -/** - * create a key/value pair - */ -static kv_t *kv_create(char *key, char *value) -{ - kv_t *this; - INIT(this, - .key = strdup(key), - .value = value, - ); - return this; -} - -/** - * destroy a key/value pair - */ -static void kv_destroy(kv_t *this) -{ - free(this->key); - free(this); -} - -/** - * create a section with the given name - */ -static section_t *section_create(char *name) -{ - section_t *this; - INIT(this, - .name = strdupnull(name), - ); - return this; -} - -/** - * destroy a section - */ -static void section_destroy(section_t *this) -{ - array_destroy_function(this->sections, (void*)section_destroy, NULL); - array_destroy_function(this->kv, (void*)kv_destroy, NULL); - array_destroy(this->fallbacks); - free(this->name); - free(this); -} - -/** - * Purge contents of a section, returns if section can be safely removed. - */ -static bool section_purge(section_t *this) -{ - section_t *current; - int i; - - array_destroy_function(this->kv, (void*)kv_destroy, NULL); - this->kv = NULL; - /* we ensure sections used as fallback, or configured with fallbacks (or - * having any such subsections) are not removed */ - for (i = array_count(this->sections) - 1; i >= 0; i--) - { - array_get(this->sections, i, ¤t); - if (section_purge(current)) - { - array_remove(this->sections, i, NULL); - section_destroy(current); - } - } - return !this->fallbacks && !array_count(this->sections); -} - -/** - * callback to find a section by name - */ -static int section_find(const void *a, const void *b) -{ - const char *key = a; - const section_t *item = b; - return strcmp(key, item->name); -} - -/** - * callback to sort sections by name - */ -static int section_sort(const void *a, const void *b, void *user) -{ - const section_t *sa = a, *sb = b; - return strcmp(sa->name, sb->name); -} - -/** - * callback to find a kv pair by key - */ -static int kv_find(const void *a, const void *b) -{ - const char *key = a; - const kv_t *item = b; - return strcmp(key, item->key); -} - -/** - * callback to sort kv pairs by key - */ -static int kv_sort(const void *a, const void *b, void *user) -{ - const kv_t *kva = a, *kvb = b; - return strcmp(kva->key, kvb->key); -} - /** * Print a format key, but consume already processed arguments */ @@ -290,13 +141,13 @@ static section_t *find_section_buffered(section_t *section, { found = section; } - else if (array_bsearch(section->sections, buf, section_find, &found) == -1) + else if (array_bsearch(section->sections, buf, settings_section_find, + &found) == -1) { if (ensure) { - found = section_create(buf); - array_insert_create(§ion->sections, ARRAY_TAIL, found); - array_sort(section->sections, section_sort, NULL); + found = settings_section_create(strdup(buf)); + settings_section_add(section, found, NULL); } } if (found && pos) @@ -340,7 +191,7 @@ static void find_sections_buffered(section_t *section, char *start, char *key, } else { - array_bsearch(section->sections, buf, section_find, &found); + array_bsearch(section->sections, buf, settings_section_find, &found); } if (found) { @@ -501,14 +352,13 @@ static kv_t *find_value_buffered(section_t *section, char *start, char *key, { found = section; } - else if (array_bsearch(section->sections, buf, section_find, + else if (array_bsearch(section->sections, buf, settings_section_find, &found) == -1) { if (ensure) { - found = section_create(buf); - array_insert_create(§ion->sections, ARRAY_TAIL, found); - array_sort(section->sections, section_sort, NULL); + found = settings_section_create(strdup(buf)); + settings_section_add(section, found, NULL); } } if (found) @@ -532,13 +382,12 @@ static kv_t *find_value_buffered(section_t *section, char *start, char *key, { return NULL; } - if (array_bsearch(section->kv, buf, kv_find, &kv) == -1) + if (array_bsearch(section->kv, buf, settings_kv_find, &kv) == -1) { if (ensure) { - kv = kv_create(buf, NULL); - array_insert_create(§ion->kv, ARRAY_TAIL, kv); - array_sort(section->kv, kv_sort, NULL); + kv = settings_kv_create(strdup(buf), NULL); + settings_kv_add(section, kv, NULL); } else if (section->fallbacks) { @@ -596,19 +445,7 @@ static void set_value(private_settings_t *this, section_t *section, TRUE); if (kv) { - if (!value) - { - kv->value = NULL; - } - else if (kv->value && (strlen(value) <= strlen(kv->value))) - { /* overwrite in-place, if possible */ - strcpy(kv->value, value); - } - else - { /* otherwise clone the string and store it in the cache */ - kv->value = strdup(value); - this->contents->insert_last(this->contents, kv->value); - } + settings_kv_set(kv, strdupnull(value), this->contents); } this->lock->unlock(this->lock); } @@ -892,7 +729,8 @@ static bool section_filter(hashtable_t *seen, section_t **in, char **out) static enumerator_t *section_enumerator(section_t *section, enumerator_data_t *data) { - return enumerator_create_filter(array_create_enumerator(section->sections), + return enumerator_create_filter( + array_create_enumerator(section->sections_order), (void*)section_filter, data->seen, NULL); } @@ -929,7 +767,7 @@ static bool kv_filter(hashtable_t *seen, kv_t **in, char **key, void *none, char **value) { *key = (*in)->key; - if (seen->get(seen, *key)) + if (seen->get(seen, *key) || !(*in)->value) { return FALSE; } @@ -943,7 +781,7 @@ static bool kv_filter(hashtable_t *seen, kv_t **in, char **key, */ static enumerator_t *kv_enumerator(section_t *section, enumerator_data_t *data) { - return enumerator_create_filter(array_create_enumerator(section->kv), + return enumerator_create_filter(array_create_enumerator(section->kv_order), (void*)kv_filter, data->seen, NULL); } @@ -989,464 +827,35 @@ METHOD(settings_t, add_fallback, void, va_end(args); } -/** - * parse text, truncate "skip" chars, delimited by term respecting brackets. - * - * Chars in "skip" are truncated at the beginning and the end of the resulting - * token. "term" contains a list of characters to read up to (first match), - * while "br" contains bracket counterparts found in "term" to skip. - */ -static char parse(char **text, char *skip, char *term, char *br, char **token) -{ - char *best = NULL; - char best_term = '\0'; - - /* skip leading chars */ - while (strchr(skip, **text)) - { - (*text)++; - if (!**text) - { - return 0; - } - } - /* mark begin of subtext */ - *token = *text; - while (*term) - { - char *pos = *text; - int level = 1; - - /* find terminator */ - while (*pos) - { - if (*pos == *term) - { - level--; - } - else if (br && *pos == *br) - { - level++; - } - if (level == 0) - { - if (best == NULL || best > pos) - { - best = pos; - best_term = *term; - } - break; - } - pos++; - } - /* try next terminator */ - term++; - if (br) - { - br++; - } - } - if (best) - { - /* update input */ - *text = best; - /* null trailing bytes */ - do - { - *best = '\0'; - best--; - } - while (best >= *token && strchr(skip, *best)); - /* return found terminator */ - return best_term; - } - return 0; -} - -/** - * Check if "text" starts with "pattern". - * Characters in "skip" are skipped first. If found, TRUE is returned and "text" - * is modified to point to the character right after "pattern". - */ -static bool starts_with(char **text, char *skip, char *pattern) -{ - char *pos = *text; - int len = strlen(pattern); - while (strchr(skip, *pos)) - { - pos++; - if (!*pos) - { - return FALSE; - } - } - if (strlen(pos) < len || !strneq(pos, pattern, len)) - { - return FALSE; - } - *text = pos + len; - return TRUE; -} - -/** - * Check if what follows in "text" is an include statement. - * If this function returns TRUE, "text" will point to the character right after - * the include pattern, which is returned in "pattern". - */ -static bool parse_include(char **text, char **pattern) -{ - char *pos = *text; - if (!starts_with(&pos, "\n\t ", "include")) - { - return FALSE; - } - if (starts_with(&pos, "\t ", "=")) - { /* ignore "include = value" */ - return FALSE; - } - *text = pos; - return parse(text, "\t ", "\n", NULL, pattern) != 0; -} - -/** - * Forward declaration. - */ -static bool parse_files(linked_list_t *contents, char *file, int level, - char *pattern, section_t *section); - -/** - * Parse a section - */ -static bool parse_section(linked_list_t *contents, char *file, int level, - char **text, section_t *section) -{ - bool finished = FALSE; - char *key, *value, *inner; - - while (!finished) - { - if (parse_include(text, &value)) - { - if (!parse_files(contents, file, level, value, section)) - { - DBG1(DBG_LIB, "failed to include '%s'", value); - return FALSE; - } - continue; - } - switch (parse(text, "\t\n ", "{=#", NULL, &key)) - { - case '{': - if (parse(text, "\t ", "}", "{", &inner)) - { - section_t *sub; - if (!strlen(key)) - { - DBG1(DBG_LIB, "skipping section without name in '%s'", - section->name); - continue; - } - if (array_bsearch(section->sections, key, section_find, - &sub) == -1) - { - sub = section_create(key); - if (parse_section(contents, file, level, &inner, sub)) - { - array_insert_create(§ion->sections, ARRAY_TAIL, - sub); - array_sort(section->sections, section_sort, NULL); - continue; - } - section_destroy(sub); - } - else - { /* extend the existing section */ - if (parse_section(contents, file, level, &inner, sub)) - { - continue; - } - } - DBG1(DBG_LIB, "parsing subsection '%s' failed", key); - break; - } - DBG1(DBG_LIB, "matching '}' not found near %s", *text); - break; - case '=': - if (parse(text, "\t ", "\n", NULL, &value)) - { - kv_t *kv; - if (!strlen(key)) - { - DBG1(DBG_LIB, "skipping value without key in '%s'", - section->name); - continue; - } - if (array_bsearch(section->kv, key, kv_find, &kv) == -1) - { - kv = kv_create(key, value); - array_insert_create(§ion->kv, ARRAY_TAIL, kv); - array_sort(section->kv, kv_sort, NULL); - } - else - { /* replace with the most recently read value */ - kv->value = value; - } - continue; - } - DBG1(DBG_LIB, "parsing value failed near %s", *text); - break; - case '#': - parse(text, "", "\n", NULL, &value); - continue; - default: - finished = TRUE; - continue; - } - return FALSE; - } - return TRUE; -} - -/** - * Parse a file and add the settings to the given section. - */ -static bool parse_file(linked_list_t *contents, char *file, int level, - section_t *section) -{ - bool success; - char *text, *pos; - struct stat st; - FILE *fd; - int len; - - DBG2(DBG_LIB, "loading config file '%s'", file); - if (stat(file, &st) == -1) - { - if (errno == ENOENT) - { -#ifdef STRONGSWAN_CONF - if (streq(file, STRONGSWAN_CONF)) - { - DBG2(DBG_LIB, "'%s' does not exist, ignored", file); - } - else -#endif - { - DBG1(DBG_LIB, "'%s' does not exist, ignored", file); - } - return TRUE; - } - DBG1(DBG_LIB, "failed to stat '%s': %s", file, strerror(errno)); - return FALSE; - } - else if (!S_ISREG(st.st_mode)) - { - DBG1(DBG_LIB, "'%s' is not a regular file", file); - return FALSE; - } - fd = fopen(file, "r"); - if (fd == NULL) - { - DBG1(DBG_LIB, "'%s' is not readable", file); - return FALSE; - } - fseek(fd, 0, SEEK_END); - len = ftell(fd); - rewind(fd); - text = malloc(len + 2); - text[len] = text[len + 1] = '\0'; - if (fread(text, 1, len, fd) != len) - { - free(text); - fclose(fd); - return FALSE; - } - fclose(fd); - - pos = text; - success = parse_section(contents, file, level, &pos, section); - if (!success) - { - free(text); - } - else - { - contents->insert_last(contents, text); - } - return success; -} - -/** - * Load the files matching "pattern", which is resolved with glob(3), if - * available. - * If the pattern is relative, the directory of "file" is used as base. - */ -static bool parse_files(linked_list_t *contents, char *file, int level, - char *pattern, section_t *section) -{ - bool success = TRUE; - char pat[PATH_MAX]; - - if (level > MAX_INCLUSION_LEVEL) - { - DBG1(DBG_LIB, "maximum level of %d includes reached, ignored", - MAX_INCLUSION_LEVEL); - return TRUE; - } - - if (!strlen(pattern)) - { - DBG1(DBG_LIB, "empty include pattern, ignored"); - return TRUE; - } - - if (!file || pattern[0] == '/') - { /* absolute path */ - if (snprintf(pat, sizeof(pat), "%s", pattern) >= sizeof(pat)) - { - DBG1(DBG_LIB, "include pattern too long, ignored"); - return TRUE; - } - } - else - { /* base relative paths to the directory of the current file */ - char *dir = path_dirname(file); - if (snprintf(pat, sizeof(pat), "%s/%s", dir, pattern) >= sizeof(pat)) - { - DBG1(DBG_LIB, "include pattern too long, ignored"); - free(dir); - return TRUE; - } - free(dir); - } -#ifdef HAVE_GLOB_H - { - int status; - glob_t buf; - - status = glob(pat, GLOB_ERR, NULL, &buf); - if (status == GLOB_NOMATCH) - { - DBG1(DBG_LIB, "no files found matching '%s', ignored", pat); - } - else if (status != 0) - { - DBG1(DBG_LIB, "expanding file pattern '%s' failed", pat); - success = FALSE; - } - else - { - char **expanded; - for (expanded = buf.gl_pathv; *expanded != NULL; expanded++) - { - success &= parse_file(contents, *expanded, level + 1, section); - if (!success) - { - break; - } - } - } - globfree(&buf); - } -#else /* HAVE_GLOB_H */ - /* if glob(3) is not available, try to load pattern directly */ - success = parse_file(contents, pat, level + 1, section); -#endif /* HAVE_GLOB_H */ - return success; -} - -/** - * Recursivly extends "base" with "extension". - */ -static void section_extend(section_t *base, section_t *extension) -{ - enumerator_t *enumerator; - section_t *sec; - kv_t *kv; - - enumerator = array_create_enumerator(extension->sections); - while (enumerator->enumerate(enumerator, (void**)&sec)) - { - section_t *found; - if (array_bsearch(base->sections, sec->name, section_find, - &found) != -1) - { - section_extend(found, sec); - } - else - { - array_remove_at(extension->sections, enumerator); - array_insert_create(&base->sections, ARRAY_TAIL, sec); - array_sort(base->sections, section_sort, NULL); - } - } - enumerator->destroy(enumerator); - - enumerator = array_create_enumerator(extension->kv); - while (enumerator->enumerate(enumerator, (void**)&kv)) - { - kv_t *found; - if (array_bsearch(base->kv, kv->key, kv_find, &found) != -1) - { - found->value = kv->value; - } - else - { - array_remove_at(extension->kv, enumerator); - array_insert_create(&base->kv, ARRAY_TAIL, kv); - array_sort(base->kv, kv_sort, NULL); - } - } - enumerator->destroy(enumerator); -} - /** * Load settings from files matching the given file pattern. * All sections and values are added relative to "parent". * All files (even included ones) have to be loaded successfully. + * If merge is FALSE the contents of parent are replaced with the parsed + * contents, otherwise they are merged together. */ static bool load_files_internal(private_settings_t *this, section_t *parent, char *pattern, bool merge) { - char *text; - linked_list_t *contents; section_t *section; - if (pattern == NULL) - { -#ifdef STRONGSWAN_CONF - pattern = STRONGSWAN_CONF; -#else + if (pattern == NULL || !pattern[0]) + { /* TODO: Clear parent if merge is FALSE? */ return FALSE; -#endif } - contents = linked_list_create(); - section = section_create(NULL); - - if (!parse_files(contents, NULL, 0, pattern, section)) + section = settings_section_create(NULL); + if (!settings_parser_parse_file(section, pattern)) { - contents->destroy_function(contents, (void*)free); - section_destroy(section); + settings_section_destroy(section, NULL); return FALSE; } this->lock->write_lock(this->lock); - if (!merge) - { - section_purge(parent); - } - /* extend parent section */ - section_extend(parent, section); - /* move contents of loaded files to main store */ - while (contents->remove_first(contents, (void**)&text) == SUCCESS) - { - this->contents->insert_last(this->contents, text); - } + settings_section_extend(parent, section, this->contents, !merge); this->lock->unlock(this->lock); - section_destroy(section); - contents->destroy(contents); + settings_section_destroy(section, NULL); return TRUE; } @@ -1476,8 +885,8 @@ METHOD(settings_t, load_files_section, bool, METHOD(settings_t, destroy, void, private_settings_t *this) { - section_destroy(this->top); - this->contents->destroy_function(this->contents, (void*)free); + settings_section_destroy(this->top, NULL); + array_destroy_function(this->contents, (void*)free, NULL); this->lock->destroy(this->lock); free(this); } @@ -1509,8 +918,8 @@ settings_t *settings_create(char *file) .load_files_section = _load_files_section, .destroy = _destroy, }, - .top = section_create(NULL), - .contents = linked_list_create(), + .top = settings_section_create(NULL), + .contents = array_create(0, 0), .lock = rwlock_create(RWLOCK_TYPE_DEFAULT), ); diff --git a/src/libstrongswan/utils/settings.h b/src/libstrongswan/settings/settings.h similarity index 97% rename from src/libstrongswan/utils/settings.h rename to src/libstrongswan/settings/settings.h index 46403c4d3..3b87c8feb 100644 --- a/src/libstrongswan/utils/settings.h +++ b/src/libstrongswan/settings/settings.h @@ -16,7 +16,10 @@ /** * @defgroup settings settings - * @{ @ingroup utils + * @ingroup libstrongswan + * + * @defgroup settings_t settings + * @{ @ingroup settings */ #ifndef SETTINGS_H_ @@ -24,7 +27,7 @@ typedef struct settings_t settings_t; -#include "utils.h" +#include "utils/utils.h" #include "collections/enumerator.h" /** @@ -340,7 +343,9 @@ struct settings_t { /** * Load settings from a file. * - * @param file file to read settings from, NULL for default + * @note If parsing the file fails the object is still created. + * + * @param file optional file to read settings from * @return settings object */ settings_t *settings_create(char *file); diff --git a/src/libstrongswan/settings/settings_lexer.l b/src/libstrongswan/settings/settings_lexer.l new file mode 100644 index 000000000..c6546f464 --- /dev/null +++ b/src/libstrongswan/settings/settings_lexer.l @@ -0,0 +1,201 @@ +%{ +/* + * Copyright (C) 2014 Tobias Brunner + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * 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 "settings_parser.h" + +bool settings_parser_open_next_file(parser_helper_t *ctx); + +static void include_files(parser_helper_t *ctx); + +%} +%option debug +%option warn + +/* use start conditions stack */ +%option stack + +/* do not declare unneded functions */ +%option noinput noyywrap + +/* don't use global variables, and interact properly with bison */ +%option reentrant bison-bridge + +/* maintain the line number */ +%option yylineno + +/* don't generate a default rule */ +%option nodefault + +/* prefix function/variable declarations */ +%option prefix="settings_parser_" +/* don't change the name of the output file otherwise autotools has issues */ +%option outfile="lex.yy.c" + +/* type of our extra data */ +%option extra-type="parser_helper_t*" + +/* state used to scan include file patterns */ +%x inc +/* state used to scan quoted strings */ +%x str + +%% + +[\t ]*#[^\n]* /* eat comments */ +[\t ]+ /* eat whitespace */ +\n|#.*\n return NEWLINE; /* also eats comments at the end of a line */ + +"{" | +"}" | +"=" return yytext[0]; + +"include"[\t ]+/[^=] { + yyextra->string_init(yyextra); + yy_push_state(inc, yyscanner); +} + +"\"" { + yyextra->string_init(yyextra); + yy_push_state(str, yyscanner); +} + +[^#{}="\n\t ]+ { + yylval->s = strdup(yytext); + return NAME; +} + +{ + /* we allow all characters except #, } and spaces, they can be escaped */ + <> | + [#}\n\t ] { + if (*yytext) + { + switch (yytext[0]) + { + case '\n': + /* put the newline back to fix the line numbers */ + unput('\n'); + yy_set_bol(0); + break; + case '#': + case '}': + /* these are parsed outside of this start condition */ + unput(yytext[0]); + break; + } + } + include_files(yyextra); + yy_pop_state(yyscanner); + } + "\"" { /* string include */ + yy_push_state(str, yyscanner); + } + \\ { + yyextra->string_add(yyextra, yytext); + } + \\["#} ] { + yyextra->string_add(yyextra, yytext+1); + } + [^"\\#}\n\t ]+ { + yyextra->string_add(yyextra, yytext); + } +} + +{ + "\"" | + <> | + \n | + \\ { + if (!streq(yytext, "\"")) + { + if (streq(yytext, "\n")) + { /* put the newline back to fix the line numbers */ + unput('\n'); + yy_set_bol(0); + } + PARSER_DBG1(yyextra, "unterminated string detected"); + } + if (yy_top_state(yyscanner) == inc) + { /* string include */ + include_files(yyextra); + yy_pop_state(yyscanner); + yy_pop_state(yyscanner); + } + else + { + yy_pop_state(yyscanner); + yylval->s = yyextra->string_get(yyextra); + return STRING; + } + } + \\n yyextra->string_add(yyextra, "\n"); + \\r yyextra->string_add(yyextra, "\r"); + \\t yyextra->string_add(yyextra, "\t"); + \\b yyextra->string_add(yyextra, "\b"); + \\f yyextra->string_add(yyextra, "\f"); + \\(.|\n) { + yyextra->string_add(yyextra, yytext+1); + } + [^\\\n"]+ { + yyextra->string_add(yyextra, yytext); + } +} + +<> { + settings_parser_pop_buffer_state(yyscanner); + if (!settings_parser_open_next_file(yyextra) && !YY_CURRENT_BUFFER) + { + yyterminate(); + } +} + +%% + +/** + * Open the next file, if any is queued and readable, otherwise returns FALSE. + */ +bool settings_parser_open_next_file(parser_helper_t *ctx) +{ + FILE *file; + + file = ctx->file_next(ctx); + if (!file) + { + return FALSE; + } + + settings_parser_set_in(file, ctx->scanner); + settings_parser_push_buffer_state( + settings_parser__create_buffer(file, YY_BUF_SIZE, + ctx->scanner), ctx->scanner); + return TRUE; +} + +/** + * Assumes that the file pattern to include is currently stored as string on + * the helper object. + */ +static void include_files(parser_helper_t *ctx) +{ + char *pattern = ctx->string_get(ctx); + + ctx->file_include(ctx, pattern); + free(pattern); + + settings_parser_open_next_file(ctx); +} diff --git a/src/libstrongswan/settings/settings_parser.y b/src/libstrongswan/settings/settings_parser.y new file mode 100644 index 000000000..b79faf6b7 --- /dev/null +++ b/src/libstrongswan/settings/settings_parser.y @@ -0,0 +1,290 @@ +%{ +/* + * Copyright (C) 2014 Tobias Brunner + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * 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 /* for asprintf() */ +#include + +#include +#include +#include +#include + +#include "settings_parser.h" + +#define YYDEBUG 1 + +/** + * Defined by the lexer + */ +int settings_parser_lex(YYSTYPE *lvalp, void *scanner); +int settings_parser_lex_init_extra(parser_helper_t *extra, void *scanner); +int settings_parser_lex_destroy(void *scanner); +int settings_parser_set_in(FILE *in, void *scanner); +void settings_parser_set_debug(int debug, void *scanner); +char *settings_parser_get_text(void *scanner); +int settings_parser_get_leng(void *scanner); +int settings_parser_get_lineno(void *scanner); +/* Custom functions in lexer */ +bool settings_parser_open_next_file(parser_helper_t *ctx); + +/** + * Forward declarations + */ +static void settings_parser_error(parser_helper_t *ctx, const char *s); +static section_t *push_section(parser_helper_t *ctx, char *name); +static section_t *pop_section(parser_helper_t *ctx); +static void add_section(parser_helper_t *ctx, section_t *section); +static void add_setting(parser_helper_t *ctx, kv_t *kv); + +/** + * Make sure to call lexer with the proper context + */ +#undef yylex +static int yylex(YYSTYPE *lvalp, parser_helper_t *ctx) +{ + return settings_parser_lex(lvalp, ctx->scanner); +} + +%} +%debug + +/* generate verbose error messages */ +%error-verbose +/* generate a reentrant parser */ +%define api.pure +/* prefix function/variable declarations */ +%name-prefix "settings_parser_" + +/* interact properly with the reentrant lexer */ +%lex-param {parser_helper_t *ctx} +%parse-param {parser_helper_t *ctx} + +/* types for terminal symbols... (can't use the typedef'd types) */ +%union { + char *s; + struct section_t *sec; + struct kv_t *kv; +} +%token NAME STRING +%token NEWLINE + +/* ...and other symbols */ +%type value valuepart +%type section_start section +%type setting + +/* properly destroy string tokens that are strdup()ed on error */ +%destructor { free($$); } NAME STRING value valuepart +/* properly destroy parse results on error */ +%destructor { pop_section(ctx); settings_section_destroy($$, NULL); } section_start section +%destructor { settings_kv_destroy($$, NULL); } setting + +/* there are two shift/reduce conflicts because of the "NAME = NAME" and + * "NAME {" ambiguity, and the "NAME =" rule) */ +%expect 2 + +%% + +/** + * strongswan.conf grammar rules + */ +statements: + /* empty */ + | statements NEWLINE + | statements statement + ; + +statement: + section + { + add_section(ctx, $section); + } + | setting + { + add_setting(ctx, $setting); + } + ; + +section: + section_start statements '}' + { + pop_section(ctx); + $$ = $section_start; + } + ; + +section_start: + NAME '{' + { + $$ = push_section(ctx, $NAME); + } + | + NAME NEWLINE '{' + { + $$ = push_section(ctx, $NAME); + } + ; + +setting: + NAME '=' value + { + $$ = settings_kv_create($NAME, $value); + } + | + NAME '=' + { + $$ = settings_kv_create($NAME, NULL); + } + ; + +value: + valuepart + | value valuepart + { /* just put a single space between them, use strings for more */ + if (asprintf(&$$, "%s %s", $1, $2) < 0) + { + free($1); + free($2); + YYERROR; + } + free($1); + free($2); + } + ; + +valuepart: + NAME + | STRING + ; + +%% + +/** + * Referenced by the generated parser + */ +static void settings_parser_error(parser_helper_t *ctx, const char *s) +{ + char *text = settings_parser_get_text(ctx->scanner); + int len = settings_parser_get_leng(ctx->scanner); + + if (len && text[len-1] == '\n') + { /* cut off newline at the end to avoid muti-line log messages */ + len--; + } + PARSER_DBG1(ctx, "%s [%.*s]", s, len, text); +} + +/** + * Create a section and push it to the stack (the name is adopted), returns + * the created section + */ +static section_t *push_section(parser_helper_t *ctx, char *name) +{ + array_t *sections = (array_t*)ctx->context; + section_t *section; + + section = settings_section_create(name); + array_insert(sections, ARRAY_TAIL, section); + return section; +} + +/** + * Removes the top section of the stack and returns it + */ +static section_t *pop_section(parser_helper_t *ctx) +{ + array_t *sections = (array_t*)ctx->context; + section_t *section; + + array_remove(sections, ARRAY_TAIL, §ion); + return section; +} + +/** + * Adds the given section to the section on top of the stack + */ +static void add_section(parser_helper_t *ctx, section_t *section) +{ + array_t *sections = (array_t*)ctx->context; + section_t *parent; + + array_get(sections, ARRAY_TAIL, &parent); + settings_section_add(parent, section, NULL); +} + +/** + * Adds the given key/value pair to the section on top of the stack + */ +static void add_setting(parser_helper_t *ctx, kv_t *kv) +{ + array_t *sections = (array_t*)ctx->context; + section_t *section; + + array_get(sections, ARRAY_TAIL, §ion); + settings_kv_add(section, kv, NULL); +} + +/** + * Parse the given file and add all sections and key/value pairs to the + * given section. + */ +bool settings_parser_parse_file(section_t *root, char *name) +{ + parser_helper_t *helper; + array_t *sections = NULL; + bool success = FALSE; + + array_insert_create(§ions, ARRAY_TAIL, root); + helper = parser_helper_create(sections); + helper->get_lineno = settings_parser_get_lineno; + if (settings_parser_lex_init_extra(helper, &helper->scanner) != 0) + { + helper->destroy(helper); + array_destroy(sections); + return FALSE; + } + helper->file_include(helper, name); + if (!settings_parser_open_next_file(helper)) + { +#ifdef STRONGSWAN_CONF + if (streq(name, STRONGSWAN_CONF)) + { + DBG2(DBG_CFG, "failed to open config file '%s'", name); + } + else +#endif + { + DBG1(DBG_CFG, "failed to open config file '%s'", name); + } + } + else + { + if (getenv("DEBUG_SETTINGS_PARSER")) + { + yydebug = 1; + settings_parser_set_debug(1, helper->scanner); + } + success = yyparse(helper) == 0; + if (!success) + { + DBG1(DBG_CFG, "invalid config file '%s'", name); + } + } + array_destroy(sections); + settings_parser_lex_destroy(helper->scanner); + helper->destroy(helper); + return success; +} diff --git a/src/libstrongswan/settings/settings_types.c b/src/libstrongswan/settings/settings_types.c new file mode 100644 index 000000000..125676237 --- /dev/null +++ b/src/libstrongswan/settings/settings_types.c @@ -0,0 +1,321 @@ +/* + * Copyright (C) 2010-2014 Tobias Brunner + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * 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 "settings_types.h" + +/* + * Described in header + */ +kv_t *settings_kv_create(char *key, char *value) +{ + kv_t *this; + + INIT(this, + .key = key, + .value = value, + ); + return this; +} + +/* + * Described in header + */ +void settings_kv_destroy(kv_t *this, array_t *contents) +{ + free(this->key); + if (contents && this->value) + { + array_insert(contents, ARRAY_TAIL, this->value); + } + else + { + free(this->value); + } + free(this); +} + +/* + * Described in header + */ +section_t *settings_section_create(char *name) +{ + section_t *this; + + INIT(this, + .name = name, + ); + return this; +} + +static void section_destroy(section_t *section, int idx, array_t *contents) +{ + settings_section_destroy(section, contents); +} + +static void kv_destroy(kv_t *kv, int idx, array_t *contents) +{ + settings_kv_destroy(kv, contents); +} + +/* + * Described in header + */ +void settings_section_destroy(section_t *this, array_t *contents) +{ + array_destroy_function(this->sections, (void*)section_destroy, contents); + array_destroy(this->sections_order); + array_destroy_function(this->kv, (void*)kv_destroy, contents); + array_destroy(this->kv_order); + array_destroy(this->fallbacks); + free(this->name); + free(this); +} + +/* + * Described in header + */ +void settings_kv_set(kv_t *kv, char *value, array_t *contents) +{ + if (value && kv->value && streq(value, kv->value)) + { /* no update required */ + free(value); + return; + } + + /* if the new value was shorter we could overwrite the existing one but that + * could lead to reads of partially updated values from other threads that + * have a pointer to the existing value, so we replace it anyway */ + if (kv->value && contents) + { + array_insert(contents, ARRAY_TAIL, kv->value); + } + else + { + free(kv->value); + } + kv->value = value; +} + +/* + * Described in header + */ +void settings_kv_add(section_t *section, kv_t *kv, array_t *contents) +{ + kv_t *found; + + if (array_bsearch(section->kv, kv->key, settings_kv_find, &found) == -1) + { + array_insert_create(§ion->kv, ARRAY_TAIL, kv); + array_sort(section->kv, settings_kv_sort, NULL); + array_insert_create(§ion->kv_order, ARRAY_TAIL, kv); + } + else + { + settings_kv_set(found, kv->value, contents); + kv->value = NULL; + settings_kv_destroy(kv, NULL); + } +} + +/* + * Add a section to the given parent, optionally remove settings/subsections + * not found when extending an existing section + */ +static void add_section(section_t *parent, section_t *section, + array_t *contents, bool purge) +{ + section_t *found; + + if (array_bsearch(parent->sections, section->name, settings_section_find, + &found) == -1) + { + array_insert_create(&parent->sections, ARRAY_TAIL, section); + array_sort(parent->sections, settings_section_sort, NULL); + array_insert_create(&parent->sections_order, ARRAY_TAIL, section); + } + else + { + settings_section_extend(found, section, contents, purge); + settings_section_destroy(section, contents); + } +} + +/* + * Described in header + */ +void settings_section_add(section_t *parent, section_t *section, + array_t *contents) +{ + add_section(parent, section, contents, FALSE); +} + +/** + * Purge contents of a section, returns TRUE if section can be safely removed. + */ +static bool section_purge(section_t *this, array_t *contents) +{ + section_t *current; + int i, idx; + + array_destroy_function(this->kv, (void*)kv_destroy, contents); + this->kv = NULL; + array_destroy(this->kv_order); + this->kv_order = NULL; + /* we ensure sections used as fallback, or configured with fallbacks (or + * having any such subsections) are not removed */ + for (i = array_count(this->sections_order) - 1; i >= 0; i--) + { + array_get(this->sections, i, ¤t); + if (section_purge(current, contents)) + { + array_remove(this->sections_order, i, NULL); + idx = array_bsearch(this->sections, current->name, + settings_section_find, NULL); + array_remove(this->sections, idx, NULL); + settings_section_destroy(current, contents); + } + } + return !this->fallbacks && !array_count(this->sections); +} + +/* + * Described in header + */ +void settings_section_extend(section_t *base, section_t *extension, + array_t *contents, bool purge) +{ + enumerator_t *enumerator; + section_t *section; + kv_t *kv; + array_t *sections = NULL, *kvs = NULL; + int idx; + + if (purge) + { /* remove sections and settings in base not found in extension, the + * others are removed too (from the _order list) so they can be inserted + * in the order found in extension */ + enumerator = array_create_enumerator(base->sections_order); + while (enumerator->enumerate(enumerator, (void**)§ion)) + { + if (array_bsearch(extension->sections, section->name, + settings_section_find, NULL) == -1) + { + idx = array_bsearch(base->sections, section->name, + settings_section_find, NULL); + if (section_purge(section, contents)) + { /* only remove them if we can purge them */ + array_remove(base->sections, idx, NULL); + array_remove_at(base->sections_order, enumerator); + settings_section_destroy(section, contents); + } + } + else + { + array_remove_at(base->sections_order, enumerator); + array_insert_create(§ions, ARRAY_TAIL, section); + array_sort(sections, settings_section_sort, NULL); + } + } + enumerator->destroy(enumerator); + + while (array_remove(base->kv_order, 0, &kv)) + { + if (array_bsearch(extension->kv, kv->key, settings_kv_find, + NULL) == -1) + { + idx = array_bsearch(base->kv, kv->key, settings_kv_find, NULL); + array_remove(base->kv, idx, NULL); + settings_kv_destroy(kv, contents); + } + else + { + array_insert_create(&kvs, ARRAY_TAIL, kv); + array_sort(kvs, settings_kv_sort, NULL); + } + } + } + + while (array_remove(extension->sections_order, 0, §ion)) + { + idx = array_bsearch(sections, section->name, + settings_section_find, NULL); + if (idx != -1) + { + section_t *existing; + + array_remove(sections, idx, &existing); + array_insert(base->sections_order, ARRAY_TAIL, existing); + } + idx = array_bsearch(extension->sections, section->name, + settings_section_find, NULL); + array_remove(extension->sections, idx, NULL); + add_section(base, section, contents, purge); + } + + while (array_remove(extension->kv_order, 0, &kv)) + { + idx = array_bsearch(kvs, kv->key, settings_kv_find, NULL); + if (idx != -1) + { + kv_t *existing; + + array_remove(kvs, idx, &existing); + array_insert(base->kv_order, ARRAY_TAIL, existing); + } + idx = array_bsearch(extension->kv, kv->key, settings_kv_find, NULL); + array_remove(extension->kv, idx, NULL); + settings_kv_add(base, kv, contents); + } + array_destroy(sections); + array_destroy(kvs); +} + +/* + * Described in header + */ +int settings_section_find(const void *a, const void *b) +{ + const char *key = a; + const section_t *item = b; + return strcmp(key, item->name); +} + +/* + * Described in header + */ +int settings_section_sort(const void *a, const void *b, void *user) +{ + const section_t *sa = a, *sb = b; + return strcmp(sa->name, sb->name); +} + +/* + * Described in header + */ +int settings_kv_find(const void *a, const void *b) +{ + const char *key = a; + const kv_t *item = b; + return strcmp(key, item->key); +} + +/* + * Described in header + */ +int settings_kv_sort(const void *a, const void *b, void *user) +{ + const kv_t *kva = a, *kvb = b; + return strcmp(kva->key, kvb->key); +} diff --git a/src/libstrongswan/settings/settings_types.h b/src/libstrongswan/settings/settings_types.h new file mode 100644 index 000000000..67299d8e7 --- /dev/null +++ b/src/libstrongswan/settings/settings_types.h @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2010-2014 Tobias Brunner + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * 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. + */ + +/** + * Internal data types and functions shared between the parser and t. + * + * @defgroup settings_types settings_types + * @{ @ingroup settings + */ + +#ifndef SETTINGS_TYPES_H_ +#define SETTINGS_TYPES_H_ + +typedef struct kv_t kv_t; +typedef struct section_t section_t; + +#include "collections/array.h" + +/** + * Key/value pair. + */ +struct kv_t { + + /** + * Key string, relative, not the full name. + */ + char *key; + + /** + * Value as string. + */ + char *value; +}; + +/** + * Section containing subsections and key value pairs. + */ +struct section_t { + + /** + * Name of the section. + */ + char *name; + + /** + * Fallback sections, as section_t. + */ + array_t *fallbacks; + + /** + * Subsections, as section_t. + */ + array_t *sections; + + /** + * Subsections in original order, as section_t (pointer to obj in sections). + */ + array_t *sections_order; + + /** + * Key value pairs, as kv_t. + */ + array_t *kv; + + /** + * Key value pairs in original order, as kv_t (pointer to obj in kv). + */ + array_t *kv_order; +}; + +/** + * Create a key/value pair. + * + * @param key key (gets adopted) + * @param value value (gets adopted) + * @return allocated key/value pair + */ +kv_t *settings_kv_create(char *key, char *value); + +/** + * Destroy a key/value pair. + * + * @param this key/value pair to destroy + * @param contents optional array to store the value in + */ +void settings_kv_destroy(kv_t *this, array_t *contents); + +/** + * Set the value of the given key/value pair. + * + * @param kv key/value pair + * @param value new value (gets adopted), may be NULL + * @param contents optional array to store replaced values in + */ +void settings_kv_set(kv_t *kv, char *value, array_t *contents); + +/** + * Add the given key/value pair to the given section. + * + * @param section section to add pair to + * @param kv key/value pair to add (gets adopted) + * @param contents optional array to store replaced values in + */ +void settings_kv_add(section_t *section, kv_t *kv, array_t *contents); + +/** + * Create a section with the given name. + * + * @param name name (gets adopted) + * @return allocated section + */ +section_t *settings_section_create(char *name); + +/** + * Destroy a section. + * + * @param this section to destroy + * @param contents optional array to store values of removed key/value pairs + */ +void settings_section_destroy(section_t *this, array_t *contents); + +/** + * Add the given section to the given parent section. + * + * @param parent section to add section to + * @param section section to add (gets adopted) + * @param contents optional array to store replaced values in + */ +void settings_section_add(section_t *parent, section_t *section, + array_t *contents); + +/** + * Extend the first section with the values and sub-sections of the second + * section, from where they are consequently removed. + * + * @param base base section to extend + * @param extension section whose data is extracted + * @param contents optional array to store replaced values in + * @param purge TRUE to remove settings and sections not found in the + * extension (unless (sub-)sections have/are fallbacks) + */ +void settings_section_extend(section_t *base, section_t *extension, + array_t *contents, bool purge); + +/** + * Callback to find a section by name + */ +int settings_section_find(const void *a, const void *b); + +/** + * Callback to sort sections by name + */ +int settings_section_sort(const void *a, const void *b, void *user); + +/** + * Callback to find a key/value pair by key + */ +int settings_kv_find(const void *a, const void *b); + +/** + * Callback to sort kv pairs by key + */ +int settings_kv_sort(const void *a, const void *b, void *user); + +#endif /** SETTINGS_TYPES_H_ @}*/ diff --git a/src/libstrongswan/tests/suites/test_settings.c b/src/libstrongswan/tests/suites/test_settings.c index 096465191..e504b95a1 100644 --- a/src/libstrongswan/tests/suites/test_settings.c +++ b/src/libstrongswan/tests/suites/test_settings.c @@ -17,7 +17,7 @@ #include -#include +#include #include #include #include @@ -39,6 +39,7 @@ START_SETUP(setup_base_config) " # this gets overridden below\n" " key2 = val2\n" " none = \n" + " empty = \"\"\n" " sub1 {\n" " key = value\n" " key2 = value2\n" @@ -51,7 +52,8 @@ START_SETUP(setup_base_config) " sub% {\n" " id = %any\n" " }\n" - " key2 = with spaces\n" + " key2 = with space\n" + " key3 = \"string with\\nnewline\"\n" "}\n" "out = side\n" "other {\n" @@ -79,7 +81,9 @@ START_TEST(test_get_str) verify_string("val1", "main.key1"); verify_string("val1", "main..key1"); verify_string("val1", ".main.key1"); - verify_string("with spaces", "main.key2"); + verify_string("", "main.empty"); + verify_string("with space", "main.key2"); + verify_string("string with\nnewline", "main.key3"); verify_string("value", "main.sub1.key"); verify_string("value2", "main.sub1.key2"); verify_string("bar", "main.sub1.subsub.foo"); @@ -88,10 +92,8 @@ START_TEST(test_get_str) verify_string("side", "out"); verify_string("other val", "other.key1"); - /* FIXME: should this rather be undefined i.e. return the default value? */ - verify_string("", "main.none"); - - verify_null("main.key3"); + verify_null("main.none"); + verify_null("main.key4"); verify_null("other.sub"); } END_TEST @@ -125,16 +127,35 @@ START_TEST(test_get_str_printf) * probably document it at least */ verify_null("main.%s%u.key%d", "sub", 1, 2); - verify_null("%s.%s%d", "main", "key", 3); + verify_null("%s.%s%d", "main", "key", 4); } END_TEST START_TEST(test_set_str) { + char *val1, *val2; + + val1 = settings->get_str(settings, "main.key1", NULL); + ck_assert_str_eq("val1", val1); settings->set_str(settings, "main.key1", "val"); verify_string("val", "main.key1"); + /* the pointer we got before is still valid */ + ck_assert_str_eq("val1", val1); + + val2 = settings->get_str(settings, "main.key1", NULL); + ck_assert_str_eq("val", val2); settings->set_str(settings, "main.key1", "longer value"); verify_string("longer value", "main.key1"); + /* the pointers we got before are still valid */ + ck_assert_str_eq("val1", val1); + ck_assert_str_eq("val", val2); + + val1 = settings->get_str(settings, "main.key1", NULL); + settings->set_str(settings, "main.key1", "longer value"); + val2 = settings->get_str(settings, "main.key1", NULL); + /* setting the same string should should get us the same pointer */ + ck_assert(val1 == val2); + settings->set_str(settings, "main", "main val"); verify_string("main val", "main"); settings->set_str(settings, "main.sub1.new", "added"); @@ -183,6 +204,7 @@ START_SETUP(setup_bool_config) " key7 = disabled\n" " key8 = 0\n" " key9 = 5\n" + " empty = \"\"\n" " none = \n" " foo = bar\n" "}")); @@ -203,6 +225,8 @@ START_TEST(test_get_bool) verify_bool(FALSE, TRUE, "main.key7"); verify_bool(FALSE, TRUE, "main.key8"); + verify_bool(FALSE, FALSE, "main.empty"); + verify_bool(TRUE, TRUE, "main.empty"); verify_bool(FALSE, FALSE, "main.none"); verify_bool(TRUE, TRUE, "main.none"); verify_bool(FALSE, FALSE, "main.foo"); @@ -240,6 +264,7 @@ START_SETUP(setup_int_config) " # gets cut off\n" " key2 = 5.5\n" " key3 = -42\n" + " empty = \"\"\n" " none = \n" " foo1 = bar\n" " foo2 = bar13\n" @@ -257,8 +282,10 @@ START_TEST(test_get_int) verify_int(5, 0, "main.key2"); verify_int(-42, 0, "main.key3"); + verify_int(0, 11, "main.empty"); + verify_int(11, 11, "main.none"); + /* FIXME: do we want this behavior? */ - verify_int(0, 11, "main.none"); verify_int(0, 11, "main.foo1"); verify_int(0, 11, "main.foo2"); verify_int(13, 11, "main.foo3"); @@ -291,6 +318,7 @@ START_SETUP(setup_double_config) " key2 = 5.5\n" " key3 = -42\n" " key4 = -42.5\n" + " empty = \"\"\n" " none = \n" " foo1 = bar\n" " foo2 = bar13.5\n" @@ -309,8 +337,10 @@ START_TEST(test_get_double) verify_double(-42, 0, "main.key3"); verify_double(-42.5, 0, "main.key4"); + verify_double(0, 11.5, "main.empty"); + verify_double(11.5, 11.5, "main.none"); + /* FIXME: do we want this behavior? */ - verify_double(0, 11.5, "main.none"); verify_double(0, 11.5, "main.foo1"); verify_double(0, 11.5, "main.foo2"); verify_double(13.5, 11.5, "main.foo3"); @@ -345,6 +375,7 @@ START_SETUP(setup_time_config) " key2 = 5m\n" " key3 = 5h\n" " key4 = 5d\n" + " empty = \"\"\n" " none = \n" " foo1 = bar\n" " foo2 = bar13\n" @@ -363,8 +394,10 @@ START_TEST(test_get_time) verify_time(18000, 0, "main.key3"); verify_time(432000, 0, "main.key4"); + verify_time(0, 11, "main.empty"); + verify_time(11, 11, "main.none"); + /* FIXME: do we want this behavior? */ - verify_time(0, 11, "main.none"); verify_time(0, 11, "main.foo1"); verify_time(0, 11, "main.foo2"); verify_time(13, 11, "main.foo3"); @@ -387,37 +420,21 @@ START_TEST(test_set_time) } END_TEST -static bool verify_section(linked_list_t *verifier, char *section) -{ - enumerator_t *enumerator; - char *current; - bool result = FALSE; - - enumerator = verifier->create_enumerator(verifier); - while (enumerator->enumerate(enumerator, ¤t)) - { - if (streq(current, section)) - { - verifier->remove_at(verifier, enumerator); - result = TRUE; - break; - } - } - enumerator->destroy(enumerator); - return result; -} - static void verify_sections(linked_list_t *verifier, char *parent) { - enumerator_t *enumerator; - char *section; + enumerator_t *enumerator, *ver; + char *section, *current; enumerator = settings->create_section_enumerator(settings, parent); - while (enumerator->enumerate(enumerator, §ion)) + ver = verifier->create_enumerator(verifier); + while (enumerator->enumerate(enumerator, §ion) && + ver->enumerate(ver, ¤t)) { - ck_assert(verify_section(verifier, section)); + ck_assert_str_eq(section, current); + verifier->remove_at(verifier, ver); } enumerator->destroy(enumerator); + ver->destroy(ver); ck_assert_int_eq(0, verifier->get_count(verifier)); verifier->destroy(verifier); } @@ -429,8 +446,8 @@ START_TEST(test_section_enumerator) verifier = linked_list_create_with_items("sub1", "sub%", NULL); verify_sections(verifier, "main"); - settings->set_str(settings, "main.sub2.new", "added"); - verifier = linked_list_create_with_items("sub1", "sub%", "sub2", NULL); + settings->set_str(settings, "main.sub0.new", "added"); + verifier = linked_list_create_with_items("sub1", "sub%", "sub0", NULL); verify_sections(verifier, "main"); verifier = linked_list_create_with_items("subsub", NULL); @@ -447,44 +464,27 @@ START_TEST(test_section_enumerator) } END_TEST -static bool verify_key_value(linked_list_t *keys, linked_list_t *values, - char *key, char *value) -{ - enumerator_t *enum_keys, *enum_values; - char *current_key, *current_value; - bool result = FALSE; - - enum_keys = keys->create_enumerator(keys); - enum_values = values->create_enumerator(values); - while (enum_keys->enumerate(enum_keys, ¤t_key) && - enum_values->enumerate(enum_values, ¤t_value)) - { - if (streq(current_key, key)) - { - ck_assert_str_eq(current_value, value); - keys->remove_at(keys, enum_keys); - values->remove_at(values, enum_values); - result = TRUE; - break; - } - } - enum_keys->destroy(enum_keys); - enum_values->destroy(enum_values); - return result; -} - static void verify_key_values(linked_list_t *keys, linked_list_t *values, char *parent) { - enumerator_t *enumerator; - char *key, *value; + enumerator_t *enumerator, *enum_keys, *enum_values; + char *key, *value, *current_key, *current_value; enumerator = settings->create_key_value_enumerator(settings, parent); - while (enumerator->enumerate(enumerator, &key, &value)) + enum_keys = keys->create_enumerator(keys); + enum_values = values->create_enumerator(values); + while (enumerator->enumerate(enumerator, &key, &value) && + enum_keys->enumerate(enum_keys, ¤t_key) && + enum_values->enumerate(enum_values, ¤t_value)) { - ck_assert(verify_key_value(keys, values, key, value)); + ck_assert_str_eq(current_key, key); + ck_assert_str_eq(current_value, value); + keys->remove_at(keys, enum_keys); + values->remove_at(values, enum_values); } enumerator->destroy(enumerator); + enum_keys->destroy(enum_keys); + enum_values->destroy(enum_values); ck_assert_int_eq(0, keys->get_count(keys)); keys->destroy(keys); values->destroy(values); @@ -494,8 +494,8 @@ START_TEST(test_key_value_enumerator) { linked_list_t *keys, *values; - keys = linked_list_create_with_items("key1", "key2", "none", NULL); - values = linked_list_create_with_items("val1", "with spaces", "", NULL); + keys = linked_list_create_with_items("key1", "key2", "empty", "key3", NULL); + values = linked_list_create_with_items("val1", "with space", "", "string with\nnewline", NULL); verify_key_values(keys, values, "main"); keys = linked_list_create_with_items("key", "key2", "subsub", NULL); @@ -531,6 +531,7 @@ START_SETUP(setup_include_config) "main {\n" " key1 = n1\n" " key2 = n2\n" + " key3 = val3\n" " none = \n" " sub1 {\n" " key3 = value\n" @@ -563,13 +564,15 @@ static void verify_include() { verify_string("n1", "main.key1"); verify_string("v2", "main.key2"); - verify_string("", "main.none"); + verify_string("val3", "main.key3"); verify_string("val", "main.sub1.key"); verify_string("v2", "main.sub1.key2"); verify_string("val", "main.sub1.sub1.key"); verify_string("value", "main.sub1.key3"); verify_string("value", "main.sub1.include"); verify_string("val3", "main.sub2.sub3"); + + verify_null("main.none"); } START_TEST(test_include) @@ -580,13 +583,13 @@ START_TEST(test_include) " key2 = val2\n" " none = x\n" " sub1 {\n" + " include this/does/not/exist.conf\n" " include = value\n" " key2 = value2\n" " include " include2 "\n" " }\n" "}\n" - "# currently there must be a newline after include statements\n" - "include " include1 "\n"); + "include " include1); create_settings(contents); verify_include(); @@ -599,6 +602,7 @@ START_TEST(test_load_files) "main {\n" " key1 = val1\n" " key2 = val2\n" + " key3 = val3\n" " none = x\n" " sub1 {\n" " include = value\n" @@ -608,9 +612,35 @@ START_TEST(test_load_files) " }\n" " }\n" "}"); + char *val1, *val2, *val3; create_settings(contents); + val1 = settings->get_str(settings, "main.key1", NULL); + val2 = settings->get_str(settings, "main.sub1.key2", NULL); + /* loading the same file twice should not change anything, with... */ + ck_assert(settings->load_files(settings, path, TRUE)); + ck_assert(val1 == settings->get_str(settings, "main.key1", NULL)); + ck_assert(val2 == settings->get_str(settings, "main.sub1.key2", NULL)); + /* ...or without merging */ + ck_assert(settings->load_files(settings, path, FALSE)); + ck_assert(val1 == settings->get_str(settings, "main.key1", NULL)); + ck_assert(val2 == settings->get_str(settings, "main.sub1.key2", NULL)); + + val1 = settings->get_str(settings, "main.key2", NULL); + val2 = settings->get_str(settings, "main.key3", NULL); + val3 = settings->get_str(settings, "main.none", NULL); + /* only pointers for modified settings should change, but still be valid */ + ck_assert(settings->load_files(settings, include1, FALSE)); + ck_assert(val1 != settings->get_str(settings, "main.key2", NULL)); + ck_assert_str_eq(val1, "val2"); + ck_assert(val2 == settings->get_str(settings, "main.key3", NULL)); + ck_assert(val3 != settings->get_str(settings, "main.none", NULL)); + ck_assert_str_eq(val3, "x"); + + settings->destroy(settings); + create_settings(contents); + ck_assert(settings->load_files(settings, include1, TRUE)); verify_include(); @@ -641,11 +671,11 @@ START_TEST(test_load_files_section) ck_assert(settings->load_files_section(settings, include2, TRUE, "main.sub1")); verify_include(); - /* non existing files are no failure */ - ck_assert(settings->load_files_section(settings, include1".conf", TRUE, "")); + /* non existing files are a failure here */ + ck_assert(!settings->load_files_section(settings, include1".conf", TRUE, "")); verify_include(); - /* unreadable files are */ + /* unreadable files are too */ ck_assert(chunk_write(contents, include1".no", 0444, TRUE)); ck_assert(!settings->load_files_section(settings, include1".no", TRUE, "")); unlink(include1".no"); @@ -664,6 +694,87 @@ START_TEST(test_load_files_section) } END_TEST +START_TEST(test_order_kv) +{ + chunk_t base = chunk_from_str( + "main {\n" + " key1 = val1\n" + " key2 = val2\n" + " key3 = val3\n" + "}"); + chunk_t include = chunk_from_str( + "main {\n" + " key0 = val0\n" + " key3 = val3\n" + " key1 = val1\n" + "}"); + linked_list_t *keys, *values; + + create_settings(base); + ck_assert(chunk_write(include, include1, 0022, TRUE)); + + keys = linked_list_create_with_items("key1", "key2", "key3", NULL); + values = linked_list_create_with_items("val1", "val2", "val3", NULL); + verify_key_values(keys, values, "main"); + + /* the original order is maintained if the settings are merged */ + ck_assert(settings->load_files(settings, include1, TRUE)); + keys = linked_list_create_with_items("key1", "key2", "key3", "key0", NULL); + values = linked_list_create_with_items("val1", "val2", "val3", "val0", NULL); + verify_key_values(keys, values, "main"); + + /* but the new order is adopted if the settings are replaced */ + ck_assert(settings->load_files(settings, include1, FALSE)); + keys = linked_list_create_with_items("key0", "key3", "key1", NULL); + values = linked_list_create_with_items("val0", "val3", "val1", NULL); + verify_key_values(keys, values, "main"); + + unlink(include1); +} +END_TEST + +START_TEST(test_order_section) +{ + chunk_t base = chunk_from_str( + "main {\n" + " sub1 {\n" + " }\n" + " sub2 {\n" + " }\n" + " sub3 {\n" + " }\n" + "}"); + chunk_t include = chunk_from_str( + "main {\n" + " sub0 {\n" + " }\n" + " sub3 {\n" + " }\n" + " sub1 {\n" + " }\n" + "}"); + linked_list_t *sections; + + create_settings(base); + ck_assert(chunk_write(include, include1, 0022, TRUE)); + + sections = linked_list_create_with_items("sub1", "sub2", "sub3", NULL); + verify_sections(sections, "main"); + + /* the original order is maintained if the settings are merged */ + ck_assert(settings->load_files(settings, include1, TRUE)); + sections = linked_list_create_with_items("sub1", "sub2", "sub3", "sub0", NULL); + verify_sections(sections, "main"); + + /* but the new order is adopted if the settings are replaced */ + ck_assert(settings->load_files(settings, include1, FALSE)); + sections = linked_list_create_with_items("sub0", "sub3", "sub1", NULL); + verify_sections(sections, "main"); + + unlink(include1); +} +END_TEST + START_SETUP(setup_fallback_config) { create_settings(chunk_from_str( @@ -781,57 +892,85 @@ START_TEST(test_add_fallback_printf) } END_TEST -START_SETUP(setup_invalid_config) +START_SETUP(setup_string_config) { create_settings(chunk_from_str( - "# section without name\n" - "{\n" - " key1 = val1\n" - "}\n" - "main {\n" - " key2 = val2\n" - " # value without key\n" - " = val3\n" - " key4 = val4\n" - " # key without value does not change it\n" - " key4\n" - " # subsection without name\n" - " {\n" - " key5 = val5\n" - " }\n" - " # empty include pattern\n" - " include\n" - " key6 = val6\n" - "}")); + "string = \" with accurate\twhitespace\"\n" + "special = \"all { special } characters # can be used.\"\n" + "unterminated = \"is fine\n" + "but = produces a warning\n" + "newlines = \"can either be encoded\\nor\\\n" + "escaped\"\n" + "quotes = \"\\\"and\\\" slashes \\\\ can \\\\ be\" # escaped too\n" + "multiple = \"strings\" are \"combined\"\n" + )); } END_SETUP -START_TEST(test_invalid) +START_TEST(test_strings) +{ + verify_string(" with accurate\twhitespace", "string"); + verify_string("all { special } characters # can be used.", "special"); + verify_string("is fine", "unterminated"); + verify_string("produces a warning", "but"); + verify_string("can either be encoded\nor\nescaped", "newlines"); + verify_string("\"and\" slashes \\ can \\ be", "quotes"); + verify_string("strings are combined", "multiple"); +} +END_TEST + +START_TEST(test_valid) { - linked_list_t *keys, *values; chunk_t contents; - verify_null("key1"); - verify_null(".key1"); - verify_null("%s.key1", ""); - verify_string("val2", "main.key2"); - verify_string("val4", "main.key4"); - verify_null("main..key5"); - verify_string("val6", "main.key6"); - - keys = linked_list_create_with_items("main", NULL); - verify_sections(keys, ""); - - keys = linked_list_create_with_items(NULL); - verify_sections(keys, "main"); - - keys = linked_list_create_with_items("key2", "key4", "key6", NULL); - values = linked_list_create_with_items("val2", "val4", "val6", NULL); - verify_key_values(keys, values, "main"); - - /* FIXME: we should probably fix this */ contents = chunk_from_str( - "requires = newline"); + "single = value"); + ck_assert(chunk_write(contents, path, 0022, TRUE)); + ck_assert(settings->load_files(settings, path, FALSE)); + verify_string("value", "single"); + + contents = chunk_from_str( + "singleline { single = value }"); + ck_assert(chunk_write(contents, path, 0022, TRUE)); + ck_assert(settings->load_files(settings, path, FALSE)); + verify_string("value", "singleline.single"); + + contents = chunk_from_str( + "singleline { sub { sub1 = val1 } single = value }"); + ck_assert(chunk_write(contents, path, 0022, TRUE)); + ck_assert(settings->load_files(settings, path, FALSE)); + verify_string("val1", "singleline.sub.sub1"); + + contents = chunk_from_str( + "newline\n { single = value }"); + ck_assert(chunk_write(contents, path, 0022, TRUE)); + ck_assert(settings->load_files(settings, path, FALSE)); + verify_string("value", "newline.single"); + + contents = chunk_from_str( + "section {\n" + " include # without pattern produces a warning, but is fine\n" + "}\n"); + ck_assert(chunk_write(contents, path, 0022, TRUE)); + ck_assert(settings->load_files(settings, path, FALSE)); +} +END_TEST + +START_TEST(test_invalid) +{ + chunk_t contents; + + contents = chunk_from_str( + "{\n" + " no = section name\n" + "}\n"); + ck_assert(chunk_write(contents, path, 0022, TRUE)); + ck_assert(!settings->load_files(settings, path, FALSE)); + + contents = chunk_from_str( + "no {\n" + " = key name\n" + "}\n"); ck_assert(chunk_write(contents, path, 0022, TRUE)); ck_assert(!settings->load_files(settings, path, FALSE)); @@ -842,7 +981,12 @@ START_TEST(test_invalid) ck_assert(!settings->load_files(settings, path, FALSE)); contents = chunk_from_str( - "singleline { not = valid }\n"); + "spaces in name {}"); + ck_assert(chunk_write(contents, path, 0022, TRUE)); + ck_assert(!settings->load_files(settings, path, FALSE)); + + contents = chunk_from_str( + "only = a single setting = per line"); ck_assert(chunk_write(contents, path, 0022, TRUE)); ck_assert(!settings->load_files(settings, path, FALSE)); } @@ -903,6 +1047,8 @@ Suite *settings_suite_create() tcase_add_test(tc, test_include); tcase_add_test(tc, test_load_files); tcase_add_test(tc, test_load_files_section); + tcase_add_test(tc, test_order_kv); + tcase_add_test(tc, test_order_section); suite_add_tcase(s, tc); tc = tcase_create("fallback"); @@ -911,8 +1057,14 @@ Suite *settings_suite_create() tcase_add_test(tc, test_add_fallback_printf); suite_add_tcase(s, tc); - tc = tcase_create("invalid data"); - tcase_add_checked_fixture(tc, setup_invalid_config, teardown_config); + tc = tcase_create("strings"); + tcase_add_checked_fixture(tc, setup_string_config, teardown_config); + tcase_add_test(tc, test_strings); + suite_add_tcase(s, tc); + + tc = tcase_create("valid/invalid data"); + tcase_add_checked_fixture(tc, setup_base_config, teardown_config); + tcase_add_test(tc, test_valid); tcase_add_test(tc, test_invalid); suite_add_tcase(s, tc); diff --git a/src/libstrongswan/utils/parser_helper.c b/src/libstrongswan/utils/parser_helper.c new file mode 100644 index 000000000..40c6cfedd --- /dev/null +++ b/src/libstrongswan/utils/parser_helper.c @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2014 Tobias Brunner + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * 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 + +#include "parser_helper.h" + +#include + +typedef struct private_parser_helper_t private_parser_helper_t; +typedef struct parser_helper_file_t parser_helper_file_t; + +struct private_parser_helper_t { + + /** + * Public interface. + */ + parser_helper_t public; + + /** + * Stack of included files, as parser_helper_file_t. + */ + array_t *files; + + /** + * Helper for parsing strings. + */ + bio_writer_t *writer; +}; + +struct parser_helper_file_t { + + /** + * File name + */ + char *name; + + /** + * File stream + */ + FILE *file; + + /** + * Enumerator of paths matching the most recent inclusion pattern. + */ + enumerator_t *matches; +}; + +/** + * Destroy the given file data. + */ +static void parser_helper_file_destroy(parser_helper_file_t *this) +{ + if (this->file) + { + fclose(this->file); + } + free(this->name); + DESTROY_IF(this->matches); + free(this); +} + +/** + * Returns the current file, if any. + */ +static parser_helper_file_t *current_file(private_parser_helper_t *this) +{ + parser_helper_file_t *file; + + array_get(this->files, ARRAY_TAIL, &file); + if (file->name) + { + return file; + } + return NULL; +} + +METHOD(parser_helper_t, file_next, FILE*, + private_parser_helper_t *this) +{ + parser_helper_file_t *file, *next; + char *name; + + array_get(this->files, ARRAY_TAIL, &file); + if (!file->matches) + { + array_remove(this->files, ARRAY_TAIL, NULL); + parser_helper_file_destroy(file); + /* continue with previous includes, if any */ + array_get(this->files, ARRAY_TAIL, &file); + } + if (file->matches) + { + while (file->matches->enumerate(file->matches, &name, NULL)) + { + INIT(next, + .name = strdup(name), + .file = fopen(name, "r"), + ); + + if (next->file) + { + array_insert(this->files, ARRAY_TAIL, next); + return next->file; + } + PARSER_DBG2(&this->public, "unable to open '%s'", name); + parser_helper_file_destroy(next); + } + file->matches->destroy(file->matches); + file->matches = NULL; + } + return NULL; +} + +METHOD(parser_helper_t, file_include, void, + private_parser_helper_t *this, char *pattern) +{ + parser_helper_file_t *file; + char pat[PATH_MAX]; + + array_get(this->files, ARRAY_TAIL, &file); + if (!pattern || !*pattern) + { + PARSER_DBG1(&this->public, "no include pattern specified, ignored"); + file->matches = enumerator_create_empty(); + return; + } + + if (!file->name || pattern[0] == '/') + { /* absolute path */ + if (snprintf(pat, sizeof(pat), "%s", pattern) >= sizeof(pat)) + { + PARSER_DBG1(&this->public, "include pattern too long, ignored"); + file->matches = enumerator_create_empty(); + return; + } + } + else + { /* base relative paths to the directory of the current file */ + char *dir = path_dirname(file->name); + if (snprintf(pat, sizeof(pat), "%s/%s", dir, pattern) >= sizeof(pat)) + { + PARSER_DBG1(&this->public, "include pattern too long, ignored"); + free(dir); + file->matches = enumerator_create_empty(); + return; + } + free(dir); + } + + file->matches = enumerator_create_glob(pat); + if (!file->matches) + { /* if glob(3) is not available, try to load pattern directly */ + file->matches = enumerator_create_single(strdup(pat), free); + } +} + +METHOD(parser_helper_t, string_init, void, + private_parser_helper_t *this) +{ + chunk_t data; + + data = this->writer->extract_buf(this->writer); + chunk_free(&data); +} + +METHOD(parser_helper_t, string_add, void, + private_parser_helper_t *this, char *str) +{ + this->writer->write_data(this->writer, chunk_from_str(str)); +} + +METHOD(parser_helper_t, string_get, char*, + private_parser_helper_t *this) +{ + chunk_t data; + + this->writer->write_data(this->writer, chunk_from_chars('\0')); + data = this->writer->extract_buf(this->writer); + return data.ptr; +} + +METHOD(parser_helper_t, destroy, void, + private_parser_helper_t *this) +{ + array_destroy_function(this->files, (void*)parser_helper_file_destroy, NULL); + this->writer->destroy(this->writer); + free(this); +} + +/** + * Described in header + */ +void parser_helper_log(int level, parser_helper_t *ctx, char *fmt, ...) +{ + private_parser_helper_t *this = (private_parser_helper_t*)ctx; + parser_helper_file_t *file; + char msg[8192]; + va_list args; + int line; + + va_start(args, fmt); + vsnprintf(msg, sizeof(msg), fmt, args); + va_end(args); + + file = current_file(this); + line = ctx->get_lineno ? ctx->get_lineno(ctx->scanner) : 0; + if (file) + { + dbg(DBG_CFG, level, "%s:%d: %s", file->name, line, msg); + } + else + { + dbg(DBG_CFG, level, "%s", msg); + } +} + +/** + * Described in header + */ +parser_helper_t *parser_helper_create(void *context) +{ + private_parser_helper_t *this; + parser_helper_file_t *sentinel; + + INIT(this, + .public = { + .context = context, + .file_include = _file_include, + .file_next = _file_next, + .string_init = _string_init, + .string_add = _string_add, + .string_get = _string_get, + .destroy = _destroy, + }, + .files = array_create(0, 0), + .writer = bio_writer_create(0), + ); + + INIT(sentinel, + .name = NULL, + ); + array_insert(this->files, ARRAY_TAIL, sentinel); + + return &this->public; +} diff --git a/src/libstrongswan/utils/parser_helper.h b/src/libstrongswan/utils/parser_helper.h new file mode 100644 index 000000000..09ed1991c --- /dev/null +++ b/src/libstrongswan/utils/parser_helper.h @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2014 Tobias Brunner + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * 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 parser_helper parser_helper + * @{ @ingroup utils + */ + +#ifndef PARSER_HELPER_H_ +#define PARSER_HELPER_H_ + +#include +#include +#include + +typedef struct parser_helper_t parser_helper_t; + +/** + * Helper class for flex/bison based parsers. + * + * PREFIX equals whatever is configure with + * %option prefix resp. %name-prefix. + */ +struct parser_helper_t { + + /** + * A user defined parser context object. + */ + const void *context; + + /** + * Opaque object allocated by the lexer, should be set with: + * @code + * PREFIXlex_init_extra(helper, &helper->scanner). + * @endcode + */ + void *scanner; + + /** + * Function to determine the current line number (defined by the lexer). + * + * Basically, this should be assigned to PREFIXget_lineno. + * + * @param scanner the lexer + * @return current line number + */ + int (*get_lineno)(void *scanner); + + /** + * Resolves the given include pattern, relative to the location of the + * current file. + * + * Call file_next() to open the next file. + * + * @param pattern file pattern + */ + void (*file_include)(parser_helper_t *this, char *pattern); + + /** + * Get the next file to process. + * + * This will return NULL if all files matching the most recent pattern + * have been handled. If there are other patterns the next call will then + * return the next file matching the previous pattern. + * + * When hitting \<\\> first call + * @code + * PREFIXpop_buffer_state(yyscanner); + * @endcode + * then call this method to check if there are more files to include for + * the most recent call to file_include(), if so, call + * @code + * PREFIXset_in(file, helper->scanner); + * PREFIXpush_buffer_state(PREFIX_create_buffer(file, YY_BUF_SIZE, + * helper->scanner), helper->scanner); + * @endcode + * + * If there are no more files to process check + * YY_CURRENT_BUFFER and if it is FALSE call yyterminate(). + * + * @return next file to process, or NULL (see comment) + */ + FILE *(*file_next)(parser_helper_t *this); + + /** + * Start parsing a string, discards any currently stored data. + */ + void (*string_init)(parser_helper_t *this); + + /** + * Append the given string. + * + * @param str string to append + */ + void (*string_add)(parser_helper_t *this, char *str); + + /** + * Extract the current string buffer as null-terminated string. Can only + * be called once per string. + * + * @return allocated string + */ + char *(*string_get)(parser_helper_t *this); + + /** + * Destroy this instance. + */ + void (*destroy)(parser_helper_t *this); +}; + +/** + * Log the given message either as error or warning + * + * @param level log level + * @param ctx current parser context + * @param fmt error message format + * @param ... additional arguments + */ +void parser_helper_log(int level, parser_helper_t *ctx, char *fmt, ...); + +#if DEBUG_LEVEL >= 1 +# define PARSER_DBG1(ctx, fmt, ...) parser_helper_log(1, ctx, fmt, ##__VA_ARGS__) +#endif +#if DEBUG_LEVEL >= 2 +# define PARSER_DBG2(ctx, fmt, ...) parser_helper_log(2, ctx, fmt, ##__VA_ARGS__) +#endif +#if DEBUG_LEVEL >= 3 +# define PARSER_DBG3(ctx, fmt, ...) parser_helper_log(3, ctx, fmt, ##__VA_ARGS__) +#endif + +#ifndef PARSER_DBG1 +# define PARSER_DBG1(...) {} +#endif +#ifndef PARSER_DBG2 +# define PARSER_DBG2(...) {} +#endif +#ifndef PARSER_DBG3 +# define PARSER_DBG3(...) {} +#endif + +/** + * Create a parser helper object + * + * @param context user defined parser context + * @return parser helper + */ +parser_helper_t *parser_helper_create(void *context); + +#endif /** PARSER_HELPER_H_ @}*/