diff --git a/src/conf-yaml-loader.c b/src/conf-yaml-loader.c index b90a29af463..80c6409e8fe 100644 --- a/src/conf-yaml-loader.c +++ b/src/conf-yaml-loader.c @@ -24,6 +24,7 @@ */ #include +#include #include "suricata-common.h" #include "conf.h" #include "util-debug.h" @@ -118,7 +119,8 @@ ConfYamlParse(yaml_parser_t *parser, ConfNode *parent, int inseq) if (seq_node->name == NULL) return -1; snprintf(seq_node->name, DEFAULT_NAME_LEN, "%d", seq_idx++); - seq_node->val = SCStrdup(value); + if (NULL == (seq_node->val = ConfExpandEnvVar(value))) + seq_node->val = SCStrdup(value); TAILQ_INSERT_TAIL(&parent->head, seq_node, next); } else { @@ -161,7 +163,8 @@ ConfYamlParse(yaml_parser_t *parser, ConfNode *parent, int inseq) if (node->allow_override) { if (node->val != NULL) SCFree(node->val); - node->val = SCStrdup(value); + if (NULL == (node->val = ConfExpandEnvVar(value))) + node->val = SCStrdup(value); } state = CONF_KEY; } diff --git a/src/conf.c b/src/conf.c index bccdbc2be6c..3a7541f4122 100644 --- a/src/conf.c +++ b/src/conf.c @@ -773,8 +773,141 @@ char *ConfLoadCompleteIncludePath(char *file) return path; } +/** + * \brief Get the compiled pcre for environment variable expansion. + * + * This compiles the regular expression used in variable expansion and + * caches it for re-user. + * + * \retval The compiled pcre + */ +static pcre * +ConfGetEnvVarPcre(void) +{ + static pcre *envvar_pcre = NULL; + const char *error; + int erroroffset; + + static const char pattern[] = "\\$\\{" + "(" "[^\\$\\{\\}:-]*" ")" + "(:-(" "[^\\$\\{\\}:-]*" "))?" + "\\}"; + + if (envvar_pcre == NULL) { + envvar_pcre = pcre_compile(pattern, 0, &error, &erroroffset, NULL); + if (envvar_pcre == NULL) { + fprintf(stderr, "ERROR: Failed to compile pcre: %s", error); + exit(1); + } + } + + return envvar_pcre; +} + +/** + * \brief Perform environment variable expansion on the provided string. + * + * \param string The string to perform environment variable expansion. + * + * \retval A new string with environment variables expanded, only if expansion + * took place. Otherwise NULL is returned. As this is a new string it + * must be free'd by the caller. + */ +char * +ConfExpandEnvVar(char *input) +{ + char *string = NULL; + const char *var_name; + const char *var_val = NULL; + int var_val_len = 0; + int segment_start; + int segment_end; + const char *default_val = NULL; + char *pre_string = NULL; + char *post_string = NULL; + char *new_str = NULL; + int new_str_len; + int ovector[12]; + + /* Make a copy of the input string as we're destructive. */ + string = strdup(input); + if (unlikely(string == NULL)) { + SCLogError(SC_ERR_MEM_ALLOC, + "Error allocating memory for variable expansion"); + exit(EXIT_FAILURE); + } + + int match = pcre_exec(ConfGetEnvVarPcre(), NULL, string, strlen(string), 0, + 0, ovector, 12); + if (match < 2) { + goto end; + } + segment_start = ovector[0]; + segment_end = ovector[1]; + + if (pcre_get_substring(string, ovector, match, 1, &var_name) < 0) { + fprintf(stderr, "pcre failure\n"); + exit(1); + } + + /* Do we also have a default? */ + if (match == 4) { + if (pcre_get_substring(string, ovector, match, 3, &default_val) < 0) { + fprintf(stderr, "pcre failure\n"); + exit(1); + } + } + + /* Get the environment variable, using the optional default if the + * environment variable is not set. */ + if (NULL != (var_val = getenv(var_name))) { + var_val_len = strlen(var_val); + } + else if (default_val != NULL) { + var_val = default_val; + var_val_len = strlen(var_val); + } + /* Calculate the length of the new string including termination + * and then allocate it. */ + new_str_len = strlen(string) - (segment_end - segment_start) + + var_val_len + 1; + BUG_ON(new_str_len < 1); + new_str = SCCalloc(1, new_str_len); + if (unlikely(new_str == NULL)) { + SCLogError(SC_ERR_MEM_ALLOC, + "Error allocating memory for variable expansion"); + exit(EXIT_FAILURE); + } + + /* Build the new string. */ + if ((size_t)segment_end < strlen(string)) { + post_string = string + segment_end; + } + if (segment_start) { + pre_string = string; + pre_string[segment_start] = '\0'; + } + if (pre_string) + strlcat(new_str, pre_string, new_str_len); + if (var_val != NULL) + strlcat(new_str, var_val, new_str_len); + if (post_string != NULL) + strlcat(new_str, post_string, new_str_len); + + /* Recurse to expand other variables. */ + char *new_new_str = ConfExpandEnvVar(new_str); + if (new_new_str != NULL) { + SCFree(new_str); + new_str = new_new_str; + } + +end: + SCFree(string); + + return new_str; +} #ifdef UNITTESTS @@ -1194,6 +1327,158 @@ ConfSetTest(void) return 1; } +static int +ConfEnvVarExpandTest(void) +{ + char *new; + const char *old_foo = getenv("FOO"); + const char *old_bar = getenv("BAR"); + + setenv("FOO", "bar", 1); + setenv("BAR", "foo", 1); + + if (ConfExpandEnvVar("something") != NULL) + return 0; + if (ConfExpandEnvVar("$something") != NULL) + return 0; + if (ConfExpandEnvVar("${something") != NULL) + return 0; + + new = ConfExpandEnvVar("${}"); + if (new == NULL || strcmp(new, "") != 0) { + return 0; + } + SCFree(new); + + new = ConfExpandEnvVar("${FOO}"); + if (new == NULL || strcmp(new, "bar") != 0) { + return 0; + } + SCFree(new); + + new = ConfExpandEnvVar("pre${FOO}"); + if (new == NULL || strcmp(new, "prebar") != 0) { + return 0; + } + SCFree(new); + + new = ConfExpandEnvVar("${FOO}post"); + if (new == NULL || strcmp(new, "barpost") != 0) { + fprintf(stderr, "expected '%s', got '%s'\n", "barpost", new); + return 0; + } + SCFree(new); + + new = ConfExpandEnvVar("pre${FOO}post"); + if (new == NULL || strcmp(new, "prebarpost") != 0) { + fprintf(stderr, "expected '%s', got '%s'\n", "prebarpost", new); + return 0; + } + SCFree(new); + + new = ConfExpandEnvVar("pre${NOFOO}post"); + if (new == NULL || strcmp(new, "prepost") != 0) { + fprintf(stderr, "expected '%s', got '%s'\n", "prepost", new); + return 0; + } + SCFree(new); + + new = ConfExpandEnvVar("${FOO}${BAR}"); + if (new == NULL || strcmp(new, "barfoo") != 0) { + fprintf(stderr, "expected '%s', got '%s'\n", "barfoo", new); + return 0; + } + SCFree(new); + + new = ConfExpandEnvVar("${FOO}${BAR}${FOOBAR}"); + if (new == NULL || strcmp(new, "barfoo") != 0) { + fprintf(stderr, "expected '%s', got '%s'\n", "barfoo", new); + return 0; + } + SCFree(new); + + new = ConfExpandEnvVar("${USER}"); + if (new == NULL || strcmp(new, getenv("USER")) != 0) { + fprintf(stderr, "expected '%s', got '%s'\n", getenv("USER"), new); + return 0; + } + SCFree(new); + + unsetenv("FOO"); + unsetenv("BAR"); + + if (old_foo != NULL) + setenv("FOO", old_foo, 1); + if (old_bar != NULL) + setenv("BAR", old_bar, 1); + + return 1; +} + +static int +ConfEnvVarExpandTestWithDefaultValue(void) +{ + char *new; + + new = ConfExpandEnvVar("${FOO:-foo}"); + if (new == NULL || strcmp(new, "foo") != 0) { + fprintf(stderr, "%d: expected '%s', got '%s'\n", __LINE__, "foo", new); + return 0; + } + SCFree(new); + + new = ConfExpandEnvVar("pre${FOO:-foo}post"); + if (new == NULL || strcmp(new, "prefoopost") != 0) { + fprintf(stderr, "%d: expected '%s', got '%s'\n", __LINE__, "foo", new); + return 0; + } + SCFree(new); + + const char *old_foo = getenv("FOO"); + const char *old_bar = getenv("BAR"); + + setenv("FOO", "bar", 1); + setenv("BAR", "foo", 1); + + new = ConfExpandEnvVar("${FOO:-foo}"); + if (new == NULL || strcmp(new, "bar") != 0) { + fprintf(stderr, "expected '%s', got '%s'\n", "foo", new); + return 0; + } + SCFree(new); + + new = ConfExpandEnvVar("${FOO:-${BAR}}"); + if (new == NULL || strcmp(new, "bar") != 0) { + fprintf(stderr, "expected '%s', got '%s'\n", "foo", new); + return 0; + } + SCFree(new); + + new = ConfExpandEnvVar("${NOFOO:-${BAR}}"); + if (new == NULL || strcmp(new, "foo") != 0) { + fprintf(stderr, "expected '%s', got '%s'\n", "foo", new); + return 0; + } + SCFree(new); + + /* A bit silly now... */ + new = ConfExpandEnvVar("${NOFOO:-${NOBAR:-nofoobar}}"); + if (new == NULL || strcmp(new, "nofoobar") != 0) { + fprintf(stderr, "expected '%s', got '%s'\n", "foo", new); + return 0; + } + SCFree(new); + + unsetenv("FOO"); + unsetenv("BAR"); + + if (old_foo != NULL) + setenv("FOO", old_foo, 1); + if (old_bar != NULL) + setenv("BAR", old_bar, 1); + + return 1; +} void ConfRegisterTests(void) @@ -1211,6 +1496,9 @@ ConfRegisterTests(void) UtRegisterTest("ConfGetChildValueWithDefaultTest", ConfGetChildValueWithDefaultTest, 1); UtRegisterTest("ConfGetChildValueIntWithDefaultTest", ConfGetChildValueIntWithDefaultTest, 1); UtRegisterTest("ConfGetChildValueBoolWithDefaultTest", ConfGetChildValueBoolWithDefaultTest, 1); + UtRegisterTest("ConfEnvVarExpandTest", ConfEnvVarExpandTest, 1); + UtRegisterTest("ConfEnvVarExpandTestWithDefaultValue", + ConfEnvVarExpandTestWithDefaultValue, 1); } #endif /* UNITTESTS */ diff --git a/src/conf.h b/src/conf.h index c9a089f771b..09d203f4d29 100644 --- a/src/conf.h +++ b/src/conf.h @@ -85,4 +85,6 @@ int ConfGetChildValueIntWithDefault(ConfNode *base, ConfNode *dflt, char *name, int ConfGetChildValueBoolWithDefault(ConfNode *base, ConfNode *dflt, char *name, int *val); char *ConfLoadCompleteIncludePath(char *); +char *ConfExpandEnvVar(char *string); + #endif /* ! __CONF_H__ */ diff --git a/suricata.yaml.in b/suricata.yaml.in index 7d8fd671bf5..3a93842c227 100644 --- a/suricata.yaml.in +++ b/suricata.yaml.in @@ -825,7 +825,7 @@ ipfw: # Set the default rule path here to search for the files. # if not set, it will look at the current working dir -default-rule-path: @e_sysconfdir@rules +default-rule-path: ${SURICATA_RULE_PATH:-@e_sysconfdir@rules} rule-files: - botcc.rules - ciarmy.rules