diff options
Diffstat (limited to 'src')
27 files changed, 2504 insertions, 1591 deletions
diff --git a/src/libstrongswan/Makefile.am b/src/libstrongswan/Makefile.am index d3bd84e10..3fb57de5a 100644 --- a/src/libstrongswan/Makefile.am +++ b/src/libstrongswan/Makefile.am @@ -59,7 +59,7 @@ nobase_strongswan_include_HEADERS = \ library.h \ asn1/asn1.h asn1/asn1_parser.h asn1/oid.h bio/bio_reader.h bio/bio_writer.h \ collections/blocking_queue.h collections/enumerator.h collections/hashtable.h \ -collections/linked_list.h collections/array.h \ +collections/linked_list.h collections/array.h collections/dictionary.h \ crypto/crypters/crypter.h crypto/hashers/hasher.h crypto/mac.h \ crypto/proposal/proposal_keywords.h crypto/proposal/proposal_keywords_static.h \ crypto/prfs/prf.h crypto/prfs/mac_prf.h crypto/rngs/rng.h crypto/nonce_gen.h \ diff --git a/src/libstrongswan/collections/dictionary.h b/src/libstrongswan/collections/dictionary.h new file mode 100644 index 000000000..679e41d2d --- /dev/null +++ b/src/libstrongswan/collections/dictionary.h @@ -0,0 +1,55 @@ +/* + * 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 <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +/** + * @defgroup dictionary dictionary + * @{ @ingroup collections + */ + +#ifndef DICTIONARY_H_ +#define DICTIONARY_H_ + +#include <collections/enumerator.h> + +typedef struct dictionary_t dictionary_t; + +/** + * Interface for read-only dictionaries. + */ +struct dictionary_t { + + /** + * Create an enumerator over the key/value pairs in the dictionary. + * + * @return enumerator over (const void *key, void *value) + */ + enumerator_t *(*create_enumerator)(dictionary_t *this); + + /** + * Returns the value with the given key, if the dictionary contains such an + * entry, otherwise NULL is returned. + * + * @param key the key of the requested value + * @return the value, NULL if not found + */ + void *(*get)(dictionary_t *this, const void *key); + + /** + * Destroys a dictionary object. + */ + void (*destroy)(dictionary_t *this); +}; + +#endif /** DICTIONARY_H_ @}*/ diff --git a/src/libstrongswan/collections/hashtable.c b/src/libstrongswan/collections/hashtable.c index ca31d8361..2b77a37cc 100644 --- a/src/libstrongswan/collections/hashtable.c +++ b/src/libstrongswan/collections/hashtable.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008-2012 Tobias Brunner + * Copyright (C) 2008-2014 Tobias Brunner * Hochschule fuer Technik Rapperswil * * This program is free software; you can redistribute it and/or modify it @@ -250,7 +250,7 @@ static void rehash(private_hashtable_t *this) } METHOD(hashtable_t, put, void*, - private_hashtable_t *this, const void *key, void *value) + private_hashtable_t *this, const void *key, void *value) { void *old_value = NULL; pair_t *pair; @@ -309,19 +309,19 @@ static void *get_internal(private_hashtable_t *this, const void *key, } METHOD(hashtable_t, get, void*, - private_hashtable_t *this, const void *key) + private_hashtable_t *this, const void *key) { return get_internal(this, key, this->equals); } METHOD(hashtable_t, get_match, void*, - private_hashtable_t *this, const void *key, hashtable_equals_t match) + private_hashtable_t *this, const void *key, hashtable_equals_t match) { return get_internal(this, key, match); } METHOD(hashtable_t, remove_, void*, - private_hashtable_t *this, const void *key) + private_hashtable_t *this, const void *key) { void *value = NULL; pair_t *pair, *prev = NULL; @@ -353,7 +353,7 @@ METHOD(hashtable_t, remove_, void*, } METHOD(hashtable_t, remove_at, void, - private_hashtable_t *this, private_enumerator_t *enumerator) + private_hashtable_t *this, private_enumerator_t *enumerator) { if (enumerator->table == this && enumerator->current) { @@ -373,13 +373,13 @@ METHOD(hashtable_t, remove_at, void, } METHOD(hashtable_t, get_count, u_int, - private_hashtable_t *this) + private_hashtable_t *this) { return this->count; } METHOD(enumerator_t, enumerate, bool, - private_enumerator_t *this, const void **key, void **value) + private_enumerator_t *this, const void **key, void **value) { while (this->count && this->row < this->table->capacity) { @@ -411,7 +411,7 @@ METHOD(enumerator_t, enumerate, bool, } METHOD(hashtable_t, create_enumerator, enumerator_t*, - private_hashtable_t *this) + private_hashtable_t *this) { private_enumerator_t *enumerator; @@ -427,8 +427,8 @@ METHOD(hashtable_t, create_enumerator, enumerator_t*, return &enumerator->enumerator; } -METHOD(hashtable_t, destroy, void, - private_hashtable_t *this) +static void destroy_internal(private_hashtable_t *this, + void (*fn)(void*,const void*)) { pair_t *pair, *next; u_int row; @@ -438,6 +438,10 @@ METHOD(hashtable_t, destroy, void, pair = this->table[row]; while (pair) { + if (fn) + { + fn(pair->value, pair->key); + } next = pair->next; free(pair); pair = next; @@ -447,6 +451,18 @@ METHOD(hashtable_t, destroy, void, free(this); } +METHOD(hashtable_t, destroy, void, + private_hashtable_t *this) +{ + destroy_internal(this, NULL); +} + +METHOD(hashtable_t, destroy_function, void, + private_hashtable_t *this, void (*fn)(void*,const void*)) +{ + destroy_internal(this, fn); +} + /* * Described in header. */ @@ -465,6 +481,7 @@ hashtable_t *hashtable_create(hashtable_hash_t hash, hashtable_equals_t equals, .get_count = _get_count, .create_enumerator = _create_enumerator, .destroy = _destroy, + .destroy_function = _destroy_function, }, .hash = hash, .equals = equals, diff --git a/src/libstrongswan/collections/hashtable.h b/src/libstrongswan/collections/hashtable.h index 0a7ebeb65..f60564a42 100644 --- a/src/libstrongswan/collections/hashtable.h +++ b/src/libstrongswan/collections/hashtable.h @@ -156,6 +156,15 @@ struct hashtable_t { * Destroys a hash table object. */ void (*destroy) (hashtable_t *this); + + /** + * Destroys a hash table object and calls the given function for each + * item and its key in the hash table. + * + * @param function function to call on each item and key + */ + void (*destroy_function)(hashtable_t *this, + void (*)(void *val, const void *key)); }; /** diff --git a/src/libstrongswan/tests/test_runner.c b/src/libstrongswan/tests/test_runner.c index 443c0ae13..8f2e9855e 100644 --- a/src/libstrongswan/tests/test_runner.c +++ b/src/libstrongswan/tests/test_runner.c @@ -187,11 +187,17 @@ static bool call_fixture(test_case_t *tcase, bool up) { if (up) { - fixture->setup(); + if (fixture->setup) + { + fixture->setup(); + } } else { - fixture->teardown(); + if (fixture->teardown) + { + fixture->teardown(); + } } } else diff --git a/src/starter/.gitignore b/src/starter/.gitignore index 6b880307d..a370ee554 100644 --- a/src/starter/.gitignore +++ b/src/starter/.gitignore @@ -1,5 +1,4 @@ starter -lexer.c -parser.h -parser.c -parser.output +parser/lexer.c +parser/parser.[ch] +parser/parser.output
\ No newline at end of file diff --git a/src/starter/Android.mk b/src/starter/Android.mk index 2d2ad25c3..c37fc1aa6 100644 --- a/src/starter/Android.mk +++ b/src/starter/Android.mk @@ -3,11 +3,11 @@ include $(CLEAR_VARS) # copy-n-paste from Makefile.am (update for LEX/YACC) starter_SOURCES := \ -parser.c lexer.c ipsec-parser.h netkey.c args.h netkey.h \ -starterstroke.c confread.c \ -starterstroke.h confread.h args.c \ -keywords.c files.h keywords.h cmp.c starter.c cmp.h invokecharon.c \ -invokecharon.h klips.c klips.h +starter.c files.h \ +parser/parser.c parser/lexer.c parser/conf_parser.c parser/conf_parser.h \ +args.c args.h confread.c confread.h keywords.c keywords.h cmp.c cmp.h \ +invokecharon.c invokecharon.h starterstroke.c starterstroke.h \ +netkey.c netkey.h klips.c klips.h LOCAL_SRC_FILES := $(filter %.c,$(starter_SOURCES)) @@ -16,6 +16,7 @@ LOCAL_SRC_FILES := $(filter %.c,$(starter_SOURCES)) LOCAL_C_INCLUDES += \ $(strongswan_PATH)/src/libhydra \ $(strongswan_PATH)/src/libstrongswan \ + $(strongswan_PATH)/src/starter \ $(strongswan_PATH)/src/stroke LOCAL_CFLAGS := $(strongswan_CFLAGS) -DSTART_CHARON \ diff --git a/src/starter/Makefile.am b/src/starter/Makefile.am index 48110dd02..04e126d94 100644 --- a/src/starter/Makefile.am +++ b/src/starter/Makefile.am @@ -1,15 +1,22 @@ +SUBDIRS = . tests + ipsec_PROGRAMS = starter starter_SOURCES = \ -parser.y lexer.l ipsec-parser.h netkey.c args.h netkey.h \ -starterstroke.c confread.c \ -starterstroke.h confread.h args.c \ -keywords.c files.h keywords.h cmp.c starter.c cmp.h invokecharon.c \ -invokecharon.h klips.c klips.h +starter.c files.h \ +args.c args.h confread.c confread.h keywords.c keywords.h cmp.c cmp.h \ +invokecharon.c invokecharon.h starterstroke.c starterstroke.h \ +netkey.c netkey.h klips.c klips.h + +# parser is also used by tests +noinst_LTLIBRARIES = libstarter.la +libstarter_la_SOURCES = \ +parser/parser.y parser/lexer.l parser/conf_parser.c parser/conf_parser.h AM_CPPFLAGS = \ -I${linux_headers} \ -I$(top_srcdir)/src/libstrongswan \ -I$(top_srcdir)/src/libhydra \ + -I$(top_srcdir)/src/starter \ -I$(top_srcdir)/src/stroke \ -DIPSEC_DIR=\"${ipsecdir}\" \ -DIPSEC_CONFDIR=\"${sysconfdir}\" \ @@ -23,10 +30,15 @@ AM_CPPFLAGS = \ AM_YFLAGS = -v -d -starter_LDADD = $(top_builddir)/src/libstrongswan/libstrongswan.la $(top_builddir)/src/libhydra/libhydra.la $(SOCKLIB) $(PTHREADLIB) +starter_LDADD = \ + $(top_builddir)/src/libstrongswan/libstrongswan.la \ + $(top_builddir)/src/libhydra/libhydra.la \ + libstarter.la \ + $(SOCKLIB) $(PTHREADLIB) + EXTRA_DIST = keywords.txt ipsec.conf Android.mk MAINTAINERCLEANFILES = keywords.c -BUILT_SOURCES = parser.h +BUILT_SOURCES = keywords.c parser/parser.h if USE_CHARON AM_CPPFLAGS += -DSTART_CHARON diff --git a/src/starter/README b/src/starter/README deleted file mode 100644 index 4aff64978..000000000 --- a/src/starter/README +++ /dev/null @@ -1,101 +0,0 @@ - -IPsec Starter -- Version 0.2 [Contributed by Arkoon Network Security] -============================ [ http://www.arkoon.net/] - -IPsec Starter is aimed to replace all the scripts which are used to -start and stop strongSwan and to do that in a quicker and a smarter way. - -IPsec Starter can also reload the configuration file (kill --HUP or periodicaly) -and apply the changes. - -Usage: - starter [--debug] [--auto_update <x seconds>] - --debug: enable debugging output - --no_fork: all msg (including pluto) are sent to the console - --auto_update: reload the config file (like kill -HUP) every x seconds - and determine any configuration changes - -FEATURES --------- - -o Load modules of the native Linux 2.6 IPsec stack - -o Launch and monitor pluto - -o Add, initiate, route and del connections - -o Attach and detach interfaces according to config file - -o kill -HUP can be used to reload the config file. New connections will be - added, old ones will be removed and modified ones will be reloaded. - Interfaces/Klips/Pluto will be reloaded if necessary. - -o Full support of the %defaultroute wildcard parameter. - -o save own pid in /var/run/starter - -o Upon reloading, dynamic DNS addr will be resolved and reloaded. Use - --auto_update to periodicaly check dynamic DNS changes. - -o kill -USR1 can be used to reload all connections (delete then add and - route/initiate) - -o /var/run/dynip/xxxx can be used to use a virtual interface name in - ipsec.conf. By example, when adsl can be ppp0, ppp1, ... : - ipsec.conf: interfaces="ipsec0=adsl" - And use /etc/ppp/ip-up to create /var/run/dynip/adsl - /var/run/dynip/adsl: IP_PHYS=ppp0 - -o %auto can be used to automaticaly name the connections - -o kill -TERM can be used to stop FS. pluto will be stopped. - -o Can be used to start strongSwan and load lots of connections in a few - seconds. - -TODO ----- - -o handle wildcards in include lines -- use glob() fct - ex: include /etc/ipsec.*.conf - -o handle duplicates keywords and sections - -o 'also' keyword not supported - -o manually keyed connections - -o IPv6 - -o Documentation - - -CHANGES -------- - -o Version 0.1 -- 2002.01.14 -- First public release - -o Version 0.2 -- 2002.09.04 -- Various enhancements - FreeS/WAN 1.98b, x509 0.9.14, algo 0.8.0 - -o Version 0.2d -- 2004.01.13 -- Adaptions for Openswan 1.0.0 - by Stephan Scholz <sscholz@astaro.com> - -o Version 0.2e -- 2004.10.14 -- Added support for change of interface address - by Stephan Scholz <sscholz@astaro.com> - -o Version 0.2s -- 2005-12-02 -- Ported to strongSwan - by Stephan Scholz <sscholz@astaro.com> - -o Version 0.2x -- 2006-01-02 -- Added missing strongSwan keywords - Full support of the native Linux 2.6 IPsec stack - Full support of %defaultroute - Improved parsing of keywords using perfect hash - function generated by gperf. - by Andreas Steffen <andreas.steffen@hsr.ch> - -THANKS ------- - -o Nathan Angelacos - include fix - diff --git a/src/starter/args.c b/src/starter/args.c index 0d662f400..0874cc7e5 100644 --- a/src/starter/args.c +++ b/src/starter/args.c @@ -1,6 +1,7 @@ -/* automatic handling of confread struct arguments +/* + * Copyright (C) 2014 Tobias Brunner * Copyright (C) 2006 Andreas Steffen - * Hochschule fuer Technik Rapperswil, Switzerland + * 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 @@ -20,7 +21,6 @@ #include <library.h> #include <utils/debug.h> -#include "keywords.h" #include "confread.h" #include "args.h" @@ -36,7 +36,6 @@ typedef enum { ARG_UBIN, ARG_PCNT, ARG_STR, - ARG_LST, ARG_MISC } arg_t; @@ -219,123 +218,48 @@ static const token_info_t token_info[] = { ARG_MISC, 0, NULL /* KW_END_DEPRECATED */ }, }; -static void free_list(char **list) -{ - char **s; - - for (s = list; *s; s++) - { - free(*s); - } - free(list); -} - -char** new_list(char *value) -{ - char *val, *b, *e, *end, **ret; - int count; - - val = strdupnull(value); - if (!val) - { - return NULL; - } - end = val + strlen(val); - for (b = val, count = 0; b < end;) - { - for (e = b; ((*e != ' ') && (*e != '\0')); e++); - *e = '\0'; - if (e != b) - { - count++; - } - b = e + 1; - } - if (count == 0) - { - free(val); - return NULL; - } - ret = (char **)malloc((count+1) * sizeof(char *)); - - for (b = val, count = 0; b < end; ) - { - for (e = b; (*e != '\0'); e++); - if (e != b) - { - ret[count++] = strdupnull(b); - } - b = e + 1; - } - ret[count] = NULL; - free(val); - return ret; -} - - /* * assigns an argument value to a struct field */ -bool assign_arg(kw_token_t token, kw_token_t first, kw_list_t *kw, char *base, - bool *assigned) +bool assign_arg(kw_token_t token, kw_token_t first, char *key, char *value, + void *base, bool *assigned) { - char *p = base + token_info[token].offset; + char *p = (char*)base + token_info[token].offset; const char **list = token_info[token].list; - int index = -1; /* used for enumeration arguments */ - seen_t *seen = (seen_t*)base; /* seen flags are at the top of the struct */ - *assigned = FALSE; - DBG3(DBG_APP, " %s=%s", kw->entry->name, kw->value); - - if (*seen & SEEN_KW(token, first)) - { - DBG1(DBG_APP, "# duplicate '%s' option", kw->entry->name); - return FALSE; - } - - if (token == KW_ESP || token == KW_AH) - { - if (*seen & (SEEN_KW(KW_ESP, first) | SEEN_KW(KW_AH, first))) - { - DBG1(DBG_APP, "# can't have both 'ah' and 'esp' options"); - return FALSE; - } - } - - /* set flag that this argument has been seen */ - *seen |= SEEN_KW(token, first); + DBG3(DBG_APP, " %s=%s", key, value); /* is there a keyword list? */ - if (list != NULL && token_info[token].type != ARG_LST) + if (list != NULL) { bool match = FALSE; while (*list != NULL && !match) { index++; - match = streq(kw->value, *list++); + match = streq(value, *list++); } if (!match) { - DBG1(DBG_APP, "# bad value: %s=%s", kw->entry->name, kw->value); + DBG1(DBG_APP, "# bad value: %s=%s", key, value); return FALSE; } } switch (token_info[token].type) { - case ARG_NONE: - DBG1(DBG_APP, "# option '%s' not supported yet", kw->entry->name); - return FALSE; - case ARG_ENUM: + case ARG_NONE: + DBG1(DBG_APP, "# option '%s' not supported yet", key); + return FALSE; + case ARG_ENUM: { if (index < 0) { DBG1(DBG_APP, "# bad enumeration value: %s=%s (%d)", - kw->entry->name, kw->value, index); + key, value, index); return FALSE; } @@ -345,93 +269,86 @@ bool assign_arg(kw_token_t token, kw_token_t first, kw_list_t *kw, char *base, *b = (index > 0); } else - { + { /* FIXME: this is not entirely correct as the args are enums */ int *i = (int *)p; *i = index; } + break; } - break; - - case ARG_UINT: + case ARG_UINT: { char *endptr; u_int *u = (u_int *)p; - *u = strtoul(kw->value, &endptr, 10); + *u = strtoul(value, &endptr, 10); if (*endptr != '\0') { - DBG1(DBG_APP, "# bad integer value: %s=%s", kw->entry->name, - kw->value); + DBG1(DBG_APP, "# bad integer value: %s=%s", key, value); return FALSE; } + break; } - break; - case ARG_ULNG: - case ARG_PCNT: + case ARG_ULNG: + case ARG_PCNT: { char *endptr; unsigned long *l = (unsigned long *)p; - *l = strtoul(kw->value, &endptr, 10); + *l = strtoul(value, &endptr, 10); if (token_info[token].type == ARG_ULNG) { if (*endptr != '\0') { - DBG1(DBG_APP, "# bad integer value: %s=%s", kw->entry->name, - kw->value); + DBG1(DBG_APP, "# bad integer value: %s=%s", key, value); return FALSE; } } else { - if ((*endptr != '%') || (endptr[1] != '\0') || endptr == kw->value) + if ((*endptr != '%') || (endptr[1] != '\0') || endptr == value) { - DBG1(DBG_APP, "# bad percent value: %s=%s", kw->entry->name, - kw->value); + DBG1(DBG_APP, "# bad percent value: %s=%s", key, value); return FALSE; } } - + break; } - break; - case ARG_ULLI: + case ARG_ULLI: { char *endptr; unsigned long long *ll = (unsigned long long *)p; - *ll = strtoull(kw->value, &endptr, 10); + *ll = strtoull(value, &endptr, 10); if (*endptr != '\0') { - DBG1(DBG_APP, "# bad integer value: %s=%s", kw->entry->name, - kw->value); + DBG1(DBG_APP, "# bad integer value: %s=%s", key, value); return FALSE; } + break; } - break; - case ARG_UBIN: + case ARG_UBIN: { char *endptr; u_int *u = (u_int *)p; - *u = strtoul(kw->value, &endptr, 2); + *u = strtoul(value, &endptr, 2); if (*endptr != '\0') { - DBG1(DBG_APP, "# bad binary value: %s=%s", kw->entry->name, - kw->value); + DBG1(DBG_APP, "# bad binary value: %s=%s", key, value); return FALSE; } + break; } - break; - case ARG_TIME: + case ARG_TIME: { char *endptr; time_t *t = (time_t *)p; - *t = strtoul(kw->value, &endptr, 10); + *t = strtoul(value, &endptr, 10); /* time in seconds? */ if (*endptr == '\0' || (*endptr == 's' && endptr[1] == '\0')) @@ -456,60 +373,21 @@ bool assign_arg(kw_token_t token, kw_token_t first, kw_list_t *kw, char *base, break; } } - DBG1(DBG_APP, "# bad duration value: %s=%s", kw->entry->name, - kw->value); + DBG1(DBG_APP, "# bad duration value: %s=%s", key, value); return FALSE; } - case ARG_STR: + case ARG_STR: { char **cp = (char **)p; /* free any existing string */ free(*cp); - /* assign the new string */ - *cp = strdupnull(kw->value); - } - break; - case ARG_LST: - { - char ***listp = (char ***)p; - - /* free any existing list */ - if (*listp != NULL) - { - free_list(*listp); - } - /* create a new list and assign values */ - *listp = new_list(kw->value); - - /* is there a keyword list? */ - if (list != NULL) - { - char ** lst; - - for (lst = *listp; lst && *lst; lst++) - { - bool match = FALSE; - - list = token_info[token].list; - - while (*list != NULL && !match) - { - match = streq(*lst, *list++); - } - if (!match) - { - DBG1(DBG_APP, "# bad value: %s=%s", - kw->entry->name, *lst); - return FALSE; - } - } - } + *cp = strdupnull(value); + break; } - /* fall through */ - default: - return TRUE; + default: + return TRUE; } *assigned = TRUE; @@ -519,124 +397,69 @@ bool assign_arg(kw_token_t token, kw_token_t first, kw_list_t *kw, char *base, /* * frees all dynamically allocated arguments in a struct */ -void free_args(kw_token_t first, kw_token_t last, char *base) +void free_args(kw_token_t first, kw_token_t last, void *base) { kw_token_t token; for (token = first; token <= last; token++) { - char *p = base + token_info[token].offset; + char *p = (char*)base + token_info[token].offset; switch (token_info[token].type) { - case ARG_STR: + case ARG_STR: { char **cp = (char **)p; free(*cp); *cp = NULL; + break; } - break; - case ARG_LST: - { - char ***listp = (char ***)p; - - if (*listp != NULL) - { - free_list(*listp); - *listp = NULL; - } - } - break; - default: - break; - } - } -} - -/* - * clone all dynamically allocated arguments in a struct - */ -void clone_args(kw_token_t first, kw_token_t last, char *base1, char *base2) -{ - kw_token_t token; - - for (token = first; token <= last; token++) - { - if (token_info[token].type == ARG_STR) - { - char **cp1 = (char **)(base1 + token_info[token].offset); - char **cp2 = (char **)(base2 + token_info[token].offset); - - *cp1 = strdupnull(*cp2); - } - } -} - -static bool cmp_list(char **list1, char **list2) -{ - if ((list1 == NULL) && (list2 == NULL)) - { - return TRUE; - } - if ((list1 == NULL) || (list2 == NULL)) - { - return FALSE; - } - - for ( ; *list1 && *list2; list1++, list2++) - { - if (strcmp(*list1,*list2) != 0) - { - return FALSE; + default: + break; } } - - if ((*list1 != NULL) || (*list2 != NULL)) - { - return FALSE; - } - - return TRUE; } /* * compare all arguments in a struct */ -bool cmp_args(kw_token_t first, kw_token_t last, char *base1, char *base2) +bool cmp_args(kw_token_t first, kw_token_t last, void *base1, void *base2) { kw_token_t token; for (token = first; token <= last; token++) { - char *p1 = base1 + token_info[token].offset; - char *p2 = base2 + token_info[token].offset; + char *p1 = (char*)base1 + token_info[token].offset; + char *p2 = (char*)base2 + token_info[token].offset; switch (token_info[token].type) { - case ARG_ENUM: - if (token_info[token].list == LST_bool) + case ARG_ENUM: { - bool *b1 = (bool *)p1; - bool *b2 = (bool *)p2; - - if (*b1 != *b2) + if (token_info[token].list == LST_bool) { - return FALSE; - } - } - else - { - int *i1 = (int *)p1; - int *i2 = (int *)p2; + bool *b1 = (bool *)p1; + bool *b2 = (bool *)p2; - if (*i1 != *i2) + if (*b1 != *b2) + { + return FALSE; + } + } + else { - return FALSE; + int *i1 = (int *)p1; + int *i2 = (int *)p2; + + if (*i1 != *i2) + { + return FALSE; + } } + break; } - break; - case ARG_UINT: + case ARG_UINT: { u_int *u1 = (u_int *)p1; u_int *u2 = (u_int *)p2; @@ -645,10 +468,10 @@ bool cmp_args(kw_token_t first, kw_token_t last, char *base1, char *base2) { return FALSE; } + break; } - break; - case ARG_ULNG: - case ARG_PCNT: + case ARG_ULNG: + case ARG_PCNT: { unsigned long *l1 = (unsigned long *)p1; unsigned long *l2 = (unsigned long *)p2; @@ -657,9 +480,9 @@ bool cmp_args(kw_token_t first, kw_token_t last, char *base1, char *base2) { return FALSE; } + break; } - break; - case ARG_ULLI: + case ARG_ULLI: { unsigned long long *ll1 = (unsigned long long *)p1; unsigned long long *ll2 = (unsigned long long *)p2; @@ -668,9 +491,9 @@ bool cmp_args(kw_token_t first, kw_token_t last, char *base1, char *base2) { return FALSE; } + break; } - break; - case ARG_TIME: + case ARG_TIME: { time_t *t1 = (time_t *)p1; time_t *t2 = (time_t *)p2; @@ -679,9 +502,9 @@ bool cmp_args(kw_token_t first, kw_token_t last, char *base1, char *base2) { return FALSE; } + break; } - break; - case ARG_STR: + case ARG_STR: { char **cp1 = (char **)p1; char **cp2 = (char **)p2; @@ -694,21 +517,10 @@ bool cmp_args(kw_token_t first, kw_token_t last, char *base1, char *base2) { return FALSE; } + break; } - break; - case ARG_LST: - { - char ***listp1 = (char ***)p1; - char ***listp2 = (char ***)p2; - - if (!cmp_list(*listp1, *listp2)) - { - return FALSE; - } - } - break; - default: - break; + default: + break; } } return TRUE; diff --git a/src/starter/args.h b/src/starter/args.h index f5c13e6ba..76c05de8c 100644 --- a/src/starter/args.h +++ b/src/starter/args.h @@ -1,6 +1,6 @@ -/* automatic handling of confread struct arguments +/* * Copyright (C) 2006 Andreas Steffen - * Hochschule fuer Technik Rapperswil, Switzerland + * 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 @@ -17,16 +17,11 @@ #define _ARGS_H_ #include "keywords.h" -#include "ipsec-parser.h" -extern char **new_list(char *value); -extern bool assign_arg(kw_token_t token, kw_token_t first, kw_list_t *kw - , char *base, bool *assigned); -extern void free_args(kw_token_t first, kw_token_t last, char *base); -extern void clone_args(kw_token_t first, kw_token_t last, char *base1 - , char *base2); -extern bool cmp_args(kw_token_t first, kw_token_t last, char *base1 - , char *base2); +bool assign_arg(kw_token_t token, kw_token_t first, char *key, char *value, + void *base, bool *assigned); +void free_args(kw_token_t first, kw_token_t last, void *base); +bool cmp_args(kw_token_t first, kw_token_t last, void *base1, void *base2); #endif /* _ARGS_H_ */ diff --git a/src/starter/confread.c b/src/starter/confread.c index 0fac89542..17dca66a1 100644 --- a/src/starter/confread.c +++ b/src/starter/confread.c @@ -1,4 +1,7 @@ -/* strongSwan IPsec config file parser +/* + * Copyright (C) 2014 Tobias Brunner + * Hochschule fuer Technik Rapperswil + * * Copyright (C) 2001-2002 Mathieu Lafon - Arkoon Network Security * * This program is free software; you can redistribute it and/or modify it @@ -28,6 +31,7 @@ #include "confread.h" #include "args.h" #include "files.h" +#include "parser/conf_parser.h" #define IKE_LIFETIME_DEFAULT 10800 /* 3 hours */ #define IPSEC_LIFETIME_DEFAULT 3600 /* 1 hour */ @@ -41,6 +45,11 @@ static const char esp_defaults[] = "aes128-sha1,3des-sha1"; static const char firewall_defaults[] = IPSEC_SCRIPT " _updown iptables"; +/** + * Provided by GPERF + */ +extern kw_entry_t *in_word_set (char *str, unsigned int len); + static bool daemon_exists(char *daemon, char *path) { struct stat st; @@ -55,24 +64,21 @@ static bool daemon_exists(char *daemon, char *path) /** * Process deprecated keywords */ -static bool is_deprecated(kw_token_t token, kw_list_t *kw, char *name) +static bool is_deprecated(kw_token_t token, char *name, char *conn) { switch (token) { case KW_SETUP_DEPRECATED: case KW_PKCS11_DEPRECATED: - DBG1(DBG_APP, "# deprecated keyword '%s' in config setup", - kw->entry->name); + DBG1(DBG_APP, "# deprecated keyword '%s' in config setup", name); break; case KW_CONN_DEPRECATED: case KW_END_DEPRECATED: case KW_PFS_DEPRECATED: - DBG1(DBG_APP, "# deprecated keyword '%s' in conn '%s'", - kw->entry->name, name); + DBG1(DBG_APP, "# deprecated keyword '%s' in conn '%s'", name, conn); break; case KW_CA_DEPRECATED: - DBG1(DBG_APP, "# deprecated keyword '%s' in ca '%s'", - kw->entry->name, name); + DBG1(DBG_APP, "# deprecated keyword '%s' in ca '%s'", name, conn); break; default: return FALSE; @@ -81,11 +87,11 @@ static bool is_deprecated(kw_token_t token, kw_list_t *kw, char *name) switch (token) { case KW_PKCS11_DEPRECATED: - DBG1(DBG_APP, " use the 'pkcs11' plugin instead", kw->entry->name); + DBG1(DBG_APP, " use the 'pkcs11' plugin instead"); break; case KW_PFS_DEPRECATED: DBG1(DBG_APP, " PFS is enabled by specifying a DH group in the " - "'esp' cipher suite", kw->entry->name); + "'esp' cipher suite"); break; default: break; @@ -93,101 +99,54 @@ static bool is_deprecated(kw_token_t token, kw_list_t *kw, char *name) return TRUE; } -static void default_values(starter_config_t *cfg) -{ - if (cfg == NULL) - return; - - memset(cfg, 0, sizeof(struct starter_config)); - - /* is there enough space for all seen flags? */ - assert(KW_SETUP_LAST - KW_SETUP_FIRST < - sizeof(cfg->setup.seen) * BITS_PER_BYTE); - assert(KW_CONN_LAST - KW_CONN_FIRST < - sizeof(cfg->conn_default.seen) * BITS_PER_BYTE); - assert(KW_END_LAST - KW_END_FIRST < - sizeof(cfg->conn_default.right.seen) * BITS_PER_BYTE); - assert(KW_CA_LAST - KW_CA_FIRST < - sizeof(cfg->ca_default.seen) * BITS_PER_BYTE); - - cfg->setup.seen = SEEN_NONE; - cfg->setup.uniqueids = TRUE; - -#ifdef START_CHARON - cfg->setup.charonstart = TRUE; -#endif - - cfg->conn_default.seen = SEEN_NONE; - cfg->conn_default.startup = STARTUP_NO; - cfg->conn_default.state = STATE_IGNORE; - cfg->conn_default.mode = MODE_TUNNEL; - cfg->conn_default.options = SA_OPTION_MOBIKE; - - cfg->conn_default.ike = strdupnull(ike_defaults); - cfg->conn_default.esp = strdupnull(esp_defaults); - cfg->conn_default.sa_ike_life_seconds = IKE_LIFETIME_DEFAULT; - cfg->conn_default.sa_ipsec_life_seconds = IPSEC_LIFETIME_DEFAULT; - cfg->conn_default.sa_rekey_margin = SA_REPLACEMENT_MARGIN_DEFAULT; - cfg->conn_default.sa_rekey_fuzz = SA_REPLACEMENT_FUZZ_DEFAULT; - cfg->conn_default.sa_keying_tries = SA_REPLACEMENT_RETRIES_DEFAULT; - cfg->conn_default.install_policy = TRUE; - cfg->conn_default.dpd_delay = 30; /* seconds */ - cfg->conn_default.dpd_timeout = 150; /* seconds */ - cfg->conn_default.replay_window = SA_REPLAY_WINDOW_DEFAULT; - - cfg->conn_default.left.seen = SEEN_NONE; - cfg->conn_default.right.seen = SEEN_NONE; - - cfg->conn_default.left.sendcert = CERT_SEND_IF_ASKED; - cfg->conn_default.right.sendcert = CERT_SEND_IF_ASKED; - - cfg->conn_default.left.ikeport = 500; - cfg->conn_default.right.ikeport = 500; - - cfg->conn_default.left.to_port = 0xffff; - cfg->conn_default.right.to_port = 0xffff; - - cfg->ca_default.seen = SEEN_NONE; -} - -#define KW_SA_OPTION_FLAG(sy, sn, fl) \ - if (streq(kw->value, sy)) { conn->options |= fl; } \ - else if (streq(kw->value, sn)) { conn->options &= ~fl; } \ - else { DBG1(DBG_APP, "# bad option value: %s=%s", kw->entry->name, kw->value); cfg->err++; } - -static void load_setup(starter_config_t *cfg, config_parsed_t *cfgp) +/* + * parse config setup section + */ +static void load_setup(starter_config_t *cfg, conf_parser_t *parser) { - kw_list_t *kw; + enumerator_t *enumerator; + dictionary_t *dict; + kw_entry_t *entry; + char *key, *value; DBG2(DBG_APP, "Loading config setup"); - - for (kw = cfgp->config_setup; kw; kw = kw->next) + dict = parser->get_section(parser, CONF_PARSER_CONFIG_SETUP, NULL); + if (!dict) + { + return; + } + enumerator = dict->create_enumerator(dict); + while (enumerator->enumerate(enumerator, &key, &value)) { bool assigned = FALSE; - kw_token_t token = kw->entry->token; - - if ((int)token < KW_SETUP_FIRST || token > KW_SETUP_LAST) + entry = in_word_set(key, strlen(key)); + if (!entry) + { + DBG1(DBG_APP, "# unknown keyword '%s'", key); + cfg->non_fatal_err++; + continue; + } + if ((int)entry->token < KW_SETUP_FIRST || entry->token > KW_SETUP_LAST) { - DBG1(DBG_APP, "# unsupported keyword '%s' in config setup", - kw->entry->name); + DBG1(DBG_APP, "# unsupported keyword '%s' in config setup", key); cfg->err++; continue; } - - if (is_deprecated(token, kw, "")) + if (is_deprecated(entry->token, key, "")) { cfg->non_fatal_err++; continue; } - - if (!assign_arg(token, KW_SETUP_FIRST, kw, (char *)cfg, &assigned)) + if (!assign_arg(entry->token, KW_SETUP_FIRST, key, value, cfg, + &assigned)) { DBG1(DBG_APP, " bad argument value in config setup"); cfg->err++; - continue; } } + enumerator->destroy(enumerator); + dict->destroy(dict); /* verify the executables are actually available */ #ifdef START_CHARON @@ -198,292 +157,321 @@ static void load_setup(starter_config_t *cfg, config_parsed_t *cfgp) #endif } +/* + * parse a ca section + */ +static void load_ca(starter_ca_t *ca, starter_config_t *cfg, + conf_parser_t *parser) +{ + enumerator_t *enumerator; + dictionary_t *dict; + kw_entry_t *entry; + kw_token_t token; + char *key, *value; + + DBG2(DBG_APP, "Loading ca '%s'", ca->name); + dict = parser->get_section(parser, CONF_PARSER_CA, ca->name); + if (!dict) + { + return; + } + enumerator = dict->create_enumerator(dict); + while (enumerator->enumerate(enumerator, &key, &value)) + { + bool assigned = FALSE; + + entry = in_word_set(key, strlen(key)); + if (!entry) + { + DBG1(DBG_APP, "# unknown keyword '%s'", key); + cfg->non_fatal_err++; + continue; + } + token = entry->token; + if (token == KW_AUTO) + { + token = KW_CA_SETUP; + } + if (token < KW_CA_FIRST || token > KW_CA_LAST) + { + DBG1(DBG_APP, "# unsupported keyword '%s' in ca '%s'", + key, ca->name); + cfg->err++; + continue; + } + if (is_deprecated(token, key, ca->name)) + { + cfg->non_fatal_err++; + continue; + } + if (!assign_arg(token, KW_CA_FIRST, key, value, ca, &assigned)) + { + DBG1(DBG_APP, " bad argument value in ca '%s'", ca->name); + cfg->err++; + } + } + enumerator->destroy(enumerator); + dict->destroy(dict); + + /* treat 'route' and 'start' as 'add' */ + if (ca->startup != STARTUP_NO) + { + ca->startup = STARTUP_ADD; + } +} + +/* + * set some default values + */ +static void conn_defaults(starter_conn_t *conn) +{ + conn->startup = STARTUP_NO; + conn->state = STATE_IGNORE; + conn->mode = MODE_TUNNEL; + conn->options = SA_OPTION_MOBIKE; + + conn->ike = strdupnull(ike_defaults); + /* esp defaults are set after parsing the conn section */ + conn->sa_ike_life_seconds = IKE_LIFETIME_DEFAULT; + conn->sa_ipsec_life_seconds = IPSEC_LIFETIME_DEFAULT; + conn->sa_rekey_margin = SA_REPLACEMENT_MARGIN_DEFAULT; + conn->sa_rekey_fuzz = SA_REPLACEMENT_FUZZ_DEFAULT; + conn->sa_keying_tries = SA_REPLACEMENT_RETRIES_DEFAULT; + conn->install_policy = TRUE; + conn->dpd_delay = 30; /* seconds */ + conn->dpd_timeout = 150; /* seconds */ + conn->replay_window = SA_REPLAY_WINDOW_DEFAULT; + + conn->left.sendcert = CERT_SEND_IF_ASKED; + conn->right.sendcert = CERT_SEND_IF_ASKED; + + conn->left.ikeport = 500; + conn->right.ikeport = 500; + + conn->left.to_port = 0xffff; + conn->right.to_port = 0xffff; +} + +/* + * parse left|right specific options + */ static void kw_end(starter_conn_t *conn, starter_end_t *end, kw_token_t token, - kw_list_t *kw, char *conn_name, starter_config_t *cfg) + char *key, char *value, starter_config_t *cfg) { bool assigned = FALSE; - char *name = kw->entry->name; - char *value = kw->value; - - if (is_deprecated(token, kw, conn_name)) + if (is_deprecated(token, key, conn->name)) { cfg->non_fatal_err++; return; } - if (!assign_arg(token, KW_END_FIRST, kw, (char *)end, &assigned)) + if (!assign_arg(token, KW_END_FIRST, key, value, end, &assigned)) + { goto err; + } /* post processing of some keywords that were assigned automatically */ switch (token) { - case KW_HOST: - if (value && strlen(value) > 0 && value[0] == '%') - { - if (streq(value, "%defaultroute")) + case KW_HOST: + if (value && strlen(value) > 0 && value[0] == '%') { - value = "%any"; + if (streq(value, "%defaultroute")) + { + value = "%any"; + } + if (!streq(value, "%any") && !streq(value, "%any4") && + !streq(value, "%any6")) + { /* allow_any prefix */ + end->allow_any = TRUE; + value++; + } } - if (!streq(value, "%any") && !streq(value, "%any4") && - !streq(value, "%any6")) - { /* allow_any prefix */ - end->allow_any = TRUE; - value++; + free(end->host); + end->host = strdupnull(value); + break; + case KW_SOURCEIP: + conn->mode = MODE_TUNNEL; + conn->proxy_mode = FALSE; + break; + case KW_SENDCERT: + if (end->sendcert == CERT_YES_SEND) + { + end->sendcert = CERT_ALWAYS_SEND; } - } - free(end->host); - end->host = strdupnull(value); - break; - case KW_SOURCEIP: - conn->mode = MODE_TUNNEL; - conn->proxy_mode = FALSE; - break; - case KW_SENDCERT: - if (end->sendcert == CERT_YES_SEND) - { - end->sendcert = CERT_ALWAYS_SEND; - } - else if (end->sendcert == CERT_NO_SEND) - { - end->sendcert = CERT_NEVER_SEND; - } - break; - default: - break; + else if (end->sendcert == CERT_NO_SEND) + { + end->sendcert = CERT_NEVER_SEND; + } + break; + default: + break; } if (assigned) + { return; + } /* individual processing of keywords that were not assigned automatically */ switch (token) { - case KW_PROTOPORT: - { - struct protoent *proto; - struct servent *svc; - char *sep, *port = "", *endptr; - long int p; - - sep = strchr(value, '/'); - if (sep) - { /* protocol/port */ - *sep = '\0'; - port = sep + 1; - } + case KW_PROTOPORT: + { + struct protoent *proto; + struct servent *svc; + char *sep, *port = "", *endptr; + long int p; + + sep = strchr(value, '/'); + if (sep) + { /* protocol/port */ + *sep = '\0'; + port = sep + 1; + } - if (streq(value, "%any")) - { - end->protocol = 0; - } - else - { - proto = getprotobyname(value); - if (proto) + if (streq(value, "%any")) { - end->protocol = proto->p_proto; + end->protocol = 0; } else { - p = strtol(value, &endptr, 0); - if ((*value && *endptr) || p < 0 || p > 0xff) + proto = getprotobyname(value); + if (proto) { - DBG1(DBG_APP, "# bad protocol: %s=%s", name, value); - goto err; + end->protocol = proto->p_proto; + } + else + { + p = strtol(value, &endptr, 0); + if ((*value && *endptr) || p < 0 || p > 0xff) + { + DBG1(DBG_APP, "# bad protocol: %s=%s", key, value); + goto err; + } + end->protocol = (u_int8_t)p; } - end->protocol = (u_int8_t)p; } - } - if (streq(port, "%any")) - { - end->from_port = 0; - end->to_port = 0xffff; - } - else if (streq(port, "%opaque")) - { - end->from_port = 0xffff; - end->to_port = 0; - } - else if (*port) - { - svc = getservbyname(port, NULL); - if (svc) + if (streq(port, "%any")) { - end->from_port = end->to_port = ntohs(svc->s_port); + end->from_port = 0; + end->to_port = 0xffff; } - else + else if (streq(port, "%opaque")) { - p = strtol(port, &endptr, 0); - if (p < 0 || p > 0xffff) + end->from_port = 0xffff; + end->to_port = 0; + } + else if (*port) + { + svc = getservbyname(port, NULL); + if (svc) { - DBG1(DBG_APP, "# bad port: %s=%s", name, port); - goto err; + end->from_port = end->to_port = ntohs(svc->s_port); } - end->from_port = p; - if (*endptr == '-') + else { - port = endptr + 1; p = strtol(port, &endptr, 0); if (p < 0 || p > 0xffff) { - DBG1(DBG_APP, "# bad port: %s=%s", name, port); + DBG1(DBG_APP, "# bad port: %s=%s", key, port); + goto err; + } + end->from_port = p; + if (*endptr == '-') + { + port = endptr + 1; + p = strtol(port, &endptr, 0); + if (p < 0 || p > 0xffff) + { + DBG1(DBG_APP, "# bad port: %s=%s", key, port); + goto err; + } + } + end->to_port = p; + if (*endptr) + { + DBG1(DBG_APP, "# bad port: %s=%s", key, port); goto err; } - } - end->to_port = p; - if (*endptr) - { - DBG1(DBG_APP, "# bad port: %s=%s", name, port); - goto err; } } + if (sep) + { /* restore the original text in case also= is used */ + *sep = '/'; + } + break; } - if (sep) - { /* restore the original text in case also= is used */ - *sep = '/'; - } - break; - } - default: - break; + default: + break; } return; err: - DBG1(DBG_APP, " bad argument value in conn '%s'", conn_name); + DBG1(DBG_APP, " bad argument value in conn '%s'", conn->name); cfg->err++; } /* - * handles left|rightfirewall and left|rightupdown parameters + * macro to handle simple flags */ -static void handle_firewall(const char *label, starter_end_t *end, - starter_config_t *cfg) -{ - if (end->firewall && (end->seen & SEEN_KW(KW_FIREWALL, KW_END_FIRST))) - { - if (end->updown != NULL) - { - DBG1(DBG_APP, "# cannot have both %sfirewall and %supdown", label, - label); - cfg->err++; - } - else - { - end->updown = strdupnull(firewall_defaults); - end->firewall = FALSE; - } - } -} +#define KW_SA_OPTION_FLAG(sy, sn, fl) \ + if (streq(value, sy)) { conn->options |= fl; } \ + else if (streq(value, sn)) { conn->options &= ~fl; } \ + else { DBG1(DBG_APP, "# bad option value: %s=%s", key, value); cfg->err++; } /* - * parse a conn section + * parse settings not handled by the simple argument parser */ -static void load_conn(starter_conn_t *conn, kw_list_t *kw, starter_config_t *cfg) +static void handle_keyword(kw_token_t token, starter_conn_t *conn, char *key, + char *value, starter_config_t *cfg) { - char *conn_name = (conn->name == NULL)? "%default":conn->name; - - for ( ; kw; kw = kw->next) + if ((token == KW_ESP && conn->ah) || (token == KW_AH && conn->esp)) + { + DBG1(DBG_APP, "# can't have both 'ah' and 'esp' options"); + cfg->err++; + return; + } + switch (token) { - bool assigned = FALSE; - - kw_token_t token = kw->entry->token; - - if (token >= KW_LEFT_FIRST && token <= KW_LEFT_LAST) - { - kw_end(conn, &conn->left, token - KW_LEFT_FIRST + KW_END_FIRST - , kw, conn_name, cfg); - continue; - } - else if (token >= KW_RIGHT_FIRST && token <= KW_RIGHT_LAST) - { - kw_end(conn, &conn->right, token - KW_RIGHT_FIRST + KW_END_FIRST - , kw, conn_name, cfg); - continue; - } - - if (token == KW_AUTO) - { - token = KW_CONN_SETUP; - } - else if (token == KW_ALSO) - { - if (cfg->parse_also) - { - also_t *also = malloc_thing(also_t); - - also->name = strdupnull(kw->value); - also->next = conn->also; - conn->also = also; - - DBG2(DBG_APP, " also=%s", kw->value); - } - continue; - } - - if (token < KW_CONN_FIRST || token > KW_CONN_LAST) - { - DBG1(DBG_APP, "# unsupported keyword '%s' in conn '%s'", - kw->entry->name, conn_name); - cfg->err++; - continue; - } - - if (is_deprecated(token, kw, conn_name)) - { - cfg->non_fatal_err++; - continue; - } - - if (!assign_arg(token, KW_CONN_FIRST, kw, (char *)conn, &assigned)) - { - DBG1(DBG_APP, " bad argument value in conn '%s'", conn_name); - cfg->err++; - continue; - } - - if (assigned) - continue; - - switch (token) - { case KW_TYPE: + { conn->mode = MODE_TRANSPORT; conn->proxy_mode = FALSE; - if (streq(kw->value, "tunnel")) + if (streq(value, "tunnel")) { conn->mode = MODE_TUNNEL; } - else if (streq(kw->value, "beet")) + else if (streq(value, "beet")) { conn->mode = MODE_BEET; } - else if (streq(kw->value, "transport_proxy")) + else if (streq(value, "transport_proxy")) { conn->mode = MODE_TRANSPORT; conn->proxy_mode = TRUE; } - else if (streq(kw->value, "passthrough") || streq(kw->value, "pass")) + else if (streq(value, "passthrough") || streq(value, "pass")) { conn->mode = MODE_PASS; } - else if (streq(kw->value, "drop") || streq(kw->value, "reject")) + else if (streq(value, "drop") || streq(value, "reject")) { conn->mode = MODE_DROP; } - else if (!streq(kw->value, "transport")) + else if (!streq(value, "transport")) { - DBG1(DBG_APP, "# bad policy value: %s=%s", kw->entry->name, - kw->value); + DBG1(DBG_APP, "# bad policy value: %s=%s", key, value); cfg->err++; } break; + } case KW_COMPRESS: KW_SA_OPTION_FLAG("yes", "no", SA_OPTION_COMPRESS) break; case KW_MARK: - if (!mark_from_string(kw->value, &conn->mark_in)) + if (!mark_from_string(value, &conn->mark_in)) { cfg->err++; break; @@ -491,19 +479,19 @@ static void load_conn(starter_conn_t *conn, kw_list_t *kw, starter_config_t *cfg conn->mark_out = conn->mark_in; break; case KW_MARK_IN: - if (!mark_from_string(kw->value, &conn->mark_in)) + if (!mark_from_string(value, &conn->mark_in)) { cfg->err++; } break; case KW_MARK_OUT: - if (!mark_from_string(kw->value, &conn->mark_out)) + if (!mark_from_string(value, &conn->mark_out)) { cfg->err++; } break; case KW_TFC: - if (streq(kw->value, "%mtu")) + if (streq(value, "%mtu")) { conn->tfc = -1; } @@ -511,17 +499,16 @@ static void load_conn(starter_conn_t *conn, kw_list_t *kw, starter_config_t *cfg { char *endptr; - conn->tfc = strtoul(kw->value, &endptr, 10); + conn->tfc = strtoul(value, &endptr, 10); if (*endptr != '\0') { - DBG1(DBG_APP, "# bad integer value: %s=%s", kw->entry->name, - kw->value); + DBG1(DBG_APP, "# bad integer value: %s=%s", key, value); cfg->err++; } } break; case KW_KEYINGTRIES: - if (streq(kw->value, "%forever")) + if (streq(value, "%forever")) { conn->sa_keying_tries = 0; } @@ -529,11 +516,10 @@ static void load_conn(starter_conn_t *conn, kw_list_t *kw, starter_config_t *cfg { char *endptr; - conn->sa_keying_tries = strtoul(kw->value, &endptr, 10); + conn->sa_keying_tries = strtoul(value, &endptr, 10); if (*endptr != '\0') { - DBG1(DBG_APP, "# bad integer value: %s=%s", kw->entry->name, - kw->value); + DBG1(DBG_APP, "# bad integer value: %s=%s", key, value); cfg->err++; } } @@ -558,219 +544,120 @@ static void load_conn(starter_conn_t *conn, kw_list_t *kw, starter_config_t *cfg break; default: break; - } } - - handle_firewall("left", &conn->left, cfg); - handle_firewall("right", &conn->right, cfg); } /* - * initialize a conn object with the default conn + * handles left|rightfirewall and left|rightupdown parameters */ -static void conn_default(char *name, starter_conn_t *conn, starter_conn_t *def) +static void handle_firewall(const char *label, starter_end_t *end, + starter_config_t *cfg) { - memcpy(conn, def, sizeof(starter_conn_t)); - conn->name = strdupnull(name); - - clone_args(KW_CONN_FIRST, KW_CONN_LAST, (char *)conn, (char *)def); - clone_args(KW_END_FIRST, KW_END_LAST, (char *)&conn->left, (char *)&def->left); - clone_args(KW_END_FIRST, KW_END_LAST, (char *)&conn->right, (char *)&def->right); + if (end->firewall) + { + if (end->updown != NULL) + { + DBG1(DBG_APP, "# cannot have both %sfirewall and %supdown", label, + label); + cfg->err++; + } + else + { + end->updown = strdupnull(firewall_defaults); + end->firewall = FALSE; + } + } } /* - * parse a ca section + * parse a conn section */ -static void load_ca(starter_ca_t *ca, kw_list_t *kw, starter_config_t *cfg) +static void load_conn(starter_conn_t *conn, starter_config_t *cfg, + conf_parser_t *parser) { - char *ca_name = (ca->name == NULL)? "%default":ca->name; - - for ( ; kw; kw = kw->next) + enumerator_t *enumerator; + dictionary_t *dict; + kw_entry_t *entry; + kw_token_t token; + char *key, *value; + + DBG2(DBG_APP, "Loading conn '%s'", conn->name); + dict = parser->get_section(parser, CONF_PARSER_CONN, conn->name); + if (!dict) + { + return; + } + enumerator = dict->create_enumerator(dict); + while (enumerator->enumerate(enumerator, &key, &value)) { bool assigned = FALSE; - kw_token_t token = kw->entry->token; - - if (token == KW_AUTO) + entry = in_word_set(key, strlen(key)); + if (!entry) { - token = KW_CA_SETUP; - } - else if (token == KW_ALSO) - { - if (cfg->parse_also) - { - also_t *also = malloc_thing(also_t); - - also->name = strdupnull(kw->value); - also->next = ca->also; - ca->also = also; - - DBG2(DBG_APP, " also=%s", kw->value); - } + DBG1(DBG_APP, "# unknown keyword '%s'", key); + cfg->non_fatal_err++; continue; } - - if (token < KW_CA_FIRST || token > KW_CA_LAST) + token = entry->token; + if (token >= KW_LEFT_FIRST && token <= KW_LEFT_LAST) { - DBG1(DBG_APP, "# unsupported keyword '%s' in ca '%s'", - kw->entry->name, ca_name); - cfg->err++; + kw_end(conn, &conn->left, token - KW_LEFT_FIRST + KW_END_FIRST, + key, value, cfg); continue; } - - if (is_deprecated(token, kw, ca_name)) + else if (token >= KW_RIGHT_FIRST && token <= KW_RIGHT_LAST) { - cfg->non_fatal_err++; + kw_end(conn, &conn->right, token - KW_RIGHT_FIRST + KW_END_FIRST, + key, value, cfg); continue; } - - if (!assign_arg(token, KW_CA_FIRST, kw, (char *)ca, &assigned)) - { - DBG1(DBG_APP, " bad argument value in ca '%s'", ca_name); - cfg->err++; - } - } - - /* treat 'route' and 'start' as 'add' */ - if (ca->startup != STARTUP_NO) - ca->startup = STARTUP_ADD; -} - -/* - * initialize a ca object with the default ca - */ -static void ca_default(char *name, starter_ca_t *ca, starter_ca_t *def) -{ - memcpy(ca, def, sizeof(starter_ca_t)); - ca->name = strdupnull(name); - - clone_args(KW_CA_FIRST, KW_CA_LAST, (char *)ca, (char *)def); -} - -static kw_list_t* find_also_conn(const char* name, starter_conn_t *conn, - starter_config_t *cfg); - -static void load_also_conns(starter_conn_t *conn, also_t *also, - starter_config_t *cfg) -{ - while (also != NULL) - { - kw_list_t *kw = find_also_conn(also->name, conn, cfg); - - if (kw == NULL) + if (token == KW_AUTO) { - DBG1(DBG_APP, " conn '%s' cannot include '%s'", conn->name, - also->name); + token = KW_CONN_SETUP; } - else + if (token < KW_CONN_FIRST || token > KW_CONN_LAST) { - DBG2(DBG_APP, "conn '%s' includes '%s'", conn->name, also->name); - /* only load if no error occurred in the first round */ - if (cfg->err == 0) - load_conn(conn, kw, cfg); + DBG1(DBG_APP, "# unsupported keyword '%s' in conn '%s'", + key, conn->name); + cfg->err++; + continue; } - also = also->next; - } -} - -/* - * find a conn included by also - */ -static kw_list_t* find_also_conn(const char* name, starter_conn_t *conn, - starter_config_t *cfg) -{ - starter_conn_t *c = cfg->conn_first; - - while (c != NULL) - { - if (streq(name, c->name)) + if (is_deprecated(token, key, conn->name)) { - if (conn->visit == c->visit) - { - DBG1(DBG_APP, "# detected also loop"); - cfg->err++; - return NULL; - } - c->visit = conn->visit; - load_also_conns(conn, c->also, cfg); - return c->kw; + cfg->non_fatal_err++; + continue; } - c = c->next; - } - - DBG1(DBG_APP, "# also '%s' not found", name); - cfg->err++; - return NULL; -} - -static kw_list_t* find_also_ca(const char* name, starter_ca_t *ca, - starter_config_t *cfg); - -static void load_also_cas(starter_ca_t *ca, also_t *also, starter_config_t *cfg) -{ - while (also != NULL) - { - kw_list_t *kw = find_also_ca(also->name, ca, cfg); - - if (kw == NULL) + if (!assign_arg(token, KW_CONN_FIRST, key, value, conn, + &assigned)) { - DBG1(DBG_APP, " ca '%s' cannot include '%s'", ca->name, - also->name); + DBG1(DBG_APP, " bad argument value in conn '%s'", conn->name); + cfg->err++; + continue; } - else + if (!assigned) { - DBG2(DBG_APP, "ca '%s' includes '%s'", ca->name, also->name); - /* only load if no error occurred in the first round */ - if (cfg->err == 0) - load_ca(ca, kw, cfg); + handle_keyword(token, conn, key, value, cfg); } - also = also->next; } -} + enumerator->destroy(enumerator); + dict->destroy(dict); -/* - * find a ca included by also - */ -static kw_list_t* find_also_ca(const char* name, starter_ca_t *ca, - starter_config_t *cfg) -{ - starter_ca_t *c = cfg->ca_first; + handle_firewall("left", &conn->left, cfg); + handle_firewall("right", &conn->right, cfg); - while (c != NULL) + if (!conn->esp && !conn->ah) { - if (streq(name, c->name)) - { - if (ca->visit == c->visit) - { - DBG1(DBG_APP, "# detected also loop"); - cfg->err++; - return NULL; - } - c->visit = ca->visit; - load_also_cas(ca, c->also, cfg); - return c->kw; - } - c = c->next; + conn->esp = strdupnull(esp_defaults); } - - DBG1(DBG_APP, "# also '%s' not found", name); - cfg->err++; - return NULL; } /* - * free the memory used by also_t objects + * free the memory used by a starter_ca_t object */ -static void free_also(also_t *head) +static void confread_free_ca(starter_ca_t *ca) { - while (head != NULL) - { - also_t *also = head; - - head = also->next; - free(also->name); - free(also); - } + free_args(KW_CA_NAME, KW_CA_LAST, (char *)ca); } /* @@ -781,17 +668,6 @@ static void confread_free_conn(starter_conn_t *conn) free_args(KW_END_FIRST, KW_END_LAST, (char *)&conn->left); free_args(KW_END_FIRST, KW_END_LAST, (char *)&conn->right); free_args(KW_CONN_NAME, KW_CONN_LAST, (char *)conn); - free_also(conn->also); -} - -/* - * free the memory used by a starter_ca_t object - */ -static void -confread_free_ca(starter_ca_t *ca) -{ - free_args(KW_CA_NAME, KW_CA_LAST, (char *)ca); - free_also(ca->also); } /* @@ -804,8 +680,6 @@ void confread_free(starter_config_t *cfg) free_args(KW_SETUP_FIRST, KW_SETUP_LAST, (char *)cfg); - confread_free_conn(&cfg->conn_default); - while (conn != NULL) { starter_conn_t *conn_aux = conn; @@ -815,8 +689,6 @@ void confread_free(starter_config_t *cfg) free(conn_aux); } - confread_free_ca(&cfg->ca_default); - while (ca != NULL) { starter_ca_t *ca_aux = ca; @@ -834,170 +706,108 @@ void confread_free(starter_config_t *cfg) */ starter_config_t* confread_load(const char *file) { + conf_parser_t *parser; starter_config_t *cfg = NULL; - config_parsed_t *cfgp; - section_list_t *sconn, *sca; - starter_conn_t *conn; - starter_ca_t *ca; - + enumerator_t *enumerator; u_int total_err; - u_int visit = 0; + char *name; - /* load IPSec configuration file */ - cfgp = parser_load_conf(file); - if (!cfgp) + parser = conf_parser_create(file); + if (!parser->parse(parser)) { + parser->destroy(parser); return NULL; } - cfg = malloc_thing(starter_config_t); - - /* set default values */ - default_values(cfg); - /* load config setup section */ - load_setup(cfg, cfgp); - - /* in the first round parse also statements */ - cfg->parse_also = TRUE; + INIT(cfg, + .setup = { + .uniqueids = TRUE, - /* find %default ca section */ - for (sca = cfgp->ca_first; sca; sca = sca->next) - { - if (streq(sca->name, "%default")) - { - DBG2(DBG_APP, "Loading ca %%default"); - load_ca(&cfg->ca_default, sca->kw, cfg); } - } + ); +#ifdef START_CHARON + cfg->setup.charonstart = TRUE; +#endif - /* parameters defined in ca %default sections can be overloads */ - cfg->ca_default.seen = SEEN_NONE; + /* load config setup section */ + load_setup(cfg, parser); - /* load other ca sections */ - for (sca = cfgp->ca_first; sca; sca = sca->next) + /* load ca sections */ + enumerator = parser->get_sections(parser, CONF_PARSER_CA); + while (enumerator->enumerate(enumerator, &name)) { - u_int previous_err; + u_int previous_err = cfg->err; + starter_ca_t *ca; - /* skip %default ca section */ - if (streq(sca->name, "%default")) - continue; - - DBG2(DBG_APP, "Loading ca '%s'", sca->name); - ca = malloc_thing(starter_ca_t); - - ca_default(sca->name, ca, &cfg->ca_default); - ca->kw = sca->kw; - ca->next = NULL; + INIT(ca, + .name = strdup(name), + ); + load_ca(ca, cfg, parser); - previous_err = cfg->err; - load_ca(ca, ca->kw, cfg); if (cfg->err > previous_err) { - /* errors occurred - free the ca */ confread_free_ca(ca); cfg->non_fatal_err += cfg->err - previous_err; cfg->err = previous_err; } else { - /* success - insert the ca into the chained list */ if (cfg->ca_last) + { cfg->ca_last->next = ca; + } cfg->ca_last = ca; if (!cfg->ca_first) + { cfg->ca_first = ca; + } + if (ca->startup != STARTUP_NO) + { + ca->state = STATE_TO_ADD; + } } } + enumerator->destroy(enumerator); - for (ca = cfg->ca_first; ca; ca = ca->next) - { - also_t *also = ca->also; - - while (also != NULL) - { - kw_list_t *kw = find_also_ca(also->name, cfg->ca_first, cfg); - - load_ca(ca, kw, cfg); - also = also->next; - } - - if (ca->startup != STARTUP_NO) - ca->state = STATE_TO_ADD; - } - - /* find %default conn sections */ - for (sconn = cfgp->conn_first; sconn; sconn = sconn->next) - { - if (streq(sconn->name, "%default")) - { - DBG2(DBG_APP, "Loading conn %%default"); - load_conn(&cfg->conn_default, sconn->kw, cfg); - } - } - - /* parameters defined in conn %default sections can be overloaded */ - cfg->conn_default.seen = SEEN_NONE; - cfg->conn_default.right.seen = SEEN_NONE; - cfg->conn_default.left.seen = SEEN_NONE; - - /* load other conn sections */ - for (sconn = cfgp->conn_first; sconn; sconn = sconn->next) + /* load conn sections */ + enumerator = parser->get_sections(parser, CONF_PARSER_CONN); + while (enumerator->enumerate(enumerator, &name)) { - u_int previous_err; - - /* skip %default conn section */ - if (streq(sconn->name, "%default")) - continue; - - DBG2(DBG_APP, "Loading conn '%s'", sconn->name); - conn = malloc_thing(starter_conn_t); + u_int previous_err = cfg->err; + starter_conn_t *conn; - conn_default(sconn->name, conn, &cfg->conn_default); - conn->kw = sconn->kw; - conn->next = NULL; + INIT(conn, + .name = strdup(name), + ); + conn_defaults(conn); + load_conn(conn, cfg, parser); - previous_err = cfg->err; - load_conn(conn, conn->kw, cfg); if (cfg->err > previous_err) { - /* error occurred - free the conn */ confread_free_conn(conn); cfg->non_fatal_err += cfg->err - previous_err; cfg->err = previous_err; } else { - /* success - insert the conn into the chained list */ if (cfg->conn_last) + { cfg->conn_last->next = conn; + } cfg->conn_last = conn; if (!cfg->conn_first) + { cfg->conn_first = conn; + } + if (conn->startup != STARTUP_NO) + { + conn->state = STATE_TO_ADD; + } } } + enumerator->destroy(enumerator); - /* in the second round do not parse also statements */ - cfg->parse_also = FALSE; - - for (ca = cfg->ca_first; ca; ca = ca->next) - { - ca->visit = ++visit; - load_also_cas(ca, ca->also, cfg); - - if (ca->startup != STARTUP_NO) - ca->state = STATE_TO_ADD; - } - - for (conn = cfg->conn_first; conn; conn = conn->next) - { - conn->visit = ++visit; - load_also_conns(conn, conn->also, cfg); - - if (conn->startup != STARTUP_NO) - conn->state = STATE_TO_ADD; - } - - parser_free_conf(cfgp); + parser->destroy(parser); total_err = cfg->err + cfg->non_fatal_err; if (total_err > 0) @@ -1005,6 +815,5 @@ starter_config_t* confread_load(const char *file) DBG1(DBG_APP, "### %d parsing error%s (%d fatal) ###", total_err, (total_err > 1)?"s":"", cfg->err); } - return cfg; } diff --git a/src/starter/confread.h b/src/starter/confread.h index a32f8cba4..0eea98849 100644 --- a/src/starter/confread.h +++ b/src/starter/confread.h @@ -18,13 +18,6 @@ #include <kernel/kernel_ipsec.h> -#include "ipsec-parser.h" - -/** to mark seen keywords */ -typedef u_int64_t seen_t; -#define SEEN_NONE 0; -#define SEEN_KW(kw, base) ((seen_t)1 << ((kw) - (base))) - typedef enum { STARTUP_NO, STARTUP_ADD, @@ -92,7 +85,6 @@ typedef enum { typedef struct starter_end starter_end_t; struct starter_end { - seen_t seen; char *auth; char *auth2; char *id; @@ -121,22 +113,10 @@ struct starter_end { char *dns; }; -typedef struct also also_t; - -struct also { - char *name; - bool included; - also_t *next; -}; - typedef struct starter_conn starter_conn_t; struct starter_conn { - seen_t seen; char *name; - also_t *also; - kw_list_t *kw; - u_int visit; startup_t startup; starter_state_t state; @@ -193,11 +173,7 @@ struct starter_conn { typedef struct starter_ca starter_ca_t; struct starter_ca { - seen_t seen; char *name; - also_t *also; - kw_list_t *kw; - u_int visit; startup_t startup; starter_state_t state; @@ -217,7 +193,6 @@ typedef struct starter_config starter_config_t; struct starter_config { struct { - seen_t seen; bool charonstart; char *charondebug; bool uniqueids; @@ -229,23 +204,14 @@ struct starter_config { u_int err; u_int non_fatal_err; - /* do we parse also statements */ - bool parse_also; - - /* ca %default */ - starter_ca_t ca_default; - - /* connections list (without %default) */ + /* connections list */ starter_ca_t *ca_first, *ca_last; - /* conn %default */ - starter_conn_t conn_default; - - /* connections list (without %default) */ + /* connections list */ starter_conn_t *conn_first, *conn_last; }; -extern starter_config_t *confread_load(const char *file); -extern void confread_free(starter_config_t *cfg); +starter_config_t *confread_load(const char *file); +void confread_free(starter_config_t *cfg); #endif /* _IPSEC_CONFREAD_H_ */ diff --git a/src/starter/ipsec-parser.h b/src/starter/ipsec-parser.h deleted file mode 100644 index 1c6cf20ef..000000000 --- a/src/starter/ipsec-parser.h +++ /dev/null @@ -1,55 +0,0 @@ -/* strongSwan config file parser - * Copyright (C) 2001-2002 Mathieu Lafon - Arkoon Network Security - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * for more details. - */ - -#ifndef _IPSEC_PARSER_H_ -#define _IPSEC_PARSER_H_ - -#include "keywords.h" - -typedef struct kw_entry kw_entry_t; - -struct kw_entry { - char *name; - kw_token_t token; -}; - -typedef struct kw_list kw_list_t; - -struct kw_list { - kw_entry_t *entry; - char *value; - kw_list_t *next; -}; - -typedef struct section_list section_list_t; - -struct section_list { - char *name; - kw_list_t *kw; - section_list_t *next; -}; - -typedef struct config_parsed config_parsed_t; - -struct config_parsed { - kw_list_t *config_setup; - section_list_t *conn_first, *conn_last; - section_list_t *ca_first, *ca_last; -}; - -config_parsed_t *parser_load_conf (const char *file); -void parser_free_conf (config_parsed_t *cfg); - -#endif /* _IPSEC_PARSER_H_ */ - diff --git a/src/starter/keywords.h b/src/starter/keywords.h index 5b6b28bf8..94af493f8 100644 --- a/src/starter/keywords.h +++ b/src/starter/keywords.h @@ -16,7 +16,10 @@ #ifndef _KEYWORDS_H_ #define _KEYWORDS_H_ -typedef enum { +typedef enum kw_token_t kw_token_t; +typedef struct kw_entry_t kw_entry_t; + +enum kw_token_t { /* config setup keywords */ KW_CHARONDEBUG, KW_UNIQUEIDS, @@ -185,6 +188,11 @@ typedef enum { KW_ALSO, KW_AUTO, -} kw_token_t; +}; + +struct kw_entry_t { + char *name; + kw_token_t token; +}; #endif /* _KEYWORDS_H_ */ diff --git a/src/starter/lexer.l b/src/starter/lexer.l deleted file mode 100644 index 734776a74..000000000 --- a/src/starter/lexer.l +++ /dev/null @@ -1,215 +0,0 @@ -%option noinput -%option nounput -%{ -/* FreeS/WAN config file parser (parser.l) - * Copyright (C) 2001 Mathieu Lafon - Arkoon Network Security - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * for more details. - */ - -#include <string.h> -#include <stdlib.h> - -#ifdef HAVE_GLOB_H -#include <glob.h> -#endif - -#include "parser.h" - -#define MAX_INCLUDE_DEPTH 20 - -extern void yyerror(const char *); -extern int yylex(void); -extern int yylex_destroy(void); - -static struct { - int stack_ptr; - YY_BUFFER_STATE stack[MAX_INCLUDE_DEPTH]; - FILE *file[MAX_INCLUDE_DEPTH]; - unsigned int line[MAX_INCLUDE_DEPTH]; - char *filename[MAX_INCLUDE_DEPTH]; -} __parser_y_private; - -void _parser_y_error(char *b, int size, const char *s); -void _parser_y_init (const char *f); -void _parser_y_fini (void); -int _parser_y_include (const char *filename); - -void _parser_y_error(char *b, int size, const char *s) -{ - extern char *yytext; // was: char yytext[]; - - snprintf(b, size, "%s:%d: %s [%s]", - __parser_y_private.filename[__parser_y_private.stack_ptr], - __parser_y_private.line[__parser_y_private.stack_ptr], - s, yytext); -} - -void _parser_y_init (const char *f) -{ - memset(&__parser_y_private, 0, sizeof(__parser_y_private)); - __parser_y_private.line[0] = 1; - __parser_y_private.filename[0] = strdup(f); -} - -void _parser_y_fini (void) -{ - unsigned int i; - - for (i = 0; i < MAX_INCLUDE_DEPTH; i++) - { - if (__parser_y_private.filename[i]) - free(__parser_y_private.filename[i]); - if (__parser_y_private.file[i]) - fclose(__parser_y_private.file[i]); - } - memset(&__parser_y_private, 0, sizeof(__parser_y_private)); - yylex_destroy(); -} - -/** - * parse the file located at filename - */ -int include_file(char *filename) -{ - unsigned int p = __parser_y_private.stack_ptr + 1; - FILE *f; - - if (p >= MAX_INCLUDE_DEPTH) - { - yyerror("max inclusion depth reached"); - return 1; - } - - f = fopen(filename, "r"); - if (!f) - { - yyerror("can't open include filename"); - return 0; /* ignore this error */ - } - - __parser_y_private.stack_ptr++; - __parser_y_private.file[p] = f; - __parser_y_private.stack[p] = YY_CURRENT_BUFFER; - __parser_y_private.line[p] = 1; - __parser_y_private.filename[p] = strdup(filename); - - yy_switch_to_buffer(yy_create_buffer(f, YY_BUF_SIZE)); - return 0; -} - -int _parser_y_include (const char *filename) -{ - int ret = 0; -#ifdef HAVE_GLOB_H - { - glob_t files; - int i; - - ret = glob(filename, GLOB_ERR, NULL, &files); - if (ret) - { - const char *err; - - switch (ret) - { - case GLOB_NOSPACE: - err = "include files ran out of memory"; - break; - case GLOB_ABORTED: - err = "include files aborted due to read error"; - break; - case GLOB_NOMATCH: - err = "include files found no matches"; - break; - default: - err = "unknown include files error"; - } - globfree(&files); - yyerror(err); - return 1; - } - - for (i = 0; i < files.gl_pathc; i++) - { - if ((ret = include_file(files.gl_pathv[i]))) - { - break; - } - } - globfree(&files); - } -#else /* HAVE_GLOB_H */ - /* if glob(3) is not available, try to load pattern directly */ - ret = include_file(filename); -#endif /* HAVE_GLOB_H */ - return ret; -} - -%} - -%% - -<<EOF>> { - if (__parser_y_private.filename[__parser_y_private.stack_ptr]) { - free(__parser_y_private.filename[__parser_y_private.stack_ptr]); - __parser_y_private.filename[__parser_y_private.stack_ptr] = NULL; - } - if (__parser_y_private.file[__parser_y_private.stack_ptr]) { - fclose(__parser_y_private.file[__parser_y_private.stack_ptr]); - __parser_y_private.file[__parser_y_private.stack_ptr] = NULL; - yy_delete_buffer (YY_CURRENT_BUFFER); - yy_switch_to_buffer - (__parser_y_private.stack[__parser_y_private.stack_ptr]); - } - if (--__parser_y_private.stack_ptr < 0) { - yyterminate(); - } -} - -^[\t ]+ return FIRST_SPACES; - -[\t ]+ /* ignore spaces in line */ ; - -= return EQUAL; - -\n|#.*\n { - __parser_y_private.line[__parser_y_private.stack_ptr]++; - return EOL; - } - -config return CONFIG; -setup return SETUP; -conn return CONN; -ca return CA; -include return INCLUDE; -version return FILE_VERSION; - -[^\"= \t\n]+ { - yylval.s = strdup(yytext); - return STRING; - } - -\"[^\"\n]*\" { - yylval.s = strdup(yytext+1); - if (yylval.s) yylval.s[strlen(yylval.s)-1]='\0'; - return STRING; - } - -. yyerror(yytext); - -%% - -int yywrap(void) -{ - return 1; -} - diff --git a/src/starter/parser.y b/src/starter/parser.y deleted file mode 100644 index 2cf0501f4..000000000 --- a/src/starter/parser.y +++ /dev/null @@ -1,272 +0,0 @@ -%{ -/* strongSwan config file parser (parser.y) - * Copyright (C) 2001 Mathieu Lafon - Arkoon Network Security - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * for more details. - */ - -#include <stdio.h> -#include <stdlib.h> -#include <string.h> - -#include <library.h> -#include <utils/debug.h> - -#include "ipsec-parser.h" - -#define YYERROR_VERBOSE -#define ERRSTRING_LEN 256 - -/** - * Bison - */ -static char parser_errstring[ERRSTRING_LEN+1]; - -extern void yyerror(const char *s); -extern int yylex (void); -extern void _parser_y_error(char *b, int size, const char *s); - -/** - * Static Globals - */ -static int _save_errors_; -static config_parsed_t *_parser_cfg; -static kw_list_t **_parser_kw, *_parser_kw_last; -static char errbuf[ERRSTRING_LEN+1]; - -/** - * Gperf - */ -extern kw_entry_t *in_word_set (char *str, unsigned int len); - -%} - -%union { char *s; }; -%token EQUAL FIRST_SPACES EOL CONFIG SETUP CONN CA INCLUDE FILE_VERSION -%token <s> STRING - -%% - -/* - * Config file - */ - -config_file: - config_file section_or_include - | /* NULL */ - ; - -section_or_include: - FILE_VERSION STRING EOL - { - free($2); - } - | CONFIG SETUP EOL - { - _parser_kw = &(_parser_cfg->config_setup); - _parser_kw_last = NULL; - } kw_section - | CONN STRING EOL - { - section_list_t *section = malloc_thing(section_list_t); - - section->name = strdupnull($2); - section->kw = NULL; - section->next = NULL; - _parser_kw = &(section->kw); - if (!_parser_cfg->conn_first) - _parser_cfg->conn_first = section; - if (_parser_cfg->conn_last) - _parser_cfg->conn_last->next = section; - _parser_cfg->conn_last = section; - _parser_kw_last = NULL; - free($2); - } kw_section - | CA STRING EOL - { - section_list_t *section = malloc_thing(section_list_t); - section->name = strdupnull($2); - section->kw = NULL; - section->next = NULL; - _parser_kw = &(section->kw); - if (!_parser_cfg->ca_first) - _parser_cfg->ca_first = section; - if (_parser_cfg->ca_last) - _parser_cfg->ca_last->next = section; - _parser_cfg->ca_last = section; - _parser_kw_last = NULL; - free($2); - } kw_section - | INCLUDE STRING - { - extern void _parser_y_include (const char *f); - _parser_y_include($2); - free($2); - } EOL - | EOL - ; - -kw_section: - FIRST_SPACES statement_kw EOL kw_section - | - ; - -statement_kw: - STRING EQUAL STRING - { - kw_list_t *new; - kw_entry_t *entry = in_word_set($1, strlen($1)); - - if (entry == NULL) - { - snprintf(errbuf, ERRSTRING_LEN, "unknown keyword '%s'", $1); - yyerror(errbuf); - } - else if (_parser_kw) - { - new = (kw_list_t *)malloc_thing(kw_list_t); - new->entry = entry; - new->value = strdupnull($3); - new->next = NULL; - if (_parser_kw_last) - _parser_kw_last->next = new; - _parser_kw_last = new; - if (!*_parser_kw) - *_parser_kw = new; - } - free($1); - free($3); - } - | STRING EQUAL - { - free($1); - } - | - ; - -%% - -void yyerror(const char *s) -{ - if (_save_errors_) - _parser_y_error(parser_errstring, ERRSTRING_LEN, s); -} - -config_parsed_t *parser_load_conf(const char *file) -{ - config_parsed_t *cfg = NULL; - int err = 0; - FILE *f; - - extern void _parser_y_init(const char *f); - extern void _parser_y_fini(void); - extern FILE *yyin; - - memset(parser_errstring, 0, ERRSTRING_LEN+1); - - cfg = (config_parsed_t *)malloc_thing(config_parsed_t); - if (cfg) - { - memset(cfg, 0, sizeof(config_parsed_t)); - f = fopen(file, "r"); - if (f) - { - yyin = f; - _parser_y_init(file); - _save_errors_ = 1; - _parser_cfg = cfg; - - if (yyparse() !=0 ) - { - if (parser_errstring[0] == '\0') - { - snprintf(parser_errstring, ERRSTRING_LEN, "Unknown error..."); - } - _save_errors_ = 0; - while (yyparse() != 0); - err++; - } - else if (parser_errstring[0] != '\0') - { - err++; - } - else - { - /** - * Config valid - */ - } - - fclose(f); - } - else - { - snprintf(parser_errstring, ERRSTRING_LEN, "can't load file '%s'", file); - err++; - } - } - else - { - snprintf(parser_errstring, ERRSTRING_LEN, "can't allocate memory"); - err++; - } - - if (err) - { - DBG1(DBG_APP, "%s", parser_errstring); - - if (cfg) - parser_free_conf(cfg); - cfg = NULL; - } - - _parser_y_fini(); - return cfg; -} - -static void parser_free_kwlist(kw_list_t *list) -{ - kw_list_t *elt; - - while (list) - { - elt = list; - list = list->next; - free(elt->value); - free(elt); - } -} - -void parser_free_conf(config_parsed_t *cfg) -{ - section_list_t *sec; - if (cfg) - { - parser_free_kwlist(cfg->config_setup); - while (cfg->conn_first) - { - sec = cfg->conn_first; - cfg->conn_first = cfg->conn_first->next; - free(sec->name); - parser_free_kwlist(sec->kw); - free(sec); - } - while (cfg->ca_first) - { - sec = cfg->ca_first; - cfg->ca_first = cfg->ca_first->next; - free(sec->name); - parser_free_kwlist(sec->kw); - free(sec); - } - free(cfg); - } -} diff --git a/src/starter/parser/conf_parser.c b/src/starter/parser/conf_parser.c new file mode 100644 index 000000000..119e37961 --- /dev/null +++ b/src/starter/parser/conf_parser.c @@ -0,0 +1,655 @@ +/* + * Copyright (C) 2013-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 <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "conf_parser.h" + +#include <collections/array.h> +#include <collections/hashtable.h> + +/** + * Provided by the generated parser + */ +bool conf_parser_parse_file(conf_parser_t *this, char *file); + +typedef struct private_conf_parser_t private_conf_parser_t; +typedef struct section_t section_t; + +/** + * Private data + */ +struct private_conf_parser_t { + + /** + * Public interface + */ + conf_parser_t public; + + /** + * Path to config file + */ + char *file; + + /** + * TRUE while parsing the config file + */ + bool parsing; + + /** + * Hashtable for ca sections, as section_t + */ + hashtable_t *cas; + + /** + * Hashtable for conn sections, as section_t + */ + hashtable_t *conns; + + /** + * Array to keep track of the order of conn sections, as section_t + */ + array_t *conns_order; + + /** + * config setup section + */ + section_t *config_setup; + + /** + * Pointer to the current section (the one added last) during parsing + */ + section_t *current_section; + + /** + * Refcount for this parser instance (also used by dictionaries) + */ + refcount_t ref; +}; + +typedef struct { + /** Key of the setting */ + char *key; + /** Value of the setting */ + char *value; +} setting_t; + +int setting_find(const void *a, const void *b) +{ + const char *key = a; + const setting_t *setting = b; + return strcmp(key, setting->key); +} + +int setting_sort(const void *a, const void *b, void *user) +{ + const setting_t *sa = a, *sb = b; + return strcmp(sa->key, sb->key); +} + +static void setting_destroy(setting_t *this) +{ + free(this->key); + free(this->value); + free(this); +} + +struct section_t { + /** Name of the section */ + char *name; + /** Sorted array of settings, as setting_t */ + array_t *settings; + /** Array of also= settings (in reversed order, i.e. most important to least + * important), as setting_t */ + array_t *also; + /** Array of linearized parent objects derived from also= settings, in their + * order of importance (most to least, i.e. %default) as section_t + * NULL if not yet determined */ + array_t *parents; +}; + +static section_t *section_create(char *name) +{ + section_t *this; + + INIT(this, + .name = name, + ); + return this; +} + +static void section_destroy(section_t *this) +{ + array_destroy_function(this->settings, (void*)setting_destroy, NULL); + array_destroy_function(this->also, (void*)setting_destroy, NULL); + array_destroy(this->parents); + free(this->name); + free(this); +} + +typedef struct { + /** Public interface */ + dictionary_t public; + /** Parser object */ + private_conf_parser_t *parser; + /** Section object */ + section_t *section; +} section_dictionary_t; + +typedef struct { + /** Public interface */ + enumerator_t public; + /** Current settings enumerator */ + enumerator_t *settings; + /** Enumerator into parent list */ + enumerator_t *parents; + /** Hashtable to keep track of already enumerated settings */ + hashtable_t *seen; +} dictionary_enumerator_t; + +METHOD(enumerator_t, dictionary_enumerate, bool, + dictionary_enumerator_t *this, char **key, char **value) +{ + setting_t *setting; + section_t *parent; + + while (TRUE) + { + if (this->settings && + this->settings->enumerate(this->settings, &setting)) + { + if (this->seen->get(this->seen, setting->key)) + { + continue; + } + this->seen->put(this->seen, setting->key, setting->key); + if (!setting->value) + { + continue; + } + if (key) + { + *key = setting->key; + } + if (value) + { + *value = setting->value; + } + return TRUE; + } + DESTROY_IF(this->settings); + this->settings = NULL; + if (this->parents && + this->parents->enumerate(this->parents, &parent)) + { + if (parent->settings) + { + this->settings = array_create_enumerator(parent->settings); + } + continue; + } + DESTROY_IF(this->parents); + this->parents = NULL; + break; + } + return FALSE; +} + +METHOD(enumerator_t, dictionary_enumerator_destroy, void, + dictionary_enumerator_t *this) +{ + DESTROY_IF(this->settings); + DESTROY_IF(this->parents); + this->seen->destroy(this->seen); + free(this); +} + +METHOD(dictionary_t, dictionary_create_enumerator, enumerator_t*, + section_dictionary_t *this) +{ + dictionary_enumerator_t *enumerator; + + INIT(enumerator, + .public = { + .enumerate = (void*)_dictionary_enumerate, + .destroy = _dictionary_enumerator_destroy, + }, + .seen = hashtable_create(hashtable_hash_str, hashtable_equals_str, 8), + ); + if (this->section->settings) + { + enumerator->settings = array_create_enumerator(this->section->settings); + } + if (this->section->parents) + { + enumerator->parents = array_create_enumerator(this->section->parents); + } + return &enumerator->public; +} + +METHOD(dictionary_t, dictionary_get, void*, + section_dictionary_t *this, const void *key) +{ + enumerator_t *parents; + section_t *section; + setting_t *setting; + char *value = NULL; + + section = this->section; + if (array_bsearch(section->settings, key, setting_find, &setting) != -1) + { + return setting->value; + } + parents = array_create_enumerator(section->parents); + while (parents->enumerate(parents, §ion)) + { + if (array_bsearch(section->settings, key, setting_find, &setting) != -1) + { + value = setting->value; + break; + } + } + parents->destroy(parents); + return value; +} + +METHOD(dictionary_t, dictionary_destroy, void, + section_dictionary_t *this) +{ + this->parser->public.destroy(&this->parser->public); + free(this); +} + +static dictionary_t *section_dictionary_create(private_conf_parser_t *parser, + section_t *section) +{ + section_dictionary_t *this; + + INIT(this, + .public = { + .create_enumerator = _dictionary_create_enumerator, + .get = _dictionary_get, + .destroy = _dictionary_destroy, + }, + .parser = parser, + .section = section, + ); + + ref_get(&parser->ref); + + return &this->public; +} + +static bool conn_filter(void *unused, section_t **section, char **name) +{ + *name = (*section)->name; + return TRUE; +} + +static bool ca_filter(void *unused, void *key, char **name, section_t **section) +{ + *name = (*section)->name; + return TRUE; +} + +METHOD(conf_parser_t, get_sections, enumerator_t*, + private_conf_parser_t *this, conf_parser_section_t type) +{ + switch (type) + { + case CONF_PARSER_CONN: + return enumerator_create_filter( + array_create_enumerator(this->conns_order), + (void*)conn_filter, NULL, NULL); + case CONF_PARSER_CA: + return enumerator_create_filter( + this->cas->create_enumerator(this->cas), + (void*)ca_filter, NULL, NULL); + case CONF_PARSER_CONFIG_SETUP: + default: + return enumerator_create_empty(); + } +} + +METHOD(conf_parser_t, get_section, dictionary_t*, + private_conf_parser_t *this, conf_parser_section_t type, char *name) +{ + section_t *section = NULL; + + switch (type) + { + case CONF_PARSER_CONFIG_SETUP: + section = this->config_setup; + break; + case CONF_PARSER_CONN: + section = this->conns->get(this->conns, name); + break; + case CONF_PARSER_CA: + section = this->cas->get(this->cas, name); + break; + default: + break; + } + return section ? section_dictionary_create(this, section) : NULL; +} + +METHOD(conf_parser_t, add_section, bool, + private_conf_parser_t *this, conf_parser_section_t type, char *name) +{ + hashtable_t *sections = this->conns; + array_t *order = this->conns_order; + section_t *section = NULL; + bool exists = FALSE; + + if (!this->parsing) + { + free(name); + return exists; + } + switch (type) + { + case CONF_PARSER_CONFIG_SETUP: + section = this->config_setup; + /* we don't expect a name, but just in case */ + free(name); + break; + case CONF_PARSER_CA: + sections = this->cas; + order = NULL; + /* fall-through */ + case CONF_PARSER_CONN: + section = sections->get(sections, name); + if (!section) + { + section = section_create(name); + sections->put(sections, name, section); + if (order) + { + array_insert(order, ARRAY_TAIL, section); + } + } + else + { + exists = TRUE; + free(name); + } + break; + + } + this->current_section = section; + return exists; +} + +METHOD(conf_parser_t, add_setting, void, + private_conf_parser_t *this, char *key, char *value) +{ + section_t *section = this->current_section; + setting_t *setting; + + if (!this->parsing || !this->current_section) + { + free(key); + free(value); + return; + } + if (streq(key, "also")) + { + if (!value || !strlen(value) || streq(value, "%default")) + { /* we require a name, but all sections inherit from %default */ + free(key); + free(value); + return; + } + INIT(setting, + .key = key, + .value = value, + ); + array_insert_create(§ion->also, ARRAY_HEAD, setting); + return; + } + if (array_bsearch(section->settings, key, setting_find, &setting) == -1) + { + INIT(setting, + .key = key, + .value = value, + ); + array_insert_create(§ion->settings, ARRAY_TAIL, setting); + array_sort(section->settings, setting_sort, NULL); + } + else + { + free(setting->value); + setting->value = value; + free(key); + } +} + +/** + * Check if the given section is contained in the given array. The search + * starts at the given index. + */ +static bool is_contained_in(array_t *arr, section_t *section) +{ + section_t *current; + int i; + + for (i = 0; i < array_count(arr); i++) + { + array_get(arr, i, ¤t); + if (streq(section->name, current->name)) + { + return TRUE; + } + } + return FALSE; +} + +/** + * This algorithm to linearize sections uses a bottom-first depth-first + * semantic, with an additional elimination step that removes all but the + * last occurrence of each section. + * + * Consider this configuration: + * + * conn A + * conn B + * also=A + * conn C + * also=A + * conn D + * also=C + * also=B + * + * The linearization would yield D B A C A, which gets reduced to D B C A. + * + * Ambiguous configurations are handled pragmatically. + * + * Consider the following configuration: + * + * conn A + * conn B + * conn C + * also=A + * also=B + * conn D + * also=B + * also=A + * conn E + * also=C + * also=D + * + * It is ambiguous because D and C include the same two sections but in + * a different order. + * + * The linearization would yield E D A B C B A which gets reduced to E D C B A. + */ +static bool resolve_also_single(hashtable_t *sections, + section_t *section, section_t *def, array_t *stack) +{ + enumerator_t *enumerator; + array_t *parents; + section_t *parent, *grandparent; + setting_t *also; + bool success = TRUE; + int i; + + array_insert(stack, ARRAY_HEAD, section); + parents = array_create(0, 0); + enumerator = array_create_enumerator(section->also); + while (enumerator->enumerate(enumerator, &also)) + { + parent = sections->get(sections, also->value); + if (!parent || is_contained_in(stack, parent)) + { + if (!parent) + { + DBG1(DBG_CFG, "section '%s' referenced in section '%s' not " + "found", also->value, section->name); + } + else + { + DBG1(DBG_CFG, "section '%s' referenced in section '%s' causes " + "a loop", parent->name, section->name); + } + array_remove_at(section->also, enumerator); + setting_destroy(also); + success = FALSE; + continue; + } + if (!parent->parents) + { + if (!resolve_also_single(sections, parent, def, stack)) + { + success = FALSE; + continue; + } + } + /* add the grandparents and the parent to the list */ + array_insert(parents, ARRAY_TAIL, parent); + for (i = 0; i < array_count(parent->parents); i++) + { + array_get(parent->parents, i, &grandparent); + array_insert(parents, ARRAY_TAIL, grandparent); + } + } + enumerator->destroy(enumerator); + array_remove(stack, ARRAY_HEAD, NULL); + + if (success && def && !array_count(parents)) + { + array_insert(parents, ARRAY_TAIL, def); + } + + while (success && array_remove(parents, ARRAY_HEAD, &parent)) + { + if (!is_contained_in(parents, parent)) + { /* last occurrence of this section */ + array_insert_create(§ion->parents, ARRAY_TAIL, parent); + } + } + array_destroy(parents); + return success; +} + +/** + * Resolve also= statements. The functions returns TRUE if everything is fine, + * or FALSE if either a referenced section does not exist, or if the section + * inheritance can't be determined properly (e.g. if there are loops or if a + * section inherits from multiple sections - perhaps over several levels - in + * an ambiguous way). + */ +static bool resolve_also(hashtable_t *sections) +{ + enumerator_t *enumerator; + section_t *def, *section; + array_t *stack; + bool success = TRUE; + + stack = array_create(0, 0); + + def = sections->get(sections, "%default"); + if (def) + { /* the default section is the only one with an empty parents list */ + def->parents = array_create(0, 0); + } + enumerator = sections->create_enumerator(sections); + while (enumerator->enumerate(enumerator, NULL, §ion)) + { + if (section->parents) + { /* already determined */ + continue; + } + success = resolve_also_single(sections, section, def, stack) && success; + } + enumerator->destroy(enumerator); + array_destroy(stack); + return success; +} + +METHOD(conf_parser_t, parse, bool, + private_conf_parser_t *this) +{ + bool success; + + if (!this->file) + { /* no file, lets assume this is OK */ + return TRUE; + } + this->parsing = TRUE; + success = conf_parser_parse_file(&this->public, this->file); + this->parsing = FALSE; + return success && resolve_also(this->conns) && resolve_also(this->cas); +} + +METHOD(conf_parser_t, destroy, void, + private_conf_parser_t *this) +{ + if (ref_put(&this->ref)) + { + this->cas->destroy_function(this->cas, (void*)section_destroy); + this->conns->destroy_function(this->conns, (void*)section_destroy); + section_destroy(this->config_setup); + array_destroy(this->conns_order); + free(this->file); + free(this); + } +} + +/* + * Described in header + */ +conf_parser_t *conf_parser_create(const char *file) +{ + private_conf_parser_t *this; + + INIT(this, + .public = { + .parse = _parse, + .get_sections = _get_sections, + .get_section = _get_section, + .add_section = _add_section, + .add_setting = _add_setting, + .destroy = _destroy, + }, + .file = strdupnull(file), + .cas = hashtable_create(hashtable_hash_str, + hashtable_equals_str, 8), + .conns = hashtable_create(hashtable_hash_str, + hashtable_equals_str, 8), + .conns_order = array_create(0, 0), + .config_setup = section_create(NULL), + .ref = 1, + ); + + return &this->public; +} diff --git a/src/starter/parser/conf_parser.h b/src/starter/parser/conf_parser.h new file mode 100644 index 000000000..a55b7cb53 --- /dev/null +++ b/src/starter/parser/conf_parser.h @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2013-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 <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +/** + * @defgroup conf_parser conf_parser + * @{ @ingroup starter + */ + +#ifndef CONF_PARSER_H_ +#define CONF_PARSER_H_ + +#include <library.h> +#include <collections/dictionary.h> + +typedef enum conf_parser_section_t conf_parser_section_t; +typedef struct conf_parser_t conf_parser_t; + +/** + * Type of section + */ +enum conf_parser_section_t { + /** + * config setup + */ + CONF_PARSER_CONFIG_SETUP, + + /** + * conn <name> + */ + CONF_PARSER_CONN, + + /** + * ca <name> + */ + CONF_PARSER_CA, +}; + +/** + * Parser for ipsec.conf + */ +struct conf_parser_t { + + /** + * Parse the config file. + * + * @return TRUE if config file was parsed successfully + */ + bool (*parse)(conf_parser_t *this); + + /** + * Get the names of all sections of the given type. + * + * @note Returns an empty enumerator for the config setup section. + * + * @return enumerator over char* + */ + enumerator_t *(*get_sections)(conf_parser_t *this, + conf_parser_section_t type); + + /** + * Get the section with the given type and name. + * + * @note The name is ignored for the config setup section. + * + * @return dictionary with settings + */ + dictionary_t *(*get_section)(conf_parser_t *this, + conf_parser_section_t type, char *name); + + /** + * Add a section while parsing. + * + * @note This method can only be called while parsing the config file. + * + * @param type type of section to add + * @param name name of the section, if applicable (gets adopted) + * @return TRUE if the section already existed (settings get added) + */ + bool (*add_section)(conf_parser_t *this, conf_parser_section_t type, + char *name); + + /** + * Add a key/value pair to the latest section. + * + * @note This method can only be called while parsing the config file. + * + * @param name key string (gets adopted) + * @param value optional value string (gets adopted), if no value is + * specified the key is set empty + */ + void (*add_setting)(conf_parser_t *this, char *key, char *value); + + + /** + * Destroy a conf_parser_t instance. + */ + void (*destroy)(conf_parser_t *this); +}; + +/** + * Create a conf_parser_t instance. + * + * @param file ipsec.conf file to parse (gets copied) + * @return conf_parser_t instance + */ +conf_parser_t *conf_parser_create(const char *file); + +#endif /** CONF_PARSER_H_ @}*/
\ No newline at end of file diff --git a/src/starter/parser/lexer.l b/src/starter/parser/lexer.l new file mode 100644 index 000000000..a88cbe809 --- /dev/null +++ b/src/starter/parser/lexer.l @@ -0,0 +1,205 @@ +%{ +/* + * Copyright (C) 2013-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 <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include <utils/parser_helper.h> +#include <parser/conf_parser.h> + +#include "parser.h" + +bool conf_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="conf_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 ]*"version"[^\n]*$ /* eat legacy version delcaration */ +^[\t ]+ return SPACES; +[\t ]+ /* eat other whitespace */ +[\t ]*#[^\n]* /* eat comments */ + +\n return NEWLINE; + +"=" return EQ; +^"config setup" return CONFIG_SETUP; +^"conn" return CONN; +^"ca" return CA; + +"include"[\t ]+/[^=] { + yyextra->string_init(yyextra); + yy_push_state(inc, yyscanner); +} + +"\"" { + yyextra->string_init(yyextra); + yy_push_state(str, yyscanner); +} + +(@#)?[^\"#= \t\n]+ { + yylval->s = strdup(yytext); + return STRING; +} + +<inc>{ + /* we allow all characters except # and spaces, they can be escaped */ + <<EOF>> | + [#\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 '#': + /* comments 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); + } +} + +<str>{ + "\"" | + <<EOF>> | + \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); + } +} + +<<EOF>> { + conf_parser_pop_buffer_state(yyscanner); + if (!conf_parser_open_next_file(yyextra) && !YY_CURRENT_BUFFER) + { + yyterminate(); + } +} + +%% + +/** + * Open the next file, if any is queued and readable, otherwise returns FALSE. + */ +bool conf_parser_open_next_file(parser_helper_t *ctx) +{ + FILE *file; + + file = ctx->file_next(ctx); + if (!file) + { + return FALSE; + } + + conf_parser_set_in(file, ctx->scanner); + conf_parser_push_buffer_state( + conf_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); + + conf_parser_open_next_file(ctx); +} diff --git a/src/starter/parser/parser.y b/src/starter/parser/parser.y new file mode 100644 index 000000000..54dedc12b --- /dev/null +++ b/src/starter/parser/parser.y @@ -0,0 +1,254 @@ +%{ +/* + * Copyright (C) 2013-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 <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#define _GNU_SOURCE /* for asprintf() */ +#include <stdio.h> + +#include <utils/parser_helper.h> +#include <settings/settings_types.h> +#include <parser/conf_parser.h> + +#include "parser.h" + +#define YYDEBUG 1 + +/** + * Defined by the lexer + */ +int conf_parser_lex(YYSTYPE *lvalp, void *scanner); +int conf_parser_lex_init_extra(parser_helper_t *extra, void *scanner); +int conf_parser_lex_destroy(void *scanner); +int conf_parser_set_in(FILE *in, void *scanner); +void conf_parser_set_debug(int debug, void *scanner); +char *conf_parser_get_text(void *scanner); +int conf_parser_get_leng(void *scanner); +int conf_parser_get_lineno(void *scanner); +/* Custom functions in lexer */ +bool conf_parser_open_next_file(parser_helper_t *ctx); + +/** + * Forward declaration + */ +static void conf_parser_error(parser_helper_t *ctx, const char *s); + +/** + * Make sure to call lexer with the proper context + */ +#undef yylex +static int yylex(YYSTYPE *lvalp, parser_helper_t *ctx) +{ + return conf_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 "conf_parser_" + +/* interact properly with the reentrant lexer */ +%lex-param {parser_helper_t *ctx} +%parse-param {parser_helper_t *ctx} + +/* types for terminal symbols... */ +%union { + char *s; + conf_parser_section_t t; +} +%token <s> STRING +%token EQ SPACES NEWLINE CONFIG_SETUP CONN CA + +/* ...and other symbols */ +%type <t> section_type +%type <s> section_name value + +/* make the equal sign left associative */ +%left EQ + +/* properly destroy STRING tokens, which are strdup()ed, on errors */ +%destructor { free($$); } STRING section_name value + +/* there are two shift/reduce conflicts because we allow empty lines (and lines + * with spaces) within settings and anywhere else (i.e. in the beginning) */ +//%expect 2 + +%% + +/** + * ipsec.conf grammar rules + */ +statements: + /* empty */ + | statements NEWLINE + | statements statement + ; + +statement: + section + | SPACES setting + ; + +section: + section_type section_name + { + if ($1 != CONF_PARSER_CONFIG_SETUP && (!$2 || !strlen($2))) + { + PARSER_DBG1(ctx, "section name missing"); + free($2); + YYERROR; + } + conf_parser_t *parser = (conf_parser_t*)ctx->context; + parser->add_section(parser, $1, $2); + } + ; + +section_type: + CONFIG_SETUP + { + $$ = CONF_PARSER_CONFIG_SETUP; + } + | + CONN + { + $$ = CONF_PARSER_CONN; + } + | + CA + { + $$ = CONF_PARSER_CA; + } + ; + +section_name: + /* empty */ + { + $$ = NULL; + } + | STRING + { + $$ = $1; + } + ; + +setting: + /* empty */ + | + STRING EQ value + { + if (!strlen($1)) + { + PARSER_DBG1(ctx, "setting name can't be empty"); + free($1); + free($3); + YYERROR; + } + conf_parser_t *parser = (conf_parser_t*)ctx->context; + parser->add_setting(parser, $1, $value); + } + | + STRING EQ + { + if (!strlen($1)) + { + PARSER_DBG1(ctx, "setting name can't be empty"); + free($1); + YYERROR; + } + conf_parser_t *parser = (conf_parser_t*)ctx->context; + parser->add_setting(parser, $1, NULL); + } + | + STRING + { + PARSER_DBG1(ctx, "missing value for setting '%s'", $1); + free($1); + YYERROR; + } + ; + +value: + STRING + | value STRING + { /* 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); + } + ; + +%% + +/** + * Referenced by the generated parser + */ +static void conf_parser_error(parser_helper_t *ctx, const char *s) +{ + char *text = conf_parser_get_text(ctx->scanner); + int len = conf_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, (int)len, text); +} + +/** + * Parse the given file + */ +bool conf_parser_parse_file(conf_parser_t *this, char *name) +{ + parser_helper_t *helper; + bool success = FALSE; + + helper = parser_helper_create(this); + helper->get_lineno = conf_parser_get_lineno; + if (conf_parser_lex_init_extra(helper, &helper->scanner) != 0) + { + helper->destroy(helper); + return FALSE; + } + helper->file_include(helper, name); + if (!conf_parser_open_next_file(helper)) + { + DBG1(DBG_CFG, "failed to open config file '%s'", name); + } + else + { + if (getenv("DEBUG_CONF_PARSER")) + { + yydebug = 1; + conf_parser_set_debug(1, helper->scanner); + } + success = yyparse(helper) == 0; + if (!success) + { + DBG1(DBG_CFG, "invalid config file '%s'", name); + } + } + conf_parser_lex_destroy(helper->scanner); + helper->destroy(helper); + return success; +} diff --git a/src/starter/starter.c b/src/starter/starter.c index 33916c95c..ef5780843 100644 --- a/src/starter/starter.c +++ b/src/starter/starter.c @@ -418,6 +418,7 @@ int main (int argc, char **argv) bool no_fork = FALSE; bool attach_gdb = FALSE; bool load_warning = FALSE; + bool conftest = FALSE; library_init(NULL, "starter"); atexit(library_deinit); @@ -467,6 +468,10 @@ int main (int argc, char **argv) { config_file = argv[++i]; } + else if (streq(argv[i], "--conftest")) + { + conftest = TRUE; + } else { usage(argv[0]); @@ -485,6 +490,28 @@ int main (int argc, char **argv) init_log("ipsec_starter"); + if (conftest) + { + int status = LSB_RC_SUCCESS; + + cfg = confread_load(config_file); + if (cfg == NULL || cfg->err > 0) + { + DBG1(DBG_APP, "config invalid!"); + status = LSB_RC_INVALID_ARGUMENT; + } + else + { + DBG1(DBG_APP, "config OK"); + } + if (cfg) + { + confread_free(cfg); + } + cleanup(); + exit(status); + } + DBG1(DBG_APP, "Starting %sSwan "VERSION" IPsec [starter]...", lib->settings->get_bool(lib->settings, "charon.i_dont_care_about_security_and_use_aggressive_mode_psk", diff --git a/src/starter/tests/.gitignore b/src/starter/tests/.gitignore new file mode 100644 index 000000000..c4c6471e8 --- /dev/null +++ b/src/starter/tests/.gitignore @@ -0,0 +1 @@ +starter_tests
\ No newline at end of file diff --git a/src/starter/tests/Makefile.am b/src/starter/tests/Makefile.am new file mode 100644 index 000000000..f84327b6b --- /dev/null +++ b/src/starter/tests/Makefile.am @@ -0,0 +1,19 @@ +TESTS = starter_tests + +check_PROGRAMS = $(TESTS) + +starter_tests_SOURCES = \ + suites/test_parser.c \ + starter_tests.h starter_tests.c + +starter_tests_CFLAGS = \ + -I$(top_srcdir)/src/libstrongswan \ + -I$(top_srcdir)/src/libstrongswan/tests \ + -I$(top_srcdir)/src/starter \ + @COVERAGE_CFLAGS@ + +starter_tests_LDFLAGS = @COVERAGE_LDFLAGS@ +starter_tests_LDADD = \ + $(top_builddir)/src/libstrongswan/libstrongswan.la \ + $(top_builddir)/src/libstrongswan/tests/libtest.la \ + ../libstarter.la diff --git a/src/starter/tests/starter_tests.c b/src/starter/tests/starter_tests.c new file mode 100644 index 000000000..4194c5256 --- /dev/null +++ b/src/starter/tests/starter_tests.c @@ -0,0 +1,43 @@ +/* + * 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 <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include <test_runner.h> + +/* declare test suite constructors */ +#define TEST_SUITE(x) test_suite_t* x(); +#include "starter_tests.h" +#undef TEST_SUITE + +static test_configuration_t tests[] = { +#define TEST_SUITE(x) \ + { .suite = x, }, +#include "starter_tests.h" + { .suite = NULL, } +}; + +static bool test_runner_init(bool init) +{ + if (!init) + { + lib->processor->set_threads(lib->processor, 0); + lib->processor->cancel(lib->processor); + } + return TRUE; +} + +int main(int argc, char *argv[]) +{ + return test_runner_run("stroke", tests, test_runner_init); +} diff --git a/src/starter/tests/starter_tests.h b/src/starter/tests/starter_tests.h new file mode 100644 index 000000000..3486597a0 --- /dev/null +++ b/src/starter/tests/starter_tests.h @@ -0,0 +1,16 @@ +/* + * 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 <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +TEST_SUITE(parser_suite_create) diff --git a/src/starter/tests/suites/test_parser.c b/src/starter/tests/suites/test_parser.c new file mode 100644 index 000000000..684bc2a27 --- /dev/null +++ b/src/starter/tests/suites/test_parser.c @@ -0,0 +1,527 @@ +/* + * 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 <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include <unistd.h> + +#include <test_suite.h> + +#include "../../parser/conf_parser.h" + +static char *path = "/tmp/strongswan-starter-parser-test"; +static conf_parser_t *parser; + +static void create_parser(chunk_t contents) +{ + ck_assert(chunk_write(contents, path, 0022, TRUE)); + parser = conf_parser_create(path); +} + +START_TEARDOWN(teardown_parser) +{ + parser->destroy(parser); + unlink(path); +} +END_TEARDOWN + +START_TEST(test_get_sections_config_setup) +{ + enumerator_t *enumerator; + + create_parser(chunk_from_str("")); + ck_assert(parser->parse(parser)); + enumerator = parser->get_sections(parser, CONF_PARSER_CONFIG_SETUP); + ck_assert(enumerator); + ck_assert(!enumerator->enumerate(enumerator, NULL)); + enumerator->destroy(enumerator); + parser->destroy(parser); + + create_parser(chunk_from_str("config setup\n\tfoo=bar")); + ck_assert(parser->parse(parser)); + enumerator = parser->get_sections(parser, CONF_PARSER_CONFIG_SETUP); + ck_assert(enumerator); + ck_assert(!enumerator->enumerate(enumerator, NULL)); + enumerator->destroy(enumerator); +} +END_TEST + +START_TEST(test_get_sections_conn) +{ + enumerator_t *enumerator; + char *name; + + create_parser(chunk_from_str("")); + ck_assert(parser->parse(parser)); + enumerator = parser->get_sections(parser, CONF_PARSER_CONN); + ck_assert(enumerator); + ck_assert(!enumerator->enumerate(enumerator, NULL)); + enumerator->destroy(enumerator); + parser->destroy(parser); + + create_parser(chunk_from_str( + "conn foo\n" + "conn bar\n" + "conn foo\n")); + ck_assert(parser->parse(parser)); + enumerator = parser->get_sections(parser, CONF_PARSER_CONN); + ck_assert(enumerator); + ck_assert(enumerator->enumerate(enumerator, &name)); + ck_assert_str_eq("foo", name); + ck_assert(enumerator->enumerate(enumerator, &name)); + ck_assert_str_eq("bar", name); + ck_assert(!enumerator->enumerate(enumerator, &name)); + enumerator->destroy(enumerator); +} +END_TEST + +START_TEST(test_get_section_config_setup) +{ + dictionary_t *dict; + + create_parser(chunk_from_str("")); + ck_assert(parser->parse(parser)); + dict = parser->get_section(parser, CONF_PARSER_CONFIG_SETUP, "foo"); + ck_assert(dict); + dict->destroy(dict); + parser->destroy(parser); + + create_parser(chunk_from_str("config setup\n\tfoo=bar")); + ck_assert(parser->parse(parser)); + dict = parser->get_section(parser, CONF_PARSER_CONFIG_SETUP, NULL); + ck_assert(dict); + dict->destroy(dict); + parser->destroy(parser); + + create_parser(chunk_from_str("config setup\n\tfoo=bar")); + ck_assert(parser->parse(parser)); + dict = parser->get_section(parser, CONF_PARSER_CONFIG_SETUP, "foo"); + ck_assert(dict); + dict->destroy(dict); +} +END_TEST + +START_TEST(test_get_section_conn) +{ + dictionary_t *dict; + + create_parser(chunk_from_str("")); + ck_assert(parser->parse(parser)); + dict = parser->get_section(parser, CONF_PARSER_CONN, "foo"); + ck_assert(!dict); + parser->destroy(parser); + + create_parser(chunk_from_str("conn foo\n")); + ck_assert(parser->parse(parser)); + dict = parser->get_section(parser, CONF_PARSER_CONN, "foo"); + ck_assert(!parser->get_section(parser, CONF_PARSER_CONN, "bar")); + ck_assert(dict); + dict->destroy(dict); + parser->destroy(parser); + + create_parser(chunk_from_str("conn foo\n\tfoo=bar")); + ck_assert(parser->parse(parser)); + dict = parser->get_section(parser, CONF_PARSER_CONN, "foo"); + ck_assert(dict); + dict->destroy(dict); +} +END_TEST + +START_TEST(test_enumerate_values) +{ + enumerator_t *enumerator; + dictionary_t *dict; + char *key, *value; + int i; + + create_parser(chunk_from_str( + "conn foo\n" + " foo=bar\n" + " bar=baz")); + ck_assert(parser->parse(parser)); + dict = parser->get_section(parser, CONF_PARSER_CONN, "foo"); + ck_assert(dict); + ck_assert_str_eq("bar", dict->get(dict, "foo")); + ck_assert_str_eq("baz", dict->get(dict, "bar")); + enumerator = dict->create_enumerator(dict); + for (i = 0; enumerator->enumerate(enumerator, &key, &value); i++) + { + if ((streq(key, "foo") && !streq(value, "bar")) || + (streq(key, "bar") && !streq(value, "baz"))) + { + fail("unexpected setting %s=%s", key, value); + } + } + enumerator->destroy(enumerator); + ck_assert_int_eq(i, 2); + dict->destroy(dict); +} +END_TEST + +#define extensibility_config(section) \ + section "\n" \ + " foo=bar\n" \ + " dup=one\n" \ + " dup=two\n" \ + "\n" \ + " nope=val\n" \ + "\n" \ + section "\n" \ + " foo=baz\n" \ + section "\n" \ + " answer=42\n" \ + " nope=\n" + +static struct { + char *conf; + conf_parser_section_t type; + char *name; +} extensibility_data[] = { + { extensibility_config("config setup"), CONF_PARSER_CONFIG_SETUP, NULL }, + { extensibility_config("ca ca-foo"), CONF_PARSER_CA, "ca-foo" }, + { extensibility_config("conn conn-foo"), CONF_PARSER_CONN, "conn-foo" }, +}; + +START_TEST(test_extensibility) +{ + dictionary_t *dict; + + create_parser(chunk_from_str(extensibility_data[_i].conf)); + ck_assert(parser->parse(parser)); + + dict = parser->get_section(parser, extensibility_data[_i].type, + extensibility_data[_i].name); + ck_assert(dict); + ck_assert_str_eq("baz", dict->get(dict, "foo")); + ck_assert_str_eq("two", dict->get(dict, "dup")); + ck_assert_str_eq("42", dict->get(dict, "answer")); + ck_assert(!dict->get(dict, "nope")); + ck_assert(!dict->get(dict, "anything")); + dict->destroy(dict); +} +END_TEST + +static struct { + char *conf; + bool check_section; + char *value; +} comments_data[] = { + { "# conn foo", FALSE, NULL }, + { "# conn foo\n", FALSE, NULL }, + { "conn foo # asdf", TRUE, NULL }, + { "conn foo # asdf", TRUE, NULL }, + { "conn foo# asdf\n", TRUE, NULL }, + { "conn foo # asdf\n\tkey=val", TRUE, "val" }, + { "conn foo # asdf\n#\tkey=val", TRUE, NULL }, + { "conn foo # asdf\n\t#key=val", TRUE, NULL }, + { "conn foo # asdf\n\tkey=@#keyid", TRUE, "@#keyid" }, + { "conn foo # asdf\n\tkey=\"@#keyid\"", TRUE, "@#keyid" }, + { "conn foo # asdf\n\tkey=asdf@#keyid", TRUE, "asdf@" }, + { "conn foo # asdf\n\tkey=#val", TRUE, NULL }, + { "conn foo # asdf\n\tkey=val#asdf", TRUE, "val" }, + { "conn foo # asdf\n\tkey=\"val#asdf\"", TRUE, "val#asdf" }, + { "conn foo # asdf\n\tkey=val # asdf\n", TRUE, "val" }, + { "conn foo # asdf\n# asdf\n\tkey=val\n", TRUE, "val" }, + { "conn foo # asdf\n\t# asdf\n\tkey=val\n", TRUE, "val" }, +}; + +START_TEST(test_comments) +{ + dictionary_t *dict; + + create_parser(chunk_from_str(comments_data[_i].conf)); + ck_assert(parser->parse(parser)); + if (comments_data[_i].check_section) + { + dict = parser->get_section(parser, CONF_PARSER_CONN, "foo"); + ck_assert(dict); + if (comments_data[_i].value) + { + ck_assert_str_eq(comments_data[_i].value, dict->get(dict, "key")); + } + else + { + ck_assert(!dict->get(dict, "key")); + } + dict->destroy(dict); + } + else + { + ck_assert(!parser->get_section(parser, CONF_PARSER_CONN, "foo")); + } +} +END_TEST + +static struct { + char *conf; + bool check_section; + char *value; +} whitespace_data[] = { + { "conn foo ", FALSE, NULL }, + { "conn foo", FALSE, NULL }, + { "conn foo\n", FALSE, NULL }, + { "conn foo \n", FALSE, NULL }, + { "conn foo\n ", FALSE, NULL }, + { "conn foo\n \n", FALSE, NULL }, + { "conn foo\nconn bar", TRUE, NULL }, + { "conn foo\n \nconn bar", TRUE, NULL }, + { "conn foo\n key=val", FALSE, "val" }, + { "conn foo\n\tkey=val", FALSE, "val" }, + { "conn foo\n\t \tkey=val", FALSE, "val" }, + { "conn foo\n\tkey = val ", FALSE, "val" }, +}; + +START_TEST(test_whitespace) +{ + dictionary_t *dict; + + create_parser(chunk_from_str(whitespace_data[_i].conf)); + ck_assert(parser->parse(parser)); + dict = parser->get_section(parser, CONF_PARSER_CONN, "foo"); + ck_assert(dict); + if (whitespace_data[_i].value) + { + ck_assert_str_eq(whitespace_data[_i].value, dict->get(dict, "key")); + } + else + { + ck_assert(!dict->get(dict, "key")); + } + dict->destroy(dict); + if (whitespace_data[_i].check_section) + { + dict = parser->get_section(parser, CONF_PARSER_CONN, "bar"); + ck_assert(dict); + dict->destroy(dict); + } + else + { + ck_assert(!parser->get_section(parser, CONF_PARSER_CONN, "bar")); + } +} +END_TEST + +static struct { + bool valid; + char *conf; + char *section; + char *value; +} strings_data[] = { + { FALSE, "\"conn foo\"", NULL, NULL }, + { TRUE, "conn \"foo\"", "foo", NULL }, + { FALSE, "conn foo bar", NULL, NULL }, + { TRUE, "conn \"foo bar\"", "foo bar", NULL }, + { TRUE, "conn \"#foo\"", "#foo", NULL }, + { FALSE, "conn foo\n\t\"key=val\"", "foo", NULL }, + { TRUE, "conn foo\n\t\"key\"=val", "foo", "val" }, + { TRUE, "conn foo\n\tkey=val ue", "foo", "val ue" }, + { TRUE, "conn foo\n\tkey=val ue", "foo", "val ue" }, + { TRUE, "conn foo\n\tkey=\"val ue\"", "foo", "val ue" }, + { TRUE, "conn foo\n\tkey=\"val\\nue\"", "foo", "val\nue" }, +}; + +START_TEST(test_strings) +{ + dictionary_t *dict; + + create_parser(chunk_from_str(strings_data[_i].conf)); + ck_assert(parser->parse(parser) == strings_data[_i].valid); + if (strings_data[_i].section) + { + dict = parser->get_section(parser, CONF_PARSER_CONN, + strings_data[_i].section); + ck_assert(dict); + if (strings_data[_i].value) + { + ck_assert_str_eq(strings_data[_i].value, dict->get(dict, "key")); + } + else + { + ck_assert(!dict->get(dict, "key")); + } + dict->destroy(dict); + } +} +END_TEST + +START_TEST(test_refcounting) +{ + dictionary_t *dict; + + create_parser(chunk_from_str( + "conn foo\n" + " key=val")); + + ck_assert(parser->parse(parser)); + dict = parser->get_section(parser, CONF_PARSER_CONN, "foo"); + ck_assert(dict); + ck_assert_str_eq("val", dict->get(dict, "key")); + parser->destroy(parser); + ck_assert_str_eq("val", dict->get(dict, "key")); + dict->destroy(dict); +} +END_TEST + +START_TEST(test_also) +{ + dictionary_t *dict; + + create_parser(chunk_from_str( + "conn A\n" + " key=vala\n" + " keya=val1\n" + " unset=set\n" + "conn B\n" + " also=A\n" + " key=valb\n" + " keyb=val2\n" + " unset=\n" + "conn C\n" + " keyc=val3\n" + " unset=set again\n" + " also=B\n" + "conn D\n" + " keyd=val4\n" + " also=A\n" + " also=B\n" + "conn E\n" + " keye=val5\n" + " also=B\n" + " also=A\n" + "")); + + ck_assert(parser->parse(parser)); + dict = parser->get_section(parser, CONF_PARSER_CONN, "B"); + ck_assert(dict); + ck_assert_str_eq("valb", dict->get(dict, "key")); + ck_assert_str_eq("val1", dict->get(dict, "keya")); + ck_assert_str_eq("val2", dict->get(dict, "keyb")); + ck_assert(!dict->get(dict, "unset")); + dict->destroy(dict); + dict = parser->get_section(parser, CONF_PARSER_CONN, "C"); + ck_assert(dict); + ck_assert_str_eq("valb", dict->get(dict, "key")); + ck_assert_str_eq("val1", dict->get(dict, "keya")); + ck_assert_str_eq("val2", dict->get(dict, "keyb")); + ck_assert_str_eq("val3", dict->get(dict, "keyc")); + ck_assert_str_eq("set again", dict->get(dict, "unset")); + dict->destroy(dict); + /* since B includes A too the inclusion in D and E has no effect */ + dict = parser->get_section(parser, CONF_PARSER_CONN, "D"); + ck_assert(dict); + ck_assert_str_eq("valb", dict->get(dict, "key")); + ck_assert_str_eq("val1", dict->get(dict, "keya")); + ck_assert_str_eq("val2", dict->get(dict, "keyb")); + ck_assert(!dict->get(dict, "keyc")); + ck_assert_str_eq("val4", dict->get(dict, "keyd")); + ck_assert(!dict->get(dict, "unset")); + dict->destroy(dict); + dict = parser->get_section(parser, CONF_PARSER_CONN, "E"); + ck_assert(dict); + ck_assert_str_eq("valb", dict->get(dict, "key")); + ck_assert_str_eq("val1", dict->get(dict, "keya")); + ck_assert_str_eq("val2", dict->get(dict, "keyb")); + ck_assert(!dict->get(dict, "keyc")); + ck_assert(!dict->get(dict, "keyd")); + ck_assert_str_eq("val5", dict->get(dict, "keye")); + ck_assert(!dict->get(dict, "unset")); + dict->destroy(dict); +} +END_TEST + +START_TEST(test_ambiguous) +{ + dictionary_t *dict; + + create_parser(chunk_from_str( + "conn A\n" + " key=vala\n" + "conn B\n" + " key=valb\n" + "conn C\n" + " also=A\n" + " also=B\n" + "conn D\n" + " also=B\n" + " also=A\n" + "conn E\n" + " also=C\n" + " also=D\n" + "conn F\n" + " also=D\n" + " also=C\n")); + + ck_assert(parser->parse(parser)); + dict = parser->get_section(parser, CONF_PARSER_CONN, "E"); + ck_assert(dict); + ck_assert_str_eq("valb", dict->get(dict, "key")); + dict->destroy(dict); + dict = parser->get_section(parser, CONF_PARSER_CONN, "F"); + ck_assert(dict); + ck_assert_str_eq("vala", dict->get(dict, "key")); + dict->destroy(dict); +} +END_TEST + +Suite *parser_suite_create() +{ + Suite *s; + TCase *tc; + + s = suite_create("ipsec.conf parser"); + + tc = tcase_create("get_section(s)"); + tcase_add_checked_fixture(tc, NULL, teardown_parser); + tcase_add_test(tc, test_get_sections_config_setup); + tcase_add_test(tc, test_get_sections_conn); + tcase_add_test(tc, test_get_section_config_setup); + tcase_add_test(tc, test_get_section_conn); + suite_add_tcase(s, tc); + + tc = tcase_create("enumerate settings"); + tcase_add_checked_fixture(tc, NULL, teardown_parser); + tcase_add_test(tc, test_enumerate_values); + suite_add_tcase(s, tc); + + tc = tcase_create("extensibility"); + tcase_add_checked_fixture(tc, NULL, teardown_parser); + tcase_add_loop_test(tc, test_extensibility, 0, countof(extensibility_data)); + suite_add_tcase(s, tc); + + tc = tcase_create("comments"); + tcase_add_checked_fixture(tc, NULL, teardown_parser); + tcase_add_loop_test(tc, test_comments, 0, countof(comments_data)); + suite_add_tcase(s, tc); + + tc = tcase_create("whitespace"); + tcase_add_checked_fixture(tc, NULL, teardown_parser); + tcase_add_loop_test(tc, test_whitespace, 0, countof(whitespace_data)); + suite_add_tcase(s, tc); + + tc = tcase_create("strings"); + tcase_add_checked_fixture(tc, NULL, teardown_parser); + tcase_add_loop_test(tc, test_strings, 0, countof(strings_data)); + suite_add_tcase(s, tc); + + tc = tcase_create("refcounting"); + tcase_add_test(tc, test_refcounting); + suite_add_tcase(s, tc); + + tc = tcase_create("also="); + tcase_add_checked_fixture(tc, NULL, teardown_parser); + tcase_add_test(tc, test_also); + tcase_add_test(tc, test_ambiguous); + suite_add_tcase(s, tc); + + return s; +} |