starter: Add new bison/flex based parser for ipsec.conf
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.
This commit is contained in:
@@ -1,4 +1,7 @@
|
||||
starter
|
||||
parser/lexer.c
|
||||
parser/parser.[ch]
|
||||
parser/parser.output
|
||||
lexer.c
|
||||
parser.h
|
||||
parser.c
|
||||
|
||||
@@ -3,11 +3,12 @@ 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 \
|
||||
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))
|
||||
|
||||
@@ -16,6 +17,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 \
|
||||
|
||||
+13
-7
@@ -1,15 +1,17 @@
|
||||
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 \
|
||||
-I$(top_srcdir)/src/starter \
|
||||
-I$(top_srcdir)/src/stroke \
|
||||
-DIPSEC_DIR=\"${ipsecdir}\" \
|
||||
-DIPSEC_CONFDIR=\"${sysconfdir}\" \
|
||||
@@ -23,10 +25,14 @@ 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 \
|
||||
$(SOCKLIB) $(PTHREADLIB)
|
||||
|
||||
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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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_ @}*/
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user