settings: Add flex/bison based parser for strongswan.conf
authorTobias Brunner <tobias@strongswan.org>
Fri, 7 Mar 2014 16:21:19 +0000 (17:21 +0100)
committerTobias Brunner <tobias@strongswan.org>
Thu, 15 May 2014 09:28:06 +0000 (11:28 +0200)
This parser features several improvements over the existing one.
For instance, quoted strings (with escape sequences), unlimited includes,
relaxed newline handling (e.g. at the end of files or before/after { and }),
and the difference between empty and unset values (key = vs. key = "").

It also complains a lot more about invalid syntax. The current one accepts
pretty odd stuff (like settings or sections without name) without any
errors or warnings.

src/libstrongswan/Android.mk
src/libstrongswan/Makefile.am
src/libstrongswan/settings/.gitignore [new file with mode: 0644]
src/libstrongswan/settings/settings_lexer.l [new file with mode: 0644]
src/libstrongswan/settings/settings_parser.y [new file with mode: 0644]

index 9dfd264..1840ad2 100644 (file)
@@ -33,6 +33,7 @@ pen/pen.c plugins/plugin_loader.c plugins/plugin_feature.c processing/jobs/job.c
 processing/jobs/callback_job.c processing/processor.c processing/scheduler.c \
 processing/watcher.c resolver/resolver_manager.c resolver/rr_set.c \
 selectors/traffic_selector.c settings/settings.c settings/settings_types.c \
+settings/settings_parser.c settings/settings_lexer.c \
 threading/thread.c threading/thread_value.c threading/mutex.c \
 threading/semaphore.c threading/rwlock.c threading/spinlock.c \
 utils/utils.c utils/chunk.c utils/debug.c utils/enum.c utils/identification.c \
index de2c950..c4d1a58 100644 (file)
@@ -31,6 +31,7 @@ pen/pen.c plugins/plugin_loader.c plugins/plugin_feature.c processing/jobs/job.c
 processing/jobs/callback_job.c processing/processor.c processing/scheduler.c \
 processing/watcher.c resolver/resolver_manager.c resolver/rr_set.c \
 selectors/traffic_selector.c settings/settings.c settings/settings_types.c \
+settings/settings_parser.y settings/settings_lexer.l \
 threading/thread.c threading/thread_value.c threading/mutex.c \
 threading/semaphore.c threading/rwlock.c threading/spinlock.c \
 utils/utils.c utils/chunk.c utils/debug.c utils/enum.c utils/identification.c \
@@ -107,6 +108,8 @@ AM_CFLAGS = \
 AM_LDFLAGS = \
        -no-undefined
 
+AM_YFLAGS = -v -d
+
 if USE_LEAK_DETECTIVE
   AM_CPPFLAGS += -DLEAK_DETECTIVE
   libstrongswan_la_SOURCES += utils/leak_detective.c
@@ -148,7 +151,8 @@ Android.mk AndroidConfigLocal.h
 
 BUILT_SOURCES = \
 $(srcdir)/asn1/oid.c $(srcdir)/asn1/oid.h \
-$(srcdir)/crypto/proposal/proposal_keywords_static.c
+$(srcdir)/crypto/proposal/proposal_keywords_static.c \
+settings/settings_parser.h
 
 MAINTAINERCLEANFILES = \
 $(srcdir)/asn1/oid.c $(srcdir)/asn1/oid.h \
diff --git a/src/libstrongswan/settings/.gitignore b/src/libstrongswan/settings/.gitignore
new file mode 100644 (file)
index 0000000..a6e4681
--- /dev/null
@@ -0,0 +1,3 @@
+settings_lexer.c
+settings_parser.[ch]
+settings_parser.output
\ No newline at end of file
diff --git a/src/libstrongswan/settings/settings_lexer.l b/src/libstrongswan/settings/settings_lexer.l
new file mode 100644 (file)
index 0000000..f287761
--- /dev/null
@@ -0,0 +1,193 @@
+%{
+/*
+ * Copyright (C) 2014 Tobias Brunner
+ * Hochschule fuer Technik Rapperswil
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * for more details.
+ */
+
+#include "settings_parser.h"
+
+#include <utils/parser_helper.h>
+
+bool settings_parser_open_next_file(parser_helper_t *ctx);
+
+static void include_files(parser_helper_t *ctx);
+
+%}
+%option debug
+%option perf-report
+
+%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="settings_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 ]*#[^\n]*                  /* eat comments */
+[\t ]+                                 /* eat whitespace */
+\n|#.*\n                               return NEWLINE; /* also eats comments at the end of a line */
+
+"{"                                            |
+"}"                                            |
+"="                                            return yytext[0];
+
+"include"[\t ]+/[^=]   {
+       yyextra->string_init(yyextra);
+       yy_push_state(inc, yyscanner);
+}
+
+"\""                                   {
+       yyextra->string_init(yyextra);
+       yy_push_state(str, yyscanner);
+}
+
+[^#{}="\n\t ]+                 {
+       yylval->s = strdup(yytext);
+       return NAME;
+}
+
+<inc>{
+       /* we allow all characters except #, } and spaces, they can be escaped */
+       <<EOF>>                         |
+       \n|#.*\n                        |
+       [\t ]                           {
+               if (*yytext && yytext[strlen(yytext) - 1] == '\n')
+               {       /* put the newline back to fix the line numbers */
+                       unput('\n');
+                       yy_set_bol(0);
+               }
+               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>>                                        {
+       settings_parser_pop_buffer_state(yyscanner);
+       if (!settings_parser_open_next_file(yyextra) && !YY_CURRENT_BUFFER)
+       {
+               yyterminate();
+       }
+}
+
+%%
+
+/**
+ * Open the next file, if any is queued and readable, otherwise returns FALSE.
+ */
+bool settings_parser_open_next_file(parser_helper_t *ctx)
+{
+       parser_helper_file_t *file;
+
+       file = ctx->file_next(ctx);
+       if (!file)
+       {
+               return FALSE;
+       }
+
+       settings_parser_set_in(file->file, ctx->scanner);
+       settings_parser_push_buffer_state(
+                       settings_parser__create_buffer(file->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);
+
+       settings_parser_open_next_file(ctx);
+}
diff --git a/src/libstrongswan/settings/settings_parser.y b/src/libstrongswan/settings/settings_parser.y
new file mode 100644 (file)
index 0000000..3f0877c
--- /dev/null
@@ -0,0 +1,299 @@
+%{
+/*
+ * Copyright (C) 2014 Tobias Brunner
+ * Hochschule fuer Technik Rapperswil
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * for more details.
+ */
+
+#define _GNU_SOURCE /* for asprintf() */
+#include <stdio.h>
+
+#include "settings_parser.h"
+
+#include <library.h>
+#include <collections/array.h>
+#include <settings/settings_types.h>
+#include <utils/parser_helper.h>
+
+#define YYDEBUG 1
+
+/**
+ * Defined by the lexer
+ */
+int settings_parser_lex(YYSTYPE *lvalp, void *scanner);
+int settings_parser_lex_init_extra(parser_helper_t *extra, void *scanner);
+int settings_parser_lex_destroy(void *scanner);
+int settings_parser_set_in(FILE *in, void *scanner);
+void settings_parser_set_debug(int debug, void *scanner);
+char *settings_parser_get_text(void *scanner);
+int settings_parser_get_leng(void *scanner);
+int settings_parser_get_lineno(void *scanner);
+/* Custom functions in lexer */
+bool settings_parser_open_next_file(parser_helper_t *ctx);
+
+/**
+ * Forward declarations
+ */
+static void settings_parser_error(parser_helper_t *ctx, const char *s);
+static section_t *push_section(parser_helper_t *ctx, char *name);
+static section_t *pop_section(parser_helper_t *ctx);
+static void add_section(parser_helper_t *ctx, section_t *section);
+static void add_setting(parser_helper_t *ctx, kv_t *kv);
+
+/**
+ * Make sure the generated parser code passes the correct object to the lexer
+ */
+#define YYLEX_PARAM ctx->scanner
+
+%}
+%debug
+
+/* generate verbose error messages */
+%error-verbose
+/* generate a reentrant parser */
+%define api.pure
+/* prefix function/variable declarations */
+%name-prefix "settings_parser_"
+
+/* interact properly with the reentrant lexer */
+%lex-param {void *scanner}
+%parse-param {parser_helper_t *ctx}
+
+/* types for terminal symbols... (can't use the typedef'd types) */
+%union {
+       char *s;
+       struct section_t *sec;
+       struct kv_t *kv;
+}
+%token <s> NAME STRING
+%token NEWLINE
+
+/* ...and other symbols */
+%type <s> value valuepart
+%type <sec> section_start section
+%type <kv> setting
+
+/* properly destroy string tokens that are strdup()ed on error */
+%destructor { free($$); } NAME STRING value valuepart
+/* properly destroy parse results on error */
+%destructor { pop_section(ctx); settings_section_destroy($$); } section_start section
+%destructor { settings_kv_destroy($$); } setting
+
+/* there are two shift/reduce conflicts because of the "NAME = NAME" and
+ * "NAME {" ambiguity, and the "NAME =" rule) */
+%expect 2
+
+%%
+
+/**
+ * strongswan.conf grammar rules
+ */
+statements:
+       /* empty */
+       | statements NEWLINE
+       | statements statement
+       ;
+
+statement:
+       section
+       {
+               add_section(ctx, $section);
+       }
+       | setting
+       {
+               add_setting(ctx, $setting);
+       }
+       ;
+
+section:
+       section_start statements '}'
+       {
+               pop_section(ctx);
+               $$ = $section_start;
+       }
+       ;
+
+section_start:
+       NAME '{'
+       {
+               $$ = push_section(ctx, $NAME);
+       }
+       |
+       NAME NEWLINE '{'
+       {
+               $$ = push_section(ctx, $NAME);
+       }
+       ;
+
+setting:
+       NAME '=' value
+       {
+               $$ = settings_kv_create($NAME, $value);
+       }
+       |
+       NAME '='
+       {
+               $$ = settings_kv_create($NAME, NULL);
+       }
+       ;
+
+value:
+       valuepart
+       | value valuepart
+       {       /* 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);
+       }
+       ;
+
+valuepart:
+       NAME
+       | STRING
+       ;
+
+%%
+
+/**
+ * Referenced by the generated parser
+ */
+static void settings_parser_error(parser_helper_t *ctx, const char *s)
+{
+       char *text = settings_parser_get_text(ctx->scanner);
+       int len = settings_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, len, text);
+}
+
+/**
+ * Create a section and push it to the stack (the name is adopted), returns
+ * the created section
+ */
+static section_t *push_section(parser_helper_t *ctx, char *name)
+{
+       array_t *sections = (array_t*)ctx->context;
+       section_t *section;
+
+       section = settings_section_create(name);
+       array_insert(sections, ARRAY_TAIL, section);
+       return section;
+}
+
+/**
+ * Removes the top section of the stack and returns it
+ */
+static section_t *pop_section(parser_helper_t *ctx)
+{
+       array_t *sections = (array_t*)ctx->context;
+       section_t *section;
+
+       array_remove(sections, ARRAY_TAIL, &section);
+       return section;
+}
+
+/**
+ * Adds the given section to the section on top of the stack
+ */
+static void add_section(parser_helper_t *ctx, section_t *section)
+{
+       array_t *sections = (array_t*)ctx->context;
+       section_t *parent, *existing;
+
+       array_get(sections, ARRAY_TAIL, &parent);
+       if (array_bsearch(parent->sections, section->name, settings_section_find,
+                                         &existing) == -1)
+       {
+               array_insert_create(&parent->sections, ARRAY_TAIL, section);
+               array_sort(parent->sections, settings_section_sort, NULL);
+       }
+       else
+       {       /* extend the existing section */
+               settings_section_extend(existing, section);
+               settings_section_destroy(section);
+       }
+}
+
+/**
+ * Adds the given key/value pair to the section on top of the stack
+ */
+static void add_setting(parser_helper_t *ctx, kv_t *kv)
+{
+       array_t *sections = (array_t*)ctx->context;
+       section_t *section;
+       kv_t *existing;
+
+       array_get(sections, ARRAY_TAIL, &section);
+       if (array_bsearch(section->kv, kv->key, settings_kv_find, &existing) == -1)
+       {
+               array_insert_create(&section->kv, ARRAY_TAIL, kv);
+               array_sort(section->kv, settings_kv_sort, NULL);
+       }
+       else
+       {       /* move value to existing object */
+               free(existing->value);
+               existing->value = kv->value;
+               kv->value = NULL;
+               settings_kv_destroy(kv);
+       }
+}
+
+/**
+ * Parse the given file and add all sections and key/value pairs to the
+ * given section.
+ */
+bool settings_parser_parse_file(section_t *root, char *name)
+{
+       parser_helper_t *helper;
+       array_t *sections = NULL;
+       bool success = FALSE;
+
+       array_insert_create(&sections, ARRAY_TAIL, root);
+       helper = parser_helper_create(sections);
+       helper->get_lineno = settings_parser_get_lineno;
+       if (settings_parser_lex_init_extra(helper, &helper->scanner) != 0)
+       {
+               helper->destroy(helper);
+               array_destroy(sections);
+               return FALSE;
+       }
+       helper->file_include(helper, name);
+       if (!settings_parser_open_next_file(helper))
+       {
+               DBG1(DBG_CFG, "failed to open config file '%s'", name);
+       }
+       else
+       {
+               if (getenv("DEBUG_SETTINGS_PARSER"))
+               {
+                       yydebug = 1;
+                       settings_parser_set_debug(1, helper->scanner);
+               }
+               success = yyparse(helper) == 0;
+               if (!success)
+               {
+                       DBG1(DBG_CFG, "invalid config file '%s'", name);
+               }
+       }
+       array_destroy(sections);
+       settings_parser_lex_destroy(helper->scanner);
+       helper->destroy(helper);
+       return success;
+}