/* Copyright (c) 2009 Open Information Security Foundation */ /** * This file provides a basic configuration system for the IDPS * engine. * * NOTE: Setting values should only be done from one thread during * engine initialization. Multiple threads should be able access read * configuration data. Allowing run time changes to the configuration * will require some locks. * * \author Endace Technology Limited - Jason Ish * * \todo Consider having the in-memory configuration database a direct * reflection of the configuration file and moving command line * parameters to a primary lookup table? */ #include #include "suricata-common.h" #include "conf.h" #include "util-unittest.h" #include "util-debug.h" static ConfNode *root = NULL; static ConfNode *root_backup = NULL; /** * \brief Initialize the configuration system. */ void ConfInit(void) { if (root != NULL) { SCLogDebug("already initialized"); return; } root = ConfNodeNew(); if (root == NULL) { SCLogError(SC_ERR_MEM_ALLOC, "ERROR: Failed to allocate memory for root configuration node, " "aborting."); exit(EXIT_FAILURE); } SCLogDebug("configuration module initialized"); } /** * \brief Allocate a new configuration node. * * \retval An allocated configuration node on success, NULL on failure. */ ConfNode * ConfNodeNew(void) { ConfNode *new; new = calloc(1, sizeof(*new)); if (new == NULL) { SCLogError(SC_ERR_MEM_ALLOC, "Error allocating memory for new configuration node"); exit(EXIT_FAILURE); } TAILQ_INIT(&new->head); return new; } /** * \brief Free a ConfNode and all of its children. * * \param node The configuration node to free. */ void ConfNodeFree(ConfNode *node) { ConfNode *tmp; while ((tmp = TAILQ_FIRST(&node->head))) { TAILQ_REMOVE(&node->head, tmp, next); ConfNodeFree(tmp); } if (node->name != NULL) free(node->name); if (node->val != NULL) free(node->val); free(node); } /** * \brief Get a ConfNode by name. * * \param key The full name of the configuration node to lookup. * * \retval A pointer to ConfNode is found or NULL if the configuration * node does not exist. */ ConfNode * ConfGetNode(char *key) { ConfNode *node = root; char *saveptr; char *token; /* Need to dup the key for tokenization... */ char *tokstr = strdup(key); token = strtok_r(tokstr, ".", &saveptr); for (;;) { node = ConfNodeLookupChild(node, token); if (node == NULL) break; token = strtok_r(NULL, ".", &saveptr); if (token == NULL) break; } free(tokstr); return node; } /** * \brief Get the root configuration node. */ ConfNode * ConfGetRootNode(void) { return root; } /** * \brief Set a configuration value. * * \param name The name of the configuration parameter to set. * \param val The value of the configuration parameter. * \param allow_override Can a subsequent set override this value. * * \retval 1 if the value was set otherwise 0. */ int ConfSet(char *name, char *val, int allow_override) { ConfNode *parent = root; ConfNode *node; char *token; char *saveptr; /* First check if the node already exists. */ node = ConfGetNode(name); if (node != NULL) { if (!node->allow_override) { return 0; } else { if (node->val != NULL) free(node->val); node->val = strdup(val); node->allow_override = allow_override; return 1; } } else { char *tokstr = strdup(name); token = strtok_r(tokstr, ".", &saveptr); node = ConfNodeLookupChild(parent, token); for (;;) { if (node == NULL) { node = ConfNodeNew(); node->name = strdup(token); node->parent = parent; TAILQ_INSERT_TAIL(&parent->head, node, next); parent = node; } token = strtok_r(NULL, ".", &saveptr); if (token == NULL) { if (node->val != NULL) free(node->val); node->val = strdup(val); node->allow_override = allow_override; break; } else { node = ConfNodeLookupChild(parent, token); } } free(tokstr); } SCLogDebug("configuration parameter '%s' set", name); return 1; } /** * \brief Retrieve the value of a configuration node. * * This function will return the value for a configuration node based * on the full name of the node. It is possible that the value * returned could be NULL, this could happen if the requested node * does exist but is not a node that contains a value, but contains * children ConfNodes instead. * * \param name Name of configuration parameter to get. * \param vptr Pointer that will be set to the configuration value parameter. * Note that this is just a reference to the actual value, not a copy. * * \retval 1 will be returned if the name is found, otherwise 0 will * be returned. */ int ConfGet(char *name, char **vptr) { ConfNode *node = ConfGetNode(name); if (node == NULL) { SCLogDebug("failed to lookup configuration parameter '%s'", name); return 0; } else { *vptr = node->val; return 1; } } /** * \brief Retrieve a configuration value as an integer. * * \param name Name of configuration parameter to get. * \param val Pointer to an intmax_t that will be set the * configuration value. * * \retval 1 will be returned if the name is found and was properly * converted to an interger, otherwise 0 will be returned. */ int ConfGetInt(char *name, intmax_t *val) { char *strval; intmax_t tmpint; char *endptr; if (ConfGet(name, &strval) == 0) return 0; errno = 0; tmpint = strtoimax(strval, &endptr, 0); if (strval[0] == '\0' || *endptr != '\0') return 0; if (errno == ERANGE && (tmpint == INTMAX_MAX || tmpint == INTMAX_MIN)) return 0; *val = tmpint; return 1; } /** * \brief Retrieve a configuration value as an boolen. * * \param name Name of configuration parameter to get. * \param val Pointer to an int that will be set to 1 for true, or 0 * for false. * * \retval 1 will be returned if the name is found and was properly * converted to a boolean, otherwise 0 will be returned. */ int ConfGetBool(char *name, int *val) { char *strval; char *trues[] = {"1", "yes", "true", "on"}; size_t u; *val = 0; if (ConfGet(name, &strval) != 1) return 0; for (u = 0; u < sizeof(trues) / sizeof(trues[0]); u++) { if (strcasecmp(strval, trues[u]) == 0) { *val = 1; break; } } return 1; } /** * \brief Remove (and free) the provided configuration node. */ void ConfNodeRemove(ConfNode *node) { if (node->parent != NULL) TAILQ_REMOVE(&node->parent->head, node, next); ConfNodeFree(node); } /** * \brief Remove a configuration parameter from the configuration db. * * \param name The name of the configuration parameter to remove. * * \retval Returns 1 if the parameter was removed, otherwise 0 is returned * most likely indicating the parameter was not set. */ int ConfRemove(char *name) { ConfNode *node; node = ConfGetNode(name); if (node == NULL) return 0; else { ConfNodeRemove(node); return 1; } } /** * \brief Creates a backup of the conf_hash hash_table used by the conf API. */ void ConfCreateContextBackup(void) { root_backup = root; root = NULL; return; } /** * \brief Restores the backup of the hash_table present in backup_conf_hash * back to conf_hash. */ void ConfRestoreContextBackup(void) { root = root_backup; return; } /** * \brief De-initializes the configuration system. */ void ConfDeInit(void) { if (root != NULL) ConfNodeFree(root); SCLogDebug("configuration module de-initialized"); } static char * ConfPrintNameArray(char **name_arr, int level) { static char name[128*128]; int i; name[0] = '\0'; for (i = 0; i <= level; i++) { strlcat(name, name_arr[i], sizeof(name)); if (i < level) strlcat(name, ".", sizeof(name)); } return name; } /** * \brief Dump a configuration node and all its children. */ void ConfNodeDump(ConfNode *node, const char *prefix) { ConfNode *child; static char *name[128]; static int level = -1; level++; TAILQ_FOREACH(child, &node->head, next) { name[level] = strdup(child->name); if (prefix == NULL) { printf("%s = %s\n", ConfPrintNameArray(name, level), child->val); } else { printf("%s.%s = %s\n", prefix, ConfPrintNameArray(name, level), child->val); } ConfNodeDump(child, prefix); free(name[level]); } level--; } /** * \brief Dump configuration to stdout. */ void ConfDump(void) { ConfNodeDump(root, NULL); } /** * \brief Lookup a child configuration node by name. * * Given a ConfNode this function will lookup an immediate child * ConfNode by name and return the child ConfNode. * * \param node The parent configuration node. * \param name The name of the child node to lookup. * * \retval A pointer the child ConfNode if found otherwise NULL. */ ConfNode * ConfNodeLookupChild(ConfNode *node, const char *name) { ConfNode *child; TAILQ_FOREACH(child, &node->head, next) { if (strcmp(child->name, name) == 0) return child; } return NULL; } /** * \brief Lookup the value of a child configuration node by name. * * Given a parent ConfNode this function will return the value of a * child configuration node by name returning a reference to that * value. * * \param node The parent configuration node. * \param name The name of the child node to lookup. * * \retval A pointer the child ConfNodes value if found otherwise NULL. */ const char * ConfNodeLookupChildValue(ConfNode *node, const char *name) { ConfNode *child; child = ConfNodeLookupChild(node, name); if (child != NULL) return child->val; return NULL; } #ifdef UNITTESTS /** * Lookup a non-existant value. */ static int ConfTestGetNonExistant(void) { char name[] = "non-existant-value"; char *value; return !ConfGet(name, &value); } /** * Set then lookup a value. */ static int ConfTestSetAndGet(void) { char name[] = "some-name"; char value[] = "some-value"; char *value0; if (ConfSet(name, value, 1) != 1) return 0; if (ConfGet(name, &value0) != 1) return 0; if (strcmp(value, value0) != 0) return 0; /* Cleanup. */ ConfRemove(name); return 1; } /** * Test that overriding a value is allowed provided allow_override is * true and that the config parameter gets the new value. */ static int ConfTestOverrideValue1(void) { char name[] = "some-name"; char value0[] = "some-value"; char value1[] = "new-value"; char *val; int rc; if (ConfSet(name, value0, 1) != 1) return 0; if (ConfSet(name, value1, 1) != 1) return 0; if (ConfGet(name, &val) != 1) return 0; rc = !strcmp(val, value1); /* Cleanup. */ ConfRemove(name); return rc; } /** * Test that overriding a value is not allowed provided that * allow_override is false and make sure the value was not overrided. */ static int ConfTestOverrideValue2(void) { char name[] = "some-name"; char value0[] = "some-value"; char value1[] = "new-value"; char *val; int rc; if (ConfSet(name, value0, 0) != 1) return 0; if (ConfSet(name, value1, 1) != 0) return 0; if (ConfGet(name, &val) != 1) return 0; rc = !strcmp(val, value0); /* Cleanup. */ ConfRemove(name); return rc; } /** * Test retrieving an integer value from the configuration db. */ static int ConfTestGetInt(void) { char name[] = "some-int.x"; intmax_t val; if (ConfSet(name, "0", 1) != 1) return 0; if (ConfGetInt(name, &val) != 1) return 0; return 1; if (val != 0) return 0; if (ConfSet(name, "-1", 1) != 1) return 0; if (ConfGetInt(name, &val) != 1) return 0; if (val != -1) return 0; if (ConfSet(name, "0xffff", 1) != 1) return 0; if (ConfGetInt(name, &val) != 1) return 0; if (val != 0xffff) return 0; if (ConfSet(name, "not-an-int", 1) != 1) return 0; if (ConfGetInt(name, &val) != 0) return 0; return 1; } /** * Test retrieving a boolean value from the configuration db. */ static int ConfTestGetBool(void) { char name[] = "some-bool"; char *trues[] = { "1", "on", "ON", "yes", "YeS", "true", "TRUE", }; char *falses[] = { "0", "something", "off", "OFF", "false", "FalSE", "no", "NO", }; int val; size_t u; for (u = 0; u < sizeof(trues) / sizeof(trues[0]); u++) { if (ConfSet(name, trues[u], 1) != 1) return 0; if (ConfGetBool(name, &val) != 1) return 0; if (val != 1) return 0; } for (u = 0; u < sizeof(falses) / sizeof(falses[0]); u++) { if (ConfSet(name, falses[u], 1) != 1) return 0; if (ConfGetBool(name, &val) != 1) return 0; if (val != 0) return 0; } return 1; } static int ConfNodeLookupChildTest(void) { char *test_vals[] = { "one", "two", "three" }; size_t u; ConfNode *parent = ConfNodeNew(); ConfNode *child; for (u = 0; u < sizeof(test_vals)/sizeof(test_vals[0]); u++) { child = ConfNodeNew(); child->name = strdup(test_vals[u]); child->val = strdup(test_vals[u]); TAILQ_INSERT_TAIL(&parent->head, child, next); } child = ConfNodeLookupChild(parent, "one"); if (child == NULL) return 0; if (strcmp(child->name, "one") != 0) return 0; if (strcmp(child->val, "one") != 0) return 0; child = ConfNodeLookupChild(parent, "two"); if (child == NULL) return 0; if (strcmp(child->name, "two") != 0) return 0; if (strcmp(child->val, "two") != 0) return 0; child = ConfNodeLookupChild(parent, "three"); if (child == NULL) return 0; if (strcmp(child->name, "three") != 0) return 0; if (strcmp(child->val, "three") != 0) return 0; child = ConfNodeLookupChild(parent, "four"); if (child != NULL) return 0; ConfNodeFree(parent); return 1; } static int ConfNodeLookupChildValueTest(void) { char *test_vals[] = { "one", "two", "three" }; size_t u; ConfNode *parent = ConfNodeNew(); ConfNode *child; const char *value; for (u = 0; u < sizeof(test_vals)/sizeof(test_vals[0]); u++) { child = ConfNodeNew(); child->name = strdup(test_vals[u]); child->val = strdup(test_vals[u]); TAILQ_INSERT_TAIL(&parent->head, child, next); } value = (char *)ConfNodeLookupChildValue(parent, "one"); if (value == NULL) return 0; if (strcmp(value, "one") != 0) return 0; value = (char *)ConfNodeLookupChildValue(parent, "two"); if (value == NULL) return 0; if (strcmp(value, "two") != 0) return 0; value = (char *)ConfNodeLookupChildValue(parent, "three"); if (value == NULL) return 0; if (strcmp(value, "three") != 0) return 0; value = (char *)ConfNodeLookupChildValue(parent, "four"); if (value != NULL) return 0; ConfNodeFree(parent); return 1; } /** * Test the removal of a configuration node. */ static int ConfNodeRemoveTest(void) { ConfCreateContextBackup(); ConfInit(); if (ConfSet("some.nested.parameter", "blah", 1) != 1) return 0; ConfNode *node = ConfGetNode("some.nested.parameter"); if (node == NULL) return 0; ConfNodeRemove(node); node = ConfGetNode("some.nested.parameter"); if (node != NULL) return 0; ConfDeInit(); ConfRestoreContextBackup(); return 1; } void ConfRegisterTests(void) { UtRegisterTest("ConfTestGetNonExistant", ConfTestGetNonExistant, 1); UtRegisterTest("ConfTestSetAndGet", ConfTestSetAndGet, 1); UtRegisterTest("ConfTestOverrideValue1", ConfTestOverrideValue1, 1); UtRegisterTest("ConfTestOverrideValue2", ConfTestOverrideValue2, 1); UtRegisterTest("ConfTestGetInt", ConfTestGetInt, 1); UtRegisterTest("ConfTestGetBool", ConfTestGetBool, 1); UtRegisterTest("ConfNodeLookupChildTest", ConfNodeLookupChildTest, 1); UtRegisterTest("ConfNodeLookupChildValueTest", ConfNodeLookupChildValueTest, 1); UtRegisterTest("ConfNodeRemoveTest", ConfNodeRemoveTest, 1); } #endif /* UNITTESTS */