#include <yaml.h>
#include "suricata-common.h"
#include "conf.h"
+#include "util-path.h"
#include "util-debug.h"
#include "util-unittest.h"
#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);
+
/* Configuration processing states. */
enum conf_state {
CONF_KEY = 0,
CONF_VAL,
+ CONF_INCLUDE,
};
/**
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) {
+ SCLogError(SC_ERR_MEM_ALLOC,
+ "ERROR: Failed to allocate memory while loading configuration.");
+ exit(EXIT_FAILURE);
+ }
+ }
+ else {
+ conf_dirname = SCStrdup(filename);
+ if (conf_dirname == NULL) {
+ SCLogError(SC_ERR_MEM_ALLOC,
+ "ERROR: Failed to allocate memory while loading configuration.");
+ exit(EXIT_FAILURE);
+ }
+ 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.
+ */
+static int
+ConfYamlHandleInclude(ConfNode *parent, const char *filename)
+{
+ yaml_parser_t parser;
+ char include_filename[PATH_MAX];
+ FILE *file;
+
+ if (yaml_parser_initialize(&parser) != 1) {
+ SCLogError(SC_ERR_CONF_YAML_ERROR, "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(SC_ERR_FOPEN,
+ "Failed to open configuration include file %s: %s",
+ include_filename, strerror(errno));
+ return -1;
+ }
+
+ yaml_parser_set_input_file(&parser, file);
+
+ if (ConfYamlParse(&parser, parent, 0) != 0) {
+ SCLogError(SC_ERR_CONF_YAML_ERROR,
+ "Failed to include configuration file %s", filename);
+ return -1;
+ }
+
+ yaml_parser_delete(&parser);
+ fclose(file);
+
+ return 0;
+}
+
/**
* \brief Parse a YAML layer.
*
while (!done) {
if (!yaml_parser_parse(parser, &event)) {
- fprintf(stderr,
+ SCLogError(SC_ERR_CONF_YAML_ERROR,
"Failed to parse configuration file at line %" PRIuMAX ": %s\n",
(uintmax_t)parser->problem_mark.line, parser->problem);
return -1;
}
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. */
}
else if (event.type == YAML_SCALAR_EVENT) {
char *value = (char *)event.data.scalar.value;
- SCLogDebug("event.type = YAML_SCALAR_EVENT (%s) inseq=%d",
- value, inseq);
+ 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);
if (inseq) {
ConfNode *seq_node = ConfNodeNew();
seq_node->name = SCCalloc(1, DEFAULT_NAME_LEN);
TAILQ_INSERT_TAIL(&parent->head, seq_node, next);
}
else {
- if (state == CONF_KEY) {
+ 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) {
+
+ /* Top level include statements. */
+ if ((strcmp(value, "include") == 0) &&
+ (parent == ConfGetRootNode())) {
+ state = CONF_INCLUDE;
+ goto next;
+ }
+
if (parent->is_seq) {
if (parent->val == NULL) {
parent->val = SCStrdup(value);
state = CONF_VAL;
}
else {
- if (node->allow_override) {
+ if ((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->allow_override) {
if (node->val != NULL)
SCFree(node->val);
node->val = SCStrdup(value);
}
}
else if (event.type == YAML_SEQUENCE_START_EVENT) {
- SCLogDebug("event.type = YAML_SEQUENCE_START_EVENT");
+ SCLogDebug("event.type=YAML_SEQUENCE_START_EVENT; state=%d", state);
if (ConfYamlParse(parser, node, 1) != 0)
goto fail;
state = CONF_KEY;
}
else if (event.type == YAML_SEQUENCE_END_EVENT) {
- SCLogDebug("event.type = YAML_SEQUENCE_END_EVENT");
+ SCLogDebug("event.type=YAML_SEQUENCE_END_EVENT; state=%d", state);
return 0;
}
else if (event.type == YAML_MAPPING_START_EVENT) {
- SCLogDebug("event.type = YAML_MAPPING_START_EVENT");
+ SCLogDebug("event.type=YAML_MAPPING_START_EVENT; state=%d", state);
if (inseq) {
ConfNode *seq_node = ConfNodeNew();
seq_node->is_seq = 1;
state = CONF_KEY;
}
else if (event.type == YAML_MAPPING_END_EVENT) {
- SCLogDebug("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;
yaml_parser_delete(&parser);
return -1;
}
+
+ if (conf_dirname == NULL) {
+ ConfYamlSetConfDirname(filename);
+ }
+
yaml_parser_set_input_file(&parser, infile);
ret = ConfYamlParse(&parser, root, 0);
yaml_parser_delete(&parser);
return 1;
}
+/**
+ * Test file inclusion support.
+ */
+static int
+ConfYamlFileIncludeTest(void)
+{
+ int ret = 0;
+ 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. */
+ if ((config_file = fopen(config_filename, "w")) == NULL) {
+ goto cleanup;
+ }
+ if (fwrite(config_file_contents, strlen(config_file_contents), 1,
+ config_file) != 1) {
+ goto cleanup;
+ }
+ fclose(config_file);
+ if ((config_file = fopen(include_filename, "w")) == NULL) {
+ goto cleanup;
+ }
+ if (fwrite(include_file_contents, strlen(include_file_contents), 1,
+ config_file) != 1) {
+ goto cleanup;
+ }
+ fclose(config_file);
+
+ /* Reset conf_dirname. */
+ if (conf_dirname != NULL) {
+ SCFree(conf_dirname);
+ conf_dirname = NULL;
+ }
+
+ if (ConfYamlLoadFile("ConfYamlFileIncludeTest-config.yaml") != 0)
+ goto cleanup;
+
+ /* Check values that should have been loaded into the root of the
+ * configuration. */
+ ConfNode *node;
+ node = ConfGetNode("host-mode");
+ if (node == NULL) goto cleanup;
+ if (strcmp(node->val, "auto") != 0) goto cleanup;
+ node = ConfGetNode("unix-command.enabled");
+ if (node == NULL) goto cleanup;
+ if (strcmp(node->val, "no") != 0) goto cleanup;
+
+ /* Check for values that were included under a mapping. */
+ node = ConfGetNode("mapping.host-mode");
+ if (node == NULL) goto cleanup;
+ if (strcmp(node->val, "auto") != 0) goto cleanup;
+ node = ConfGetNode("mapping.unix-command.enabled");
+ if (node == NULL) goto cleanup;
+ if (strcmp(node->val, "no") != 0) goto cleanup;
+
+ ConfDeInit();
+ ConfRestoreContextBackup();
+
+ ret = 1;
+
+cleanup:
+ unlink(config_filename);
+ unlink(include_filename);
+
+ return ret;
+}
+
#endif /* UNITTESTS */
void
UtRegisterTest("ConfYamlBadYamlVersionTest", ConfYamlBadYamlVersionTest, 1);
UtRegisterTest("ConfYamlSecondLevelSequenceTest",
ConfYamlSecondLevelSequenceTest, 1);
+ UtRegisterTest("ConfYamlFileIncludeTest", ConfYamlFileIncludeTest, 1);
#endif /* UNITTESTS */
}