starter: Add new bison/flex based parser for ipsec.conf
authorTobias Brunner <tobias@strongswan.org>
Tue, 20 May 2014 16:15:13 +0000 (18:15 +0200)
committerTobias Brunner <tobias@strongswan.org>
Thu, 19 Jun 2014 12:00:48 +0000 (14:00 +0200)
The parser simply returns key/value pairs of all sections, it already
resolves also= and allows overriding options in all included sections
(not only %default), options set in included section can also be cleared
again (key=).
It provides other improvements too, like quoted strings (with escape
sequences), unlimited includes and better whitespace/comment handling.

src/starter/.gitignore
src/starter/Android.mk
src/starter/Makefile.am
src/starter/parser/conf_parser.c [new file with mode: 0644]
src/starter/parser/conf_parser.h [new file with mode: 0644]
src/starter/parser/lexer.l [new file with mode: 0644]
src/starter/parser/parser.y [new file with mode: 0644]

index 6b88030..9382cb9 100644 (file)
@@ -1,4 +1,7 @@
 starter
 starter
+parser/lexer.c
+parser/parser.[ch]
+parser/parser.output
 lexer.c
 parser.h
 parser.c
 lexer.c
 parser.h
 parser.c
index 2d2ad25..de27af7 100644 (file)
@@ -3,11 +3,12 @@ include $(CLEAR_VARS)
 
 # copy-n-paste from Makefile.am (update for LEX/YACC)
 starter_SOURCES := \
 
 # 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 \
+parser.c lexer.c ipsec-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))
 
 
 LOCAL_SRC_FILES := $(filter %.c,$(starter_SOURCES))
 
@@ -16,6 +17,7 @@ LOCAL_SRC_FILES := $(filter %.c,$(starter_SOURCES))
 LOCAL_C_INCLUDES += \
        $(strongswan_PATH)/src/libhydra \
        $(strongswan_PATH)/src/libstrongswan \
 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 \
        $(strongswan_PATH)/src/stroke
 
 LOCAL_CFLAGS := $(strongswan_CFLAGS) -DSTART_CHARON \
index 48110dd..02c1d68 100644 (file)
@@ -1,15 +1,17 @@
 ipsec_PROGRAMS = starter
 starter_SOURCES = \
 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 \
+parser/parser.y parser/lexer.l parser/conf_parser.c parser/conf_parser.h \
+parser.y lexer.l ipsec-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
 
 AM_CPPFLAGS = \
        -I${linux_headers} \
        -I$(top_srcdir)/src/libstrongswan \
        -I$(top_srcdir)/src/libhydra \
 
 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}\" \
        -I$(top_srcdir)/src/stroke \
        -DIPSEC_DIR=\"${ipsecdir}\" \
        -DIPSEC_CONFDIR=\"${sysconfdir}\" \
@@ -23,10 +25,14 @@ AM_CPPFLAGS = \
 
 AM_YFLAGS = -v -d
 
 
 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 \
+       $(SOCKLIB) $(PTHREADLIB)
+
 EXTRA_DIST = keywords.txt ipsec.conf Android.mk
 MAINTAINERCLEANFILES = keywords.c
 EXTRA_DIST = keywords.txt ipsec.conf Android.mk
 MAINTAINERCLEANFILES = keywords.c
-BUILT_SOURCES = parser.h
+BUILT_SOURCES = keywords.c parser.h parser/parser.h
 
 if USE_CHARON
   AM_CPPFLAGS += -DSTART_CHARON
 
 if USE_CHARON
   AM_CPPFLAGS += -DSTART_CHARON
diff --git a/src/starter/parser/conf_parser.c b/src/starter/parser/conf_parser.c
new file mode 100644 (file)
index 0000000..119e379
--- /dev/null
@@ -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 (file)
index 0000000..a55b7cb
--- /dev/null
@@ -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 (file)
index 0000000..22ad617
--- /dev/null
@@ -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 (file)
index 0000000..54dedc1
--- /dev/null
@@ -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;
+}