aboutsummaryrefslogtreecommitdiffstats
path: root/src/starter/parser
diff options
context:
space:
mode:
Diffstat (limited to 'src/starter/parser')
-rw-r--r--src/starter/parser/conf_parser.c655
-rw-r--r--src/starter/parser/conf_parser.h120
-rw-r--r--src/starter/parser/lexer.l205
-rw-r--r--src/starter/parser/parser.y254
4 files changed, 1234 insertions, 0 deletions
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, &section))
+ {
+ 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(&section->also, ARRAY_HEAD, setting);
+ return;
+ }
+ if (array_bsearch(section->settings, key, setting_find, &setting) == -1)
+ {
+ INIT(setting,
+ .key = key,
+ .value = value,
+ );
+ array_insert_create(&section->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, &current);
+ 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(&section->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, &section))
+ {
+ 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;
+}