From f609682e5da0e29e89a02aa5fe21dcc49b469503 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 20 May 2014 18:15:13 +0200 Subject: [PATCH] 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. --- src/starter/.gitignore | 3 + src/starter/Android.mk | 12 +- src/starter/Makefile.am | 20 +- src/starter/parser/conf_parser.c | 655 +++++++++++++++++++++++++++++++++++++++ src/starter/parser/conf_parser.h | 120 +++++++ src/starter/parser/lexer.l | 205 ++++++++++++ src/starter/parser/parser.y | 254 +++++++++++++++ 7 files changed, 1257 insertions(+), 12 deletions(-) create mode 100644 src/starter/parser/conf_parser.c create mode 100644 src/starter/parser/conf_parser.h create mode 100644 src/starter/parser/lexer.l create mode 100644 src/starter/parser/parser.y diff --git a/src/starter/.gitignore b/src/starter/.gitignore index 6b88030..9382cb9 100644 --- a/src/starter/.gitignore +++ b/src/starter/.gitignore @@ -1,4 +1,7 @@ starter +parser/lexer.c +parser/parser.[ch] +parser/parser.output lexer.c parser.h parser.c diff --git a/src/starter/Android.mk b/src/starter/Android.mk index 2d2ad25..de27af7 100644 --- a/src/starter/Android.mk +++ b/src/starter/Android.mk @@ -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 \ diff --git a/src/starter/Makefile.am b/src/starter/Makefile.am index 48110dd..02c1d68 100644 --- a/src/starter/Makefile.am +++ b/src/starter/Makefile.am @@ -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 diff --git a/src/starter/parser/conf_parser.c b/src/starter/parser/conf_parser.c new file mode 100644 index 0000000..119e379 --- /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 . + * + * 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 +#include + +/** + * Provided by the generated parser + */ +bool conf_parser_parse_file(conf_parser_t *this, char *file); + +typedef struct private_conf_parser_t private_conf_parser_t; +typedef struct section_t section_t; + +/** + * Private data + */ +struct private_conf_parser_t { + + /** + * Public interface + */ + conf_parser_t public; + + /** + * Path to config file + */ + char *file; + + /** + * TRUE while parsing the config file + */ + bool parsing; + + /** + * Hashtable for ca sections, as section_t + */ + hashtable_t *cas; + + /** + * Hashtable for conn sections, as section_t + */ + hashtable_t *conns; + + /** + * Array to keep track of the order of conn sections, as section_t + */ + array_t *conns_order; + + /** + * config setup section + */ + section_t *config_setup; + + /** + * Pointer to the current section (the one added last) during parsing + */ + section_t *current_section; + + /** + * Refcount for this parser instance (also used by dictionaries) + */ + refcount_t ref; +}; + +typedef struct { + /** Key of the setting */ + char *key; + /** Value of the setting */ + char *value; +} setting_t; + +int setting_find(const void *a, const void *b) +{ + const char *key = a; + const setting_t *setting = b; + return strcmp(key, setting->key); +} + +int setting_sort(const void *a, const void *b, void *user) +{ + const setting_t *sa = a, *sb = b; + return strcmp(sa->key, sb->key); +} + +static void setting_destroy(setting_t *this) +{ + free(this->key); + free(this->value); + free(this); +} + +struct section_t { + /** Name of the section */ + char *name; + /** Sorted array of settings, as setting_t */ + array_t *settings; + /** Array of also= settings (in reversed order, i.e. most important to least + * important), as setting_t */ + array_t *also; + /** Array of linearized parent objects derived from also= settings, in their + * order of importance (most to least, i.e. %default) as section_t + * NULL if not yet determined */ + array_t *parents; +}; + +static section_t *section_create(char *name) +{ + section_t *this; + + INIT(this, + .name = name, + ); + return this; +} + +static void section_destroy(section_t *this) +{ + array_destroy_function(this->settings, (void*)setting_destroy, NULL); + array_destroy_function(this->also, (void*)setting_destroy, NULL); + array_destroy(this->parents); + free(this->name); + free(this); +} + +typedef struct { + /** Public interface */ + dictionary_t public; + /** Parser object */ + private_conf_parser_t *parser; + /** Section object */ + section_t *section; +} section_dictionary_t; + +typedef struct { + /** Public interface */ + enumerator_t public; + /** Current settings enumerator */ + enumerator_t *settings; + /** Enumerator into parent list */ + enumerator_t *parents; + /** Hashtable to keep track of already enumerated settings */ + hashtable_t *seen; +} dictionary_enumerator_t; + +METHOD(enumerator_t, dictionary_enumerate, bool, + dictionary_enumerator_t *this, char **key, char **value) +{ + setting_t *setting; + section_t *parent; + + while (TRUE) + { + if (this->settings && + this->settings->enumerate(this->settings, &setting)) + { + if (this->seen->get(this->seen, setting->key)) + { + continue; + } + this->seen->put(this->seen, setting->key, setting->key); + if (!setting->value) + { + continue; + } + if (key) + { + *key = setting->key; + } + if (value) + { + *value = setting->value; + } + return TRUE; + } + DESTROY_IF(this->settings); + this->settings = NULL; + if (this->parents && + this->parents->enumerate(this->parents, &parent)) + { + if (parent->settings) + { + this->settings = array_create_enumerator(parent->settings); + } + continue; + } + DESTROY_IF(this->parents); + this->parents = NULL; + break; + } + return FALSE; +} + +METHOD(enumerator_t, dictionary_enumerator_destroy, void, + dictionary_enumerator_t *this) +{ + DESTROY_IF(this->settings); + DESTROY_IF(this->parents); + this->seen->destroy(this->seen); + free(this); +} + +METHOD(dictionary_t, dictionary_create_enumerator, enumerator_t*, + section_dictionary_t *this) +{ + dictionary_enumerator_t *enumerator; + + INIT(enumerator, + .public = { + .enumerate = (void*)_dictionary_enumerate, + .destroy = _dictionary_enumerator_destroy, + }, + .seen = hashtable_create(hashtable_hash_str, hashtable_equals_str, 8), + ); + if (this->section->settings) + { + enumerator->settings = array_create_enumerator(this->section->settings); + } + if (this->section->parents) + { + enumerator->parents = array_create_enumerator(this->section->parents); + } + return &enumerator->public; +} + +METHOD(dictionary_t, dictionary_get, void*, + section_dictionary_t *this, const void *key) +{ + enumerator_t *parents; + section_t *section; + setting_t *setting; + char *value = NULL; + + section = this->section; + if (array_bsearch(section->settings, key, setting_find, &setting) != -1) + { + return setting->value; + } + parents = array_create_enumerator(section->parents); + while (parents->enumerate(parents, §ion)) + { + if (array_bsearch(section->settings, key, setting_find, &setting) != -1) + { + value = setting->value; + break; + } + } + parents->destroy(parents); + return value; +} + +METHOD(dictionary_t, dictionary_destroy, void, + section_dictionary_t *this) +{ + this->parser->public.destroy(&this->parser->public); + free(this); +} + +static dictionary_t *section_dictionary_create(private_conf_parser_t *parser, + section_t *section) +{ + section_dictionary_t *this; + + INIT(this, + .public = { + .create_enumerator = _dictionary_create_enumerator, + .get = _dictionary_get, + .destroy = _dictionary_destroy, + }, + .parser = parser, + .section = section, + ); + + ref_get(&parser->ref); + + return &this->public; +} + +static bool conn_filter(void *unused, section_t **section, char **name) +{ + *name = (*section)->name; + return TRUE; +} + +static bool ca_filter(void *unused, void *key, char **name, section_t **section) +{ + *name = (*section)->name; + return TRUE; +} + +METHOD(conf_parser_t, get_sections, enumerator_t*, + private_conf_parser_t *this, conf_parser_section_t type) +{ + switch (type) + { + case CONF_PARSER_CONN: + return enumerator_create_filter( + array_create_enumerator(this->conns_order), + (void*)conn_filter, NULL, NULL); + case CONF_PARSER_CA: + return enumerator_create_filter( + this->cas->create_enumerator(this->cas), + (void*)ca_filter, NULL, NULL); + case CONF_PARSER_CONFIG_SETUP: + default: + return enumerator_create_empty(); + } +} + +METHOD(conf_parser_t, get_section, dictionary_t*, + private_conf_parser_t *this, conf_parser_section_t type, char *name) +{ + section_t *section = NULL; + + switch (type) + { + case CONF_PARSER_CONFIG_SETUP: + section = this->config_setup; + break; + case CONF_PARSER_CONN: + section = this->conns->get(this->conns, name); + break; + case CONF_PARSER_CA: + section = this->cas->get(this->cas, name); + break; + default: + break; + } + return section ? section_dictionary_create(this, section) : NULL; +} + +METHOD(conf_parser_t, add_section, bool, + private_conf_parser_t *this, conf_parser_section_t type, char *name) +{ + hashtable_t *sections = this->conns; + array_t *order = this->conns_order; + section_t *section = NULL; + bool exists = FALSE; + + if (!this->parsing) + { + free(name); + return exists; + } + switch (type) + { + case CONF_PARSER_CONFIG_SETUP: + section = this->config_setup; + /* we don't expect a name, but just in case */ + free(name); + break; + case CONF_PARSER_CA: + sections = this->cas; + order = NULL; + /* fall-through */ + case CONF_PARSER_CONN: + section = sections->get(sections, name); + if (!section) + { + section = section_create(name); + sections->put(sections, name, section); + if (order) + { + array_insert(order, ARRAY_TAIL, section); + } + } + else + { + exists = TRUE; + free(name); + } + break; + + } + this->current_section = section; + return exists; +} + +METHOD(conf_parser_t, add_setting, void, + private_conf_parser_t *this, char *key, char *value) +{ + section_t *section = this->current_section; + setting_t *setting; + + if (!this->parsing || !this->current_section) + { + free(key); + free(value); + return; + } + if (streq(key, "also")) + { + if (!value || !strlen(value) || streq(value, "%default")) + { /* we require a name, but all sections inherit from %default */ + free(key); + free(value); + return; + } + INIT(setting, + .key = key, + .value = value, + ); + array_insert_create(§ion->also, ARRAY_HEAD, setting); + return; + } + if (array_bsearch(section->settings, key, setting_find, &setting) == -1) + { + INIT(setting, + .key = key, + .value = value, + ); + array_insert_create(§ion->settings, ARRAY_TAIL, setting); + array_sort(section->settings, setting_sort, NULL); + } + else + { + free(setting->value); + setting->value = value; + free(key); + } +} + +/** + * Check if the given section is contained in the given array. The search + * starts at the given index. + */ +static bool is_contained_in(array_t *arr, section_t *section) +{ + section_t *current; + int i; + + for (i = 0; i < array_count(arr); i++) + { + array_get(arr, i, ¤t); + if (streq(section->name, current->name)) + { + return TRUE; + } + } + return FALSE; +} + +/** + * This algorithm to linearize sections uses a bottom-first depth-first + * semantic, with an additional elimination step that removes all but the + * last occurrence of each section. + * + * Consider this configuration: + * + * conn A + * conn B + * also=A + * conn C + * also=A + * conn D + * also=C + * also=B + * + * The linearization would yield D B A C A, which gets reduced to D B C A. + * + * Ambiguous configurations are handled pragmatically. + * + * Consider the following configuration: + * + * conn A + * conn B + * conn C + * also=A + * also=B + * conn D + * also=B + * also=A + * conn E + * also=C + * also=D + * + * It is ambiguous because D and C include the same two sections but in + * a different order. + * + * The linearization would yield E D A B C B A which gets reduced to E D C B A. + */ +static bool resolve_also_single(hashtable_t *sections, + section_t *section, section_t *def, array_t *stack) +{ + enumerator_t *enumerator; + array_t *parents; + section_t *parent, *grandparent; + setting_t *also; + bool success = TRUE; + int i; + + array_insert(stack, ARRAY_HEAD, section); + parents = array_create(0, 0); + enumerator = array_create_enumerator(section->also); + while (enumerator->enumerate(enumerator, &also)) + { + parent = sections->get(sections, also->value); + if (!parent || is_contained_in(stack, parent)) + { + if (!parent) + { + DBG1(DBG_CFG, "section '%s' referenced in section '%s' not " + "found", also->value, section->name); + } + else + { + DBG1(DBG_CFG, "section '%s' referenced in section '%s' causes " + "a loop", parent->name, section->name); + } + array_remove_at(section->also, enumerator); + setting_destroy(also); + success = FALSE; + continue; + } + if (!parent->parents) + { + if (!resolve_also_single(sections, parent, def, stack)) + { + success = FALSE; + continue; + } + } + /* add the grandparents and the parent to the list */ + array_insert(parents, ARRAY_TAIL, parent); + for (i = 0; i < array_count(parent->parents); i++) + { + array_get(parent->parents, i, &grandparent); + array_insert(parents, ARRAY_TAIL, grandparent); + } + } + enumerator->destroy(enumerator); + array_remove(stack, ARRAY_HEAD, NULL); + + if (success && def && !array_count(parents)) + { + array_insert(parents, ARRAY_TAIL, def); + } + + while (success && array_remove(parents, ARRAY_HEAD, &parent)) + { + if (!is_contained_in(parents, parent)) + { /* last occurrence of this section */ + array_insert_create(§ion->parents, ARRAY_TAIL, parent); + } + } + array_destroy(parents); + return success; +} + +/** + * Resolve also= statements. The functions returns TRUE if everything is fine, + * or FALSE if either a referenced section does not exist, or if the section + * inheritance can't be determined properly (e.g. if there are loops or if a + * section inherits from multiple sections - perhaps over several levels - in + * an ambiguous way). + */ +static bool resolve_also(hashtable_t *sections) +{ + enumerator_t *enumerator; + section_t *def, *section; + array_t *stack; + bool success = TRUE; + + stack = array_create(0, 0); + + def = sections->get(sections, "%default"); + if (def) + { /* the default section is the only one with an empty parents list */ + def->parents = array_create(0, 0); + } + enumerator = sections->create_enumerator(sections); + while (enumerator->enumerate(enumerator, NULL, §ion)) + { + if (section->parents) + { /* already determined */ + continue; + } + success = resolve_also_single(sections, section, def, stack) && success; + } + enumerator->destroy(enumerator); + array_destroy(stack); + return success; +} + +METHOD(conf_parser_t, parse, bool, + private_conf_parser_t *this) +{ + bool success; + + if (!this->file) + { /* no file, lets assume this is OK */ + return TRUE; + } + this->parsing = TRUE; + success = conf_parser_parse_file(&this->public, this->file); + this->parsing = FALSE; + return success && resolve_also(this->conns) && resolve_also(this->cas); +} + +METHOD(conf_parser_t, destroy, void, + private_conf_parser_t *this) +{ + if (ref_put(&this->ref)) + { + this->cas->destroy_function(this->cas, (void*)section_destroy); + this->conns->destroy_function(this->conns, (void*)section_destroy); + section_destroy(this->config_setup); + array_destroy(this->conns_order); + free(this->file); + free(this); + } +} + +/* + * Described in header + */ +conf_parser_t *conf_parser_create(const char *file) +{ + private_conf_parser_t *this; + + INIT(this, + .public = { + .parse = _parse, + .get_sections = _get_sections, + .get_section = _get_section, + .add_section = _add_section, + .add_setting = _add_setting, + .destroy = _destroy, + }, + .file = strdupnull(file), + .cas = hashtable_create(hashtable_hash_str, + hashtable_equals_str, 8), + .conns = hashtable_create(hashtable_hash_str, + hashtable_equals_str, 8), + .conns_order = array_create(0, 0), + .config_setup = section_create(NULL), + .ref = 1, + ); + + return &this->public; +} diff --git a/src/starter/parser/conf_parser.h b/src/starter/parser/conf_parser.h new file mode 100644 index 0000000..a55b7cb --- /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 . + * + * 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 +#include + +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 + */ + CONF_PARSER_CONN, + + /** + * ca + */ + 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 0000000..22ad617 --- /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 . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include +#include + +#include "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; +} + +{ + /* we allow all characters except # and spaces, they can be escaped */ + <> | + [#\n\t ] { + if (*yytext) + { + switch (yytext[0]) + { + case '\n': + /* put the newline back to fix the line numbers */ + unput('\n'); + yy_set_bol(0); + break; + case '#': + /* 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); + } +} + +{ + "\"" | + <> | + \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); + } +} + +<> { + 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 0000000..54dedc1 --- /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 . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#define _GNU_SOURCE /* for asprintf() */ +#include + +#include +#include +#include + +#include "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 STRING +%token EQ SPACES NEWLINE CONFIG_SETUP CONN CA + +/* ...and other symbols */ +%type section_type +%type 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; +} -- 2.7.4