summaryrefslogtreecommitdiffstats
path: root/src/conf-yaml-loader.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 17:39:49 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 17:39:49 +0000
commita0aa2307322cd47bbf416810ac0292925e03be87 (patch)
tree37076262a026c4b48c8a0e84f44ff9187556ca35 /src/conf-yaml-loader.c
parentInitial commit. (diff)
downloadsuricata-a0aa2307322cd47bbf416810ac0292925e03be87.tar.xz
suricata-a0aa2307322cd47bbf416810ac0292925e03be87.zip
Adding upstream version 1:7.0.3.upstream/1%7.0.3
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/conf-yaml-loader.c')
-rw-r--r--src/conf-yaml-loader.c1059
1 files changed, 1059 insertions, 0 deletions
diff --git a/src/conf-yaml-loader.c b/src/conf-yaml-loader.c
new file mode 100644
index 0000000..1bd107e
--- /dev/null
+++ b/src/conf-yaml-loader.c
@@ -0,0 +1,1059 @@
+/* Copyright (C) 2007-2023 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ * \author Endace Technology Limited - Jason Ish <jason.ish@endace.com>
+ *
+ * YAML configuration loader.
+ */
+
+#include "suricata-common.h"
+#include "conf.h"
+#include "conf-yaml-loader.h"
+#include <yaml.h>
+#include "util-path.h"
+#include "util-debug.h"
+#include "util-unittest.h"
+
+#define YAML_VERSION_MAJOR 1
+#define YAML_VERSION_MINOR 1
+
+/* The maximum level of recursion allowed while parsing the YAML
+ * file. */
+#define RECURSION_LIMIT 128
+
+/* Sometimes we'll have to create a node name on the fly (integer
+ * conversion, etc), so this is a default length to allocate that will
+ * work most of the time. */
+#define DEFAULT_NAME_LEN 16
+
+#define MANGLE_ERRORS_MAX 10
+static int mangle_errors = 0;
+
+static char *conf_dirname = NULL;
+
+static int ConfYamlParse(yaml_parser_t *parser, ConfNode *parent, int inseq, int rlevel, int state);
+
+/* Configuration processing states. */
+enum conf_state {
+ CONF_KEY = 0,
+ CONF_VAL,
+ CONF_INCLUDE,
+};
+
+/**
+ * \brief Mangle unsupported characters.
+ *
+ * \param string A pointer to an null terminated string.
+ *
+ * \retval none
+ */
+static void
+Mangle(char *string)
+{
+ char *c;
+
+ while ((c = strchr(string, '_')))
+ *c = '-';
+
+ return;
+}
+
+/**
+ * \brief Set the directory name of the configuration file.
+ *
+ * \param filename The configuration filename.
+ */
+static void
+ConfYamlSetConfDirname(const char *filename)
+{
+ char *ep;
+
+ ep = strrchr(filename, '\\');
+ if (ep == NULL)
+ ep = strrchr(filename, '/');
+
+ if (ep == NULL) {
+ conf_dirname = SCStrdup(".");
+ if (conf_dirname == NULL) {
+ FatalError("ERROR: Failed to allocate memory while loading configuration.");
+ }
+ }
+ else {
+ conf_dirname = SCStrdup(filename);
+ if (conf_dirname == NULL) {
+ FatalError("ERROR: Failed to allocate memory while loading configuration.");
+ }
+ conf_dirname[ep - filename] = '\0';
+ }
+}
+
+/**
+ * \brief Include a file in the configuration.
+ *
+ * \param parent The configuration node the included configuration will be
+ * placed at.
+ * \param filename The filename to include.
+ *
+ * \retval 0 on success, -1 on failure.
+ */
+int ConfYamlHandleInclude(ConfNode *parent, const char *filename)
+{
+ yaml_parser_t parser;
+ char include_filename[PATH_MAX];
+ FILE *file = NULL;
+ int ret = -1;
+
+ if (yaml_parser_initialize(&parser) != 1) {
+ SCLogError("Failed to initialize YAML parser");
+ return -1;
+ }
+
+ if (PathIsAbsolute(filename)) {
+ strlcpy(include_filename, filename, sizeof(include_filename));
+ }
+ else {
+ snprintf(include_filename, sizeof(include_filename), "%s/%s",
+ conf_dirname, filename);
+ }
+
+ file = fopen(include_filename, "r");
+ if (file == NULL) {
+ SCLogError("Failed to open configuration include file %s: %s", include_filename,
+ strerror(errno));
+ goto done;
+ }
+
+ yaml_parser_set_input_file(&parser, file);
+
+ if (ConfYamlParse(&parser, parent, 0, 0, 0) != 0) {
+ SCLogError("Failed to include configuration file %s", filename);
+ goto done;
+ }
+
+ ret = 0;
+
+done:
+ yaml_parser_delete(&parser);
+ if (file != NULL) {
+ fclose(file);
+ }
+
+ return ret;
+}
+
+/**
+ * \brief Parse a YAML layer.
+ *
+ * \param parser A pointer to an active yaml_parser_t.
+ * \param parent The parent configuration node.
+ *
+ * \retval 0 on success, -1 on failure.
+ */
+static int ConfYamlParse(yaml_parser_t *parser, ConfNode *parent, int inseq, int rlevel, int state)
+{
+ ConfNode *node = parent;
+ yaml_event_t event;
+ memset(&event, 0, sizeof(event));
+ int done = 0;
+ int seq_idx = 0;
+ int retval = 0;
+ int was_empty = -1;
+ int include_count = 0;
+
+ if (rlevel++ > RECURSION_LIMIT) {
+ SCLogError("Recursion limit reached while parsing "
+ "configuration file, aborting.");
+ return -1;
+ }
+
+ while (!done) {
+ if (!yaml_parser_parse(parser, &event)) {
+ SCLogError("Failed to parse configuration file at line %" PRIuMAX ": %s\n",
+ (uintmax_t)parser->problem_mark.line, parser->problem);
+ retval = -1;
+ break;
+ }
+
+ if (event.type == YAML_DOCUMENT_START_EVENT) {
+ SCLogDebug("event.type=YAML_DOCUMENT_START_EVENT; state=%d", state);
+ /* Verify YAML version - its more likely to be a valid
+ * Suricata configuration file if the version is
+ * correct. */
+ yaml_version_directive_t *ver =
+ event.data.document_start.version_directive;
+ if (ver == NULL) {
+ SCLogError("ERROR: Invalid configuration file.");
+ SCLogError("The configuration file must begin with the following two lines: %%YAML "
+ "1.1 and ---");
+ goto fail;
+ }
+ int major = ver->major;
+ int minor = ver->minor;
+ if (!(major == YAML_VERSION_MAJOR && minor == YAML_VERSION_MINOR)) {
+ SCLogError("ERROR: Invalid YAML version. Must be 1.1");
+ goto fail;
+ }
+ }
+ else if (event.type == YAML_SCALAR_EVENT) {
+ char *value = (char *)event.data.scalar.value;
+ char *tag = (char *)event.data.scalar.tag;
+ SCLogDebug("event.type=YAML_SCALAR_EVENT; state=%d; value=%s; "
+ "tag=%s; inseq=%d", state, value, tag, inseq);
+
+ /* Skip over empty scalar values while in KEY state. This
+ * tends to only happen on an empty file, where a scalar
+ * event probably shouldn't fire anyways. */
+ if (state == CONF_KEY && strlen(value) == 0) {
+ goto next;
+ }
+
+ /* If the value is unquoted, certain strings in YAML represent NULL. */
+ if ((inseq || state == CONF_VAL) &&
+ event.data.scalar.style == YAML_PLAIN_SCALAR_STYLE) {
+ if (strlen(value) == 0 || strcmp(value, "~") == 0 || strcmp(value, "null") == 0 ||
+ strcmp(value, "Null") == 0 || strcmp(value, "NULL") == 0) {
+ value = NULL;
+ }
+ }
+
+ if (inseq) {
+ if (state == CONF_INCLUDE) {
+ if (value != NULL) {
+ SCLogInfo("Including configuration file %s.", value);
+ if (ConfYamlHandleInclude(parent, value) != 0) {
+ goto fail;
+ }
+ }
+ goto next;
+ }
+ char sequence_node_name[DEFAULT_NAME_LEN];
+ snprintf(sequence_node_name, DEFAULT_NAME_LEN, "%d", seq_idx++);
+ ConfNode *seq_node = NULL;
+ if (was_empty < 0) {
+ // initialize was_empty
+ if (TAILQ_EMPTY(&parent->head)) {
+ was_empty = 1;
+ } else {
+ was_empty = 0;
+ }
+ }
+ // we only check if the node's list was not empty at first
+ if (was_empty == 0) {
+ seq_node = ConfNodeLookupChild(parent, sequence_node_name);
+ }
+ if (seq_node != NULL) {
+ /* The sequence node has already been set, probably
+ * from the command line. Remove it so it gets
+ * re-added in the expected order for iteration.
+ */
+ TAILQ_REMOVE(&parent->head, seq_node, next);
+ }
+ else {
+ seq_node = ConfNodeNew();
+ if (unlikely(seq_node == NULL)) {
+ goto fail;
+ }
+ seq_node->name = SCStrdup(sequence_node_name);
+ if (unlikely(seq_node->name == NULL)) {
+ SCFree(seq_node);
+ goto fail;
+ }
+ if (value != NULL) {
+ seq_node->val = SCStrdup(value);
+ if (unlikely(seq_node->val == NULL)) {
+ SCFree(seq_node->name);
+ goto fail;
+ }
+ } else {
+ seq_node->val = NULL;
+ }
+ }
+ TAILQ_INSERT_TAIL(&parent->head, seq_node, next);
+ }
+ else {
+ if (state == CONF_INCLUDE) {
+ SCLogInfo("Including configuration file %s.", value);
+ if (ConfYamlHandleInclude(parent, value) != 0) {
+ goto fail;
+ }
+ state = CONF_KEY;
+ }
+ else if (state == CONF_KEY) {
+
+ if (strcmp(value, "include") == 0) {
+ state = CONF_INCLUDE;
+ if (++include_count > 1) {
+ SCLogWarning("Multipline \"include\" fields at the same level are "
+ "deprecated and will not work in Suricata 8, please move "
+ "to an array of include files: line: %zu",
+ parser->mark.line);
+ }
+ goto next;
+ }
+
+ if (parent->is_seq) {
+ if (parent->val == NULL) {
+ parent->val = SCStrdup(value);
+ if (parent->val && strchr(parent->val, '_'))
+ Mangle(parent->val);
+ }
+ }
+
+ if (strchr(value, '.') != NULL) {
+ node = ConfNodeGetNodeOrCreate(parent, value, 0);
+ if (node == NULL) {
+ /* Error message already logged. */
+ goto fail;
+ }
+ } else {
+ ConfNode *existing = ConfNodeLookupChild(parent, value);
+ if (existing != NULL) {
+ if (!existing->final) {
+ SCLogInfo("Configuration node '%s' redefined.", existing->name);
+ ConfNodePrune(existing);
+ }
+ node = existing;
+ } else {
+ node = ConfNodeNew();
+ node->name = SCStrdup(value);
+ node->parent = parent;
+ if (node->name && strchr(node->name, '_')) {
+ if (!(parent->name &&
+ ((strcmp(parent->name, "address-groups") == 0) ||
+ (strcmp(parent->name, "port-groups") == 0)))) {
+ Mangle(node->name);
+ if (mangle_errors < MANGLE_ERRORS_MAX) {
+ SCLogWarning(
+ "%s is deprecated. Please use %s on line %" PRIuMAX
+ ".",
+ value, node->name,
+ (uintmax_t)parser->mark.line + 1);
+ mangle_errors++;
+ if (mangle_errors >= MANGLE_ERRORS_MAX)
+ SCLogWarning("not showing more "
+ "parameter name warnings.");
+ }
+ }
+ }
+ TAILQ_INSERT_TAIL(&parent->head, node, next);
+ }
+ }
+ state = CONF_VAL;
+ }
+ else {
+ if (value != NULL && (tag != NULL) && (strcmp(tag, "!include") == 0)) {
+ SCLogInfo("Including configuration file %s at "
+ "parent node %s.", value, node->name);
+ if (ConfYamlHandleInclude(node, value) != 0)
+ goto fail;
+ } else if (!node->final && value != NULL) {
+ if (node->val != NULL)
+ SCFree(node->val);
+ node->val = SCStrdup(value);
+ }
+ state = CONF_KEY;
+ }
+ }
+ }
+ else if (event.type == YAML_SEQUENCE_START_EVENT) {
+ SCLogDebug("event.type=YAML_SEQUENCE_START_EVENT; state=%d", state);
+ /* If we're processing a list of includes, use the current parent. */
+ if (ConfYamlParse(parser, state == CONF_INCLUDE ? parent : node, 1, rlevel,
+ state == CONF_INCLUDE ? CONF_INCLUDE : 0) != 0)
+ goto fail;
+ node->is_seq = 1;
+ state = CONF_KEY;
+ }
+ else if (event.type == YAML_SEQUENCE_END_EVENT) {
+ SCLogDebug("event.type=YAML_SEQUENCE_END_EVENT; state=%d", state);
+ done = 1;
+ }
+ else if (event.type == YAML_MAPPING_START_EVENT) {
+ SCLogDebug("event.type=YAML_MAPPING_START_EVENT; state=%d", state);
+ if (state == CONF_INCLUDE) {
+ SCLogError("Include fields cannot be a mapping: line %zu", parser->mark.line);
+ goto fail;
+ }
+ if (inseq) {
+ char sequence_node_name[DEFAULT_NAME_LEN];
+ snprintf(sequence_node_name, DEFAULT_NAME_LEN, "%d", seq_idx++);
+ ConfNode *seq_node = ConfNodeLookupChild(node,
+ sequence_node_name);
+ if (seq_node != NULL) {
+ /* The sequence node has already been set, probably
+ * from the command line. Remove it so it gets
+ * re-added in the expected order for iteration.
+ */
+ TAILQ_REMOVE(&node->head, seq_node, next);
+ }
+ else {
+ seq_node = ConfNodeNew();
+ if (unlikely(seq_node == NULL)) {
+ goto fail;
+ }
+ seq_node->name = SCStrdup(sequence_node_name);
+ if (unlikely(seq_node->name == NULL)) {
+ SCFree(seq_node);
+ goto fail;
+ }
+ }
+ seq_node->is_seq = 1;
+ TAILQ_INSERT_TAIL(&node->head, seq_node, next);
+ if (ConfYamlParse(parser, seq_node, 0, rlevel, 0) != 0)
+ goto fail;
+ }
+ else {
+ if (ConfYamlParse(parser, node, inseq, rlevel, 0) != 0)
+ goto fail;
+ }
+ state = CONF_KEY;
+ }
+ else if (event.type == YAML_MAPPING_END_EVENT) {
+ SCLogDebug("event.type=YAML_MAPPING_END_EVENT; state=%d", state);
+ done = 1;
+ }
+ else if (event.type == YAML_STREAM_END_EVENT) {
+ SCLogDebug("event.type=YAML_STREAM_END_EVENT; state=%d", state);
+ done = 1;
+ }
+
+ next:
+ yaml_event_delete(&event);
+ continue;
+
+ fail:
+ yaml_event_delete(&event);
+ retval = -1;
+ break;
+ }
+
+ rlevel--;
+ return retval;
+}
+
+/**
+ * \brief Load configuration from a YAML file.
+ *
+ * This function will load a configuration file. On failure -1 will
+ * be returned and it is suggested that the program then exit. Any
+ * errors while loading the configuration file will have already been
+ * logged.
+ *
+ * \param filename Filename of configuration file to load.
+ *
+ * \retval 0 on success, -1 on failure.
+ */
+int
+ConfYamlLoadFile(const char *filename)
+{
+ FILE *infile;
+ yaml_parser_t parser;
+ int ret;
+ ConfNode *root = ConfGetRootNode();
+
+ if (yaml_parser_initialize(&parser) != 1) {
+ SCLogError("failed to initialize yaml parser.");
+ return -1;
+ }
+
+ struct stat stat_buf;
+ if (stat(filename, &stat_buf) == 0) {
+ if (stat_buf.st_mode & S_IFDIR) {
+ SCLogError("yaml argument is not a file but a directory: %s. "
+ "Please specify the yaml file in your -c option.",
+ filename);
+ yaml_parser_delete(&parser);
+ return -1;
+ }
+ }
+
+ // coverity[toctou : FALSE]
+ infile = fopen(filename, "r");
+ if (infile == NULL) {
+ SCLogError("failed to open file: %s: %s", filename, strerror(errno));
+ yaml_parser_delete(&parser);
+ return -1;
+ }
+
+ if (conf_dirname == NULL) {
+ ConfYamlSetConfDirname(filename);
+ }
+
+ yaml_parser_set_input_file(&parser, infile);
+ ret = ConfYamlParse(&parser, root, 0, 0, 0);
+ yaml_parser_delete(&parser);
+ fclose(infile);
+
+ return ret;
+}
+
+/**
+ * \brief Load configuration from a YAML string.
+ */
+int
+ConfYamlLoadString(const char *string, size_t len)
+{
+ ConfNode *root = ConfGetRootNode();
+ yaml_parser_t parser;
+ int ret;
+
+ if (yaml_parser_initialize(&parser) != 1) {
+ fprintf(stderr, "Failed to initialize yaml parser.\n");
+ exit(EXIT_FAILURE);
+ }
+ yaml_parser_set_input_string(&parser, (const unsigned char *)string, len);
+ ret = ConfYamlParse(&parser, root, 0, 0, 0);
+ yaml_parser_delete(&parser);
+
+ return ret;
+}
+
+/**
+ * \brief Load configuration from a YAML file, insert in tree at 'prefix'
+ *
+ * This function will load a configuration file and insert it into the
+ * config tree at 'prefix'. This means that if this is called with prefix
+ * "abc" and the file contains a parameter "def", it will be loaded as
+ * "abc.def".
+ *
+ * \param filename Filename of configuration file to load.
+ * \param prefix Name prefix to use.
+ *
+ * \retval 0 on success, -1 on failure.
+ */
+int
+ConfYamlLoadFileWithPrefix(const char *filename, const char *prefix)
+{
+ FILE *infile;
+ yaml_parser_t parser;
+ int ret;
+ ConfNode *root = ConfGetNode(prefix);
+
+ if (yaml_parser_initialize(&parser) != 1) {
+ SCLogError("failed to initialize yaml parser.");
+ return -1;
+ }
+
+ struct stat stat_buf;
+ /* coverity[toctou] */
+ if (stat(filename, &stat_buf) == 0) {
+ if (stat_buf.st_mode & S_IFDIR) {
+ SCLogError("yaml argument is not a file but a directory: %s. "
+ "Please specify the yaml file in your -c option.",
+ filename);
+ return -1;
+ }
+ }
+
+ /* coverity[toctou] */
+ infile = fopen(filename, "r");
+ if (infile == NULL) {
+ SCLogError("failed to open file: %s: %s", filename, strerror(errno));
+ yaml_parser_delete(&parser);
+ return -1;
+ }
+
+ if (conf_dirname == NULL) {
+ ConfYamlSetConfDirname(filename);
+ }
+
+ if (root == NULL) {
+ /* if node at 'prefix' doesn't yet exist, add a place holder */
+ ConfSet(prefix, "<prefix root node>");
+ root = ConfGetNode(prefix);
+ if (root == NULL) {
+ fclose(infile);
+ yaml_parser_delete(&parser);
+ return -1;
+ }
+ }
+ yaml_parser_set_input_file(&parser, infile);
+ ret = ConfYamlParse(&parser, root, 0, 0, 0);
+ yaml_parser_delete(&parser);
+ fclose(infile);
+
+ return ret;
+}
+
+#ifdef UNITTESTS
+
+static int
+ConfYamlSequenceTest(void)
+{
+ char input[] = "\
+%YAML 1.1\n\
+---\n\
+rule-files:\n\
+ - netbios.rules\n\
+ - x11.rules\n\
+\n\
+default-log-dir: /tmp\n\
+";
+
+ ConfCreateContextBackup();
+ ConfInit();
+
+ ConfYamlLoadString(input, strlen(input));
+
+ ConfNode *node;
+ node = ConfGetNode("rule-files");
+ FAIL_IF_NULL(node);
+ FAIL_IF_NOT(ConfNodeIsSequence(node));
+ FAIL_IF(TAILQ_EMPTY(&node->head));
+ int i = 0;
+ ConfNode *filename;
+ TAILQ_FOREACH(filename, &node->head, next) {
+ if (i == 0) {
+ FAIL_IF(strcmp(filename->val, "netbios.rules") != 0);
+ FAIL_IF(ConfNodeIsSequence(filename));
+ FAIL_IF(filename->is_seq != 0);
+ }
+ else if (i == 1) {
+ FAIL_IF(strcmp(filename->val, "x11.rules") != 0);
+ FAIL_IF(ConfNodeIsSequence(filename));
+ }
+ FAIL_IF(i > 1);
+ i++;
+ }
+
+ ConfDeInit();
+ ConfRestoreContextBackup();
+ PASS;
+}
+
+static int
+ConfYamlLoggingOutputTest(void)
+{
+ char input[] = "\
+%YAML 1.1\n\
+---\n\
+logging:\n\
+ output:\n\
+ - interface: console\n\
+ log-level: error\n\
+ - interface: syslog\n\
+ facility: local4\n\
+ log-level: info\n\
+";
+
+ ConfCreateContextBackup();
+ ConfInit();
+
+ ConfYamlLoadString(input, strlen(input));
+
+ ConfNode *outputs;
+ outputs = ConfGetNode("logging.output");
+ FAIL_IF_NULL(outputs);
+
+ ConfNode *output;
+ ConfNode *output_param;
+
+ output = TAILQ_FIRST(&outputs->head);
+ FAIL_IF_NULL(output);
+ FAIL_IF(strcmp(output->name, "0") != 0);
+
+ output_param = TAILQ_FIRST(&output->head);
+ FAIL_IF_NULL(output_param);
+ FAIL_IF(strcmp(output_param->name, "interface") != 0);
+ FAIL_IF(strcmp(output_param->val, "console") != 0);
+
+ output_param = TAILQ_NEXT(output_param, next);
+ FAIL_IF(strcmp(output_param->name, "log-level") != 0);
+ FAIL_IF(strcmp(output_param->val, "error") != 0);
+
+ output = TAILQ_NEXT(output, next);
+ FAIL_IF_NULL(output);
+ FAIL_IF(strcmp(output->name, "1") != 0);
+
+ output_param = TAILQ_FIRST(&output->head);
+ FAIL_IF_NULL(output_param);
+ FAIL_IF(strcmp(output_param->name, "interface") != 0);
+ FAIL_IF(strcmp(output_param->val, "syslog") != 0);
+
+ output_param = TAILQ_NEXT(output_param, next);
+ FAIL_IF(strcmp(output_param->name, "facility") != 0);
+ FAIL_IF(strcmp(output_param->val, "local4") != 0);
+
+ output_param = TAILQ_NEXT(output_param, next);
+ FAIL_IF(strcmp(output_param->name, "log-level") != 0);
+ FAIL_IF(strcmp(output_param->val, "info") != 0);
+
+ ConfDeInit();
+ ConfRestoreContextBackup();
+
+ PASS;
+}
+
+/**
+ * Try to load something that is not a valid YAML file.
+ */
+static int
+ConfYamlNonYamlFileTest(void)
+{
+ ConfCreateContextBackup();
+ ConfInit();
+
+ FAIL_IF(ConfYamlLoadFile("/etc/passwd") != -1);
+
+ ConfDeInit();
+ ConfRestoreContextBackup();
+
+ PASS;
+}
+
+static int
+ConfYamlBadYamlVersionTest(void)
+{
+ char input[] = "\
+%YAML 9.9\n\
+---\n\
+logging:\n\
+ output:\n\
+ - interface: console\n\
+ log-level: error\n\
+ - interface: syslog\n\
+ facility: local4\n\
+ log-level: info\n\
+";
+
+ ConfCreateContextBackup();
+ ConfInit();
+
+ FAIL_IF(ConfYamlLoadString(input, strlen(input)) != -1);
+
+ ConfDeInit();
+ ConfRestoreContextBackup();
+
+ PASS;
+}
+
+static int
+ConfYamlSecondLevelSequenceTest(void)
+{
+ char input[] = "\
+%YAML 1.1\n\
+---\n\
+libhtp:\n\
+ server-config:\n\
+ - apache-php:\n\
+ address: [\"192.168.1.0/24\"]\n\
+ personality: [\"Apache_2_2\", \"PHP_5_3\"]\n\
+ path-parsing: [\"compress_separators\", \"lowercase\"]\n\
+ - iis-php:\n\
+ address:\n\
+ - 192.168.0.0/24\n\
+\n\
+ personality:\n\
+ - IIS_7_0\n\
+ - PHP_5_3\n\
+\n\
+ path-parsing:\n\
+ - compress_separators\n\
+";
+
+ ConfCreateContextBackup();
+ ConfInit();
+
+ FAIL_IF(ConfYamlLoadString(input, strlen(input)) != 0);
+
+ ConfNode *outputs;
+ outputs = ConfGetNode("libhtp.server-config");
+ FAIL_IF_NULL(outputs);
+
+ ConfNode *node;
+
+ node = TAILQ_FIRST(&outputs->head);
+ FAIL_IF_NULL(node);
+ FAIL_IF(strcmp(node->name, "0") != 0);
+
+ node = TAILQ_FIRST(&node->head);
+ FAIL_IF_NULL(node);
+ FAIL_IF(strcmp(node->name, "apache-php") != 0);
+
+ node = ConfNodeLookupChild(node, "address");
+ FAIL_IF_NULL(node);
+
+ node = TAILQ_FIRST(&node->head);
+ FAIL_IF_NULL(node);
+ FAIL_IF(strcmp(node->name, "0") != 0);
+ FAIL_IF(strcmp(node->val, "192.168.1.0/24") != 0);
+
+ ConfDeInit();
+ ConfRestoreContextBackup();
+
+ PASS;
+}
+
+/**
+ * Test file inclusion support.
+ */
+static int
+ConfYamlFileIncludeTest(void)
+{
+ FILE *config_file;
+
+ const char config_filename[] = "ConfYamlFileIncludeTest-config.yaml";
+ const char config_file_contents[] =
+ "%YAML 1.1\n"
+ "---\n"
+ "# Include something at the root level.\n"
+ "include: ConfYamlFileIncludeTest-include.yaml\n"
+ "# Test including under a mapping.\n"
+ "mapping: !include ConfYamlFileIncludeTest-include.yaml\n";
+
+ const char include_filename[] = "ConfYamlFileIncludeTest-include.yaml";
+ const char include_file_contents[] =
+ "%YAML 1.1\n"
+ "---\n"
+ "host-mode: auto\n"
+ "unix-command:\n"
+ " enabled: no\n";
+
+ ConfCreateContextBackup();
+ ConfInit();
+
+ /* Write out the test files. */
+ FAIL_IF_NULL((config_file = fopen(config_filename, "w")));
+ FAIL_IF(fwrite(config_file_contents, strlen(config_file_contents), 1, config_file) != 1);
+ fclose(config_file);
+
+ FAIL_IF_NULL((config_file = fopen(include_filename, "w")));
+ FAIL_IF(fwrite(include_file_contents, strlen(include_file_contents), 1, config_file) != 1);
+ fclose(config_file);
+
+ /* Reset conf_dirname. */
+ if (conf_dirname != NULL) {
+ SCFree(conf_dirname);
+ conf_dirname = NULL;
+ }
+
+ FAIL_IF(ConfYamlLoadFile("ConfYamlFileIncludeTest-config.yaml") != 0);
+
+ /* Check values that should have been loaded into the root of the
+ * configuration. */
+ ConfNode *node;
+ node = ConfGetNode("host-mode");
+ FAIL_IF_NULL(node);
+ FAIL_IF(strcmp(node->val, "auto") != 0);
+
+ node = ConfGetNode("unix-command.enabled");
+ FAIL_IF_NULL(node);
+ FAIL_IF(strcmp(node->val, "no") != 0);
+
+ /* Check for values that were included under a mapping. */
+ node = ConfGetNode("mapping.host-mode");
+ FAIL_IF_NULL(node);
+ FAIL_IF(strcmp(node->val, "auto") != 0);
+
+ node = ConfGetNode("mapping.unix-command.enabled");
+ FAIL_IF_NULL(node);
+ FAIL_IF(strcmp(node->val, "no") != 0);
+
+ ConfDeInit();
+ ConfRestoreContextBackup();
+
+ unlink(config_filename);
+ unlink(include_filename);
+
+ PASS;
+}
+
+/**
+ * Test that a configuration section is overridden but subsequent
+ * occurrences.
+ */
+static int
+ConfYamlOverrideTest(void)
+{
+ char config[] = "%YAML 1.1\n"
+ "---\n"
+ "some-log-dir: /var/log\n"
+ "some-log-dir: /tmp\n"
+ "\n"
+ "parent:\n"
+ " child0:\n"
+ " key: value\n"
+ "parent:\n"
+ " child1:\n"
+ " key: value\n"
+ "vars:\n"
+ " address-groups:\n"
+ " HOME_NET: \"[192.168.0.0/16,10.0.0.0/8,172.16.0.0/12]\"\n"
+ " EXTERNAL_NET: any\n"
+ "vars.address-groups.HOME_NET: \"10.10.10.10/32\"\n";
+ const char *value;
+
+ ConfCreateContextBackup();
+ ConfInit();
+
+ FAIL_IF(ConfYamlLoadString(config, strlen(config)) != 0);
+ FAIL_IF_NOT(ConfGet("some-log-dir", &value));
+ FAIL_IF(strcmp(value, "/tmp") != 0);
+
+ /* Test that parent.child0 does not exist, but child1 does. */
+ FAIL_IF_NOT_NULL(ConfGetNode("parent.child0"));
+ FAIL_IF_NOT(ConfGet("parent.child1.key", &value));
+ FAIL_IF(strcmp(value, "value") != 0);
+
+ /* First check that vars.address-groups.EXTERNAL_NET has the
+ * expected parent of vars.address-groups and save this
+ * pointer. We want to make sure that the overrided value has the
+ * same parent later on. */
+ ConfNode *vars_address_groups = ConfGetNode("vars.address-groups");
+ FAIL_IF_NULL(vars_address_groups);
+ ConfNode *vars_address_groups_external_net = ConfGetNode("vars.address-groups.EXTERNAL_NET");
+ FAIL_IF_NULL(vars_address_groups_external_net);
+ FAIL_IF_NOT(vars_address_groups_external_net->parent == vars_address_groups);
+
+ /* Now check that HOME_NET has the overrided value. */
+ ConfNode *vars_address_groups_home_net = ConfGetNode("vars.address-groups.HOME_NET");
+ FAIL_IF_NULL(vars_address_groups_home_net);
+ FAIL_IF(strcmp(vars_address_groups_home_net->val, "10.10.10.10/32") != 0);
+
+ /* And check that it has the correct parent. */
+ FAIL_IF_NOT(vars_address_groups_home_net->parent == vars_address_groups);
+
+ ConfDeInit();
+ ConfRestoreContextBackup();
+
+ PASS;
+}
+
+/**
+ * Test that a configuration parameter loaded from YAML doesn't
+ * override a 'final' value that may be set on the command line.
+ */
+static int
+ConfYamlOverrideFinalTest(void)
+{
+ ConfCreateContextBackup();
+ ConfInit();
+
+ char config[] =
+ "%YAML 1.1\n"
+ "---\n"
+ "default-log-dir: /var/log\n";
+
+ /* Set the log directory as if it was set on the command line. */
+ FAIL_IF_NOT(ConfSetFinal("default-log-dir", "/tmp"));
+ FAIL_IF(ConfYamlLoadString(config, strlen(config)) != 0);
+
+ const char *default_log_dir;
+
+ FAIL_IF_NOT(ConfGet("default-log-dir", &default_log_dir));
+ FAIL_IF(strcmp(default_log_dir, "/tmp") != 0);
+
+ ConfDeInit();
+ ConfRestoreContextBackup();
+
+ PASS;
+}
+
+static int ConfYamlNull(void)
+{
+ ConfCreateContextBackup();
+ ConfInit();
+
+ char config[] = "%YAML 1.1\n"
+ "---\n"
+ "quoted-tilde: \"~\"\n"
+ "unquoted-tilde: ~\n"
+ "quoted-null: \"null\"\n"
+ "unquoted-null: null\n"
+ "quoted-Null: \"Null\"\n"
+ "unquoted-Null: Null\n"
+ "quoted-NULL: \"NULL\"\n"
+ "unquoted-NULL: NULL\n"
+ "empty-quoted: \"\"\n"
+ "empty-unquoted: \n"
+ "list: [\"null\", null, \"Null\", Null, \"NULL\", NULL, \"~\", ~]\n";
+ FAIL_IF(ConfYamlLoadString(config, strlen(config)) != 0);
+
+ const char *val;
+
+ FAIL_IF_NOT(ConfGet("quoted-tilde", &val));
+ FAIL_IF_NULL(val);
+ FAIL_IF_NOT(ConfGet("unquoted-tilde", &val));
+ FAIL_IF_NOT_NULL(val);
+
+ FAIL_IF_NOT(ConfGet("quoted-null", &val));
+ FAIL_IF_NULL(val);
+ FAIL_IF_NOT(ConfGet("unquoted-null", &val));
+ FAIL_IF_NOT_NULL(val);
+
+ FAIL_IF_NOT(ConfGet("quoted-Null", &val));
+ FAIL_IF_NULL(val);
+ FAIL_IF_NOT(ConfGet("unquoted-Null", &val));
+ FAIL_IF_NOT_NULL(val);
+
+ FAIL_IF_NOT(ConfGet("quoted-NULL", &val));
+ FAIL_IF_NULL(val);
+ FAIL_IF_NOT(ConfGet("unquoted-NULL", &val));
+ FAIL_IF_NOT_NULL(val);
+
+ FAIL_IF_NOT(ConfGet("empty-quoted", &val));
+ FAIL_IF_NULL(val);
+ FAIL_IF_NOT(ConfGet("empty-unquoted", &val));
+ FAIL_IF_NOT_NULL(val);
+
+ FAIL_IF_NOT(ConfGet("list.0", &val));
+ FAIL_IF_NULL(val);
+ FAIL_IF_NOT(ConfGet("list.1", &val));
+ FAIL_IF_NOT_NULL(val);
+
+ FAIL_IF_NOT(ConfGet("list.2", &val));
+ FAIL_IF_NULL(val);
+ FAIL_IF_NOT(ConfGet("list.3", &val));
+ FAIL_IF_NOT_NULL(val);
+
+ FAIL_IF_NOT(ConfGet("list.4", &val));
+ FAIL_IF_NULL(val);
+ FAIL_IF_NOT(ConfGet("list.5", &val));
+ FAIL_IF_NOT_NULL(val);
+
+ FAIL_IF_NOT(ConfGet("list.6", &val));
+ FAIL_IF_NULL(val);
+ FAIL_IF_NOT(ConfGet("list.7", &val));
+ FAIL_IF_NOT_NULL(val);
+
+ ConfDeInit();
+ ConfRestoreContextBackup();
+
+ PASS;
+}
+
+#endif /* UNITTESTS */
+
+void
+ConfYamlRegisterTests(void)
+{
+#ifdef UNITTESTS
+ UtRegisterTest("ConfYamlSequenceTest", ConfYamlSequenceTest);
+ UtRegisterTest("ConfYamlLoggingOutputTest", ConfYamlLoggingOutputTest);
+ UtRegisterTest("ConfYamlNonYamlFileTest", ConfYamlNonYamlFileTest);
+ UtRegisterTest("ConfYamlBadYamlVersionTest", ConfYamlBadYamlVersionTest);
+ UtRegisterTest("ConfYamlSecondLevelSequenceTest",
+ ConfYamlSecondLevelSequenceTest);
+ UtRegisterTest("ConfYamlFileIncludeTest", ConfYamlFileIncludeTest);
+ UtRegisterTest("ConfYamlOverrideTest", ConfYamlOverrideTest);
+ UtRegisterTest("ConfYamlOverrideFinalTest", ConfYamlOverrideFinalTest);
+ UtRegisterTest("ConfYamlNull", ConfYamlNull);
+#endif /* UNITTESTS */
+}