diff --git a/src/conf-yaml-loader.c b/src/conf-yaml-loader.c index d99ad67d57..3b456232f9 100644 --- a/src/conf-yaml-loader.c +++ b/src/conf-yaml-loader.c @@ -17,19 +17,6 @@ #define YAML_VERSION_MAJOR 1 #define YAML_VERSION_MINOR 1 -/* Define to print the current YAML state. */ -#undef PRINT_STATES -#ifdef PRINT_STATES -#define DPRINT_STATE(x) do { SCLogDebug x ; } while (0) -#else -#define DPRINT_STATE(x) -#endif /* PRINT_STATES */ - -/* Defines the maximum number of levels YAML may nest. This is - * primarily used for construction of lookup-keys for configuration - * values. */ -#define MAX_LEVELS 16 - /* 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. */ @@ -39,57 +26,18 @@ enum conf_state { CONF_KEY = 0, CONF_VAL, - CONF_SEQ, }; -/** - * \brief Return the name of the current configuration key value. - * - * This function returns the current value of the configuration key. - * This is all the key components joined together with a ".". - * - * NOTE: This function is not re-entrant safe, but we do not expect to - * be loading configuration files concurrently. - */ -static char * -GetKeyName(char **key, int level) -{ - /* Statically allocate a string that should be large enough. */ - static char print_key[1024]; - int i; - - print_key[0] = '\0'; - - for (i = 0; i <= level; i++) { - if (key[i] == NULL) - break; - if (strlen(key[i]) + strlen(print_key) + 2 > sizeof(print_key)) { - /* Overflow. */ - return NULL; - } - else { - strlcat(print_key, key[i], sizeof(print_key)); - if (i < level) - strlcat(print_key, ".", sizeof(print_key)); - } - } - - return print_key; -} - /** * \brief Parse a YAML layer. * - * This will eventually replace ConfYamlParse but for now its just - * used to load lists. - * * \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 -ConfYamlParse2(yaml_parser_t *parser, ConfNode *parent, int inseq) +ConfYamlParse(yaml_parser_t *parser, ConfNode *parent, int inseq) { ConfNode *node = parent; yaml_event_t event; @@ -104,7 +52,26 @@ ConfYamlParse2(yaml_parser_t *parser, ConfNode *parent, int inseq) return -1; } - if (event.type == YAML_SCALAR_EVENT) { + if (event.type == YAML_DOCUMENT_START_EVENT) { + /* 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) { + fprintf(stderr, "ERROR: Invalid configuration file.\n\n"); + fprintf(stderr, "The configuration file must begin with the following two lines:\n\n"); + fprintf(stderr, "%%YAML 1.1\n---\n\n"); + goto fail; + } + int major = event.data.document_start.version_directive->major; + int minor = event.data.document_start.version_directive->minor; + if (!(major == YAML_VERSION_MAJOR && minor == YAML_VERSION_MINOR)) { + fprintf(stderr, "ERROR: Invalid YAML version. Must be 1.1\n"); + goto fail; + } + } + else if (event.type == YAML_SCALAR_EVENT) { char *value = (char *)event.data.scalar.value; SCLogDebug("event.type = YAML_SCALAR_EVENT (%s) inseq=%d\n", value, inseq); @@ -117,6 +84,20 @@ ConfYamlParse2(yaml_parser_t *parser, ConfNode *parent, int inseq) } else { if (state == CONF_KEY) { + /* If the node already exists, check if we can + * override it. If we can, free it then continue + * otherwise move onto the next configuration + * parameter. */ + ConfNode *n0 = ConfNodeLookupChild(parent, value); + if (n0 != NULL) { + if (n0->allow_override) { + ConfNodeRemove(n0); + } + else { + state = CONF_VAL; + goto next; + } + } if (parent->is_seq) { if (parent->val == NULL) { parent->val = strdup(value); @@ -135,7 +116,7 @@ ConfYamlParse2(yaml_parser_t *parser, ConfNode *parent, int inseq) } else if (event.type == YAML_SEQUENCE_START_EVENT) { SCLogDebug("event.type = YAML_SEQUENCE_START_EVENT\n"); - if (ConfYamlParse2(parser, node, 1) != 0) + if (ConfYamlParse(parser, node, 1) != 0) goto fail; state = CONF_KEY; } @@ -151,10 +132,10 @@ ConfYamlParse2(yaml_parser_t *parser, ConfNode *parent, int inseq) seq_node->name = calloc(1, DEFAULT_NAME_LEN); snprintf(seq_node->name, DEFAULT_NAME_LEN, "%d", seq_idx++); TAILQ_INSERT_TAIL(&node->head, seq_node, next); - ConfYamlParse2(parser, seq_node, 0); + ConfYamlParse(parser, seq_node, 0); } else { - ConfYamlParse2(parser, node, inseq); + ConfYamlParse(parser, node, inseq); } state = CONF_KEY; } @@ -166,130 +147,7 @@ ConfYamlParse2(yaml_parser_t *parser, ConfNode *parent, int inseq) done = 1; } - yaml_event_delete(&event); - continue; - - fail: - yaml_event_delete(&event); - return -1; - } - - return 0; -} - -/** - * \brief Process a YAML parser. - * - * Loads a configuration from a setup YAML parser. - * - * \param parser A YAML parser setup for processing. - */ -static int -ConfYamlParse(yaml_parser_t *parser) -{ - yaml_event_t event; - int done; - int level; - int state; - int inseq; - char *key[MAX_LEVELS]; - - memset(key, 0, sizeof(key)); - - state = CONF_KEY; - done = 0; - level = -1; - inseq = 0; - while (!done) { - if (!yaml_parser_parse(parser, &event)) { - fprintf(stderr, "Failed to parse configuration file: %s\n", - parser->problem); - return -1; - } - switch (event.type) { - case YAML_STREAM_START_EVENT: - break; - case YAML_STREAM_END_EVENT: - done = 1; - break; - case YAML_DOCUMENT_START_EVENT: { - /* 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) { - fprintf(stderr, "ERROR: Invalid configuration file.\n\n"); - fprintf(stderr, "The configuration file must begin with the following two lines:\n\n"); - fprintf(stderr, "%%YAML 1.1\n---\n\n"); - goto fail; - } - int major = event.data.document_start.version_directive->major; - int minor = event.data.document_start.version_directive->minor; - if (!(major == YAML_VERSION_MAJOR && minor == YAML_VERSION_MINOR)) { - fprintf(stderr, "ERROR: Invalid YAML version. Must be 1.1\n"); - goto fail; - } - break; - } - case YAML_DOCUMENT_END_EVENT: - /* Ignored. */ - break; - case YAML_SEQUENCE_START_EVENT: { - ConfNode *new; - new = ConfNodeNew(); - new->name = strdup(GetKeyName(key, level)); - ConfYamlParse2(parser, new, 1); - ConfSetNode(new); - state = CONF_KEY; - break; - } - case YAML_SEQUENCE_END_EVENT: - break; - case YAML_MAPPING_START_EVENT: - level++; - if (level == MAX_LEVELS) { - fprintf(stderr, - "Reached maximum configuration nesting level.\n"); - goto fail; - } - - /* Since we are entering a new mapping, state goes back to key. */ - state = CONF_KEY; - - break; - case YAML_MAPPING_END_EVENT: - if (level > -1) { - free(key[level]); - key[level] = NULL; - } - level--; - break; - case YAML_SCALAR_EVENT: { - char *value = (char *)event.data.scalar.value; - if (level < 0) { - /* Don't process values until we've hit a mapping. */ - continue; - } - if (state == CONF_KEY) { - if (key[level] != NULL) - free(key[level]); - key[level] = strdup(value); - - /* Move state to expecting a value. */ - state = CONF_VAL; - } - else if (state == CONF_VAL) { - ConfSet(GetKeyName(key, level), value, 1); - state = CONF_KEY; - } - break; - } - case YAML_ALIAS_EVENT: - break; - case YAML_NO_EVENT: - break; - } + next: yaml_event_delete(&event); continue; @@ -319,6 +177,7 @@ ConfYamlLoadFile(const char *filename) FILE *infile; yaml_parser_t parser; int ret; + ConfNode *root = ConfGetRootNode(); if (yaml_parser_initialize(&parser) != 1) { fprintf(stderr, "Failed to initialize yaml parser.\n"); @@ -333,7 +192,7 @@ ConfYamlLoadFile(const char *filename) return -1; } yaml_parser_set_input_file(&parser, infile); - ret = ConfYamlParse(&parser); + ret = ConfYamlParse(&parser, root, 0); yaml_parser_delete(&parser); fclose(infile); @@ -346,15 +205,16 @@ ConfYamlLoadFile(const char *filename) 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"); - return -1; + exit(EXIT_FAILURE); } yaml_parser_set_input_string(&parser, (const unsigned char *)string, len); - ret = ConfYamlParse(&parser); + ret = ConfYamlParse(&parser, root, 0); yaml_parser_delete(&parser); return ret; @@ -378,8 +238,9 @@ default-log-dir: /tmp\n\ ConfCreateContextBackup(); ConfInit(); - ConfNode *node; ConfYamlLoadString(input, strlen(input)); + + ConfNode *node; node = ConfGetNode("rule-files"); if (node == NULL) return 0; @@ -555,7 +416,8 @@ libhtp:\n\ ConfCreateContextBackup(); ConfInit(); - ConfYamlLoadString(input, strlen(input)); + if (ConfYamlLoadString(input, strlen(input)) != 0) + return 0; ConfNode *outputs; outputs = ConfGetNode("libhtp.server-config"); diff --git a/src/conf.c b/src/conf.c index 7a2b0be5c2..f0c4be2d7a 100644 --- a/src/conf.c +++ b/src/conf.c @@ -11,86 +11,20 @@ * * \author Endace Technology Limited - Jason Ish * - * \todo Consider using HashListTable to allow easy dumping of all data. + * \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-hash.h" #include "util-unittest.h" #include "util-debug.h" -#define CONF_HASH_TBL_SIZE 1024 - -static HashTable *conf_hash = NULL; - -/* temporary variable that would be used to hold the hash_table instance - * present in conf_hash. Used while running tests that require their - * own yaml conf file. The backup can be set and then be reused by using - * the function ConfCreateContextBackup() and ConfRestoreContextBackup() */ -static HashTable *backup_conf_hash = NULL; - -/** - * \brief Function to generate the hash of a configuration value. - * - * This is a callback function provided to HashTable for creating the - * hash key. Its a simple wrapper around the generic hash function - * the passes on the configuration parameter name. - * - * \retval The hash ID of the configuration parameters name. - */ -static uint32_t -ConfHashFunc(HashTable *ht, void *data, uint16_t len) -{ - ConfNode *cn = (ConfNode *)data; - uint32_t hash; - - hash = HashTableGenericHash(ht, cn->name, strlen(cn->name)); - SCLogDebug("%s -> %" PRIu32 "", cn->name, hash); - return hash; -} - -/** - * \brief Function to compare 2 hash nodes. - * - * This is a callback function provided to the HashTable for comparing - * 2 nodes. - * - * \retval 1 if equivalant otherwise 0. - */ -static char -ConfHashComp(void *a, uint16_t a_len, void *b, uint16_t b_len) -{ - ConfNode *ca = (ConfNode *)a; - ConfNode *cb = (ConfNode *)b; - - if (strcmp(ca->name, cb->name) == 0) - return 1; - else - return 0; -} - -/** - * \brief Callback function to free a hash node. - */ -static void ConfHashFree(void *data) -{ - ConfNode *cn = (ConfNode *)data; - if (cn == NULL) - return; - - /** \todo VJ apparently the list that is free is also in the hash - * individually resulting in double free errors if we - * call ConfNodeFree (that clears the list) from this - * hash free function */ - - if (cn->name != NULL) - free(cn->name); - if (cn->val != NULL) - free(cn->val); - free(cn); - //ConfNodeFree(cn); -} +static ConfNode *root = NULL; +static ConfNode *root_backup = NULL; /** * \brief Initialize the configuration system. @@ -98,18 +32,16 @@ static void ConfHashFree(void *data) void ConfInit(void) { - /* Prevent double initialization. */ - if (conf_hash != NULL) { + if (root != NULL) { SCLogDebug("already initialized"); return; } - - conf_hash = HashTableInit(CONF_HASH_TBL_SIZE, ConfHashFunc, ConfHashComp, - ConfHashFree); - if (conf_hash == NULL) { - fprintf(stderr, - "ERROR: Failed to allocate memory for configuration, aborting.\n"); - exit(1); + 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"); } @@ -158,50 +90,43 @@ ConfNodeFree(ConfNode *node) } /** - * \brief Set a configuration node. + * \brief Get a ConfNode by name. + * + * \param key The full name of the configuration node to lookup. * - * \retval 1 on success, 0 on failure. + * \retval A pointer to ConfNode is found or NULL if the configuration + * node does not exist. */ -int -ConfSetNode(ConfNode *node) +ConfNode * +ConfGetNode(char *key) { - ConfNode lookup; - ConfNode *pnode; + ConfNode *node = root; + char *saveptr; + char *token; - lookup.name = node->name; - pnode = HashTableLookup(conf_hash, &lookup, sizeof(lookup)); - if (pnode != NULL) { - if (!pnode->allow_override) { - return 0; - } - HashTableRemove(conf_hash, pnode, sizeof(*pnode)); - } + /* Need to dup the key for tokenization... */ + char *tokstr = strdup(key); - if (HashTableAdd(conf_hash, node, sizeof(*node)) != 0) { - SCLogError(SC_ERR_MEM_ALLOC, "Failed to add new configuration node."); - exit(EXIT_FAILURE); + token = strtok_r(tokstr, ".", &saveptr); + for (;;) { + node = ConfNodeLookupChild(node, token); + if (node == NULL) + break; + token = strtok_r(NULL, ".", &saveptr); + if (token == NULL) + break; } - - return 1; + free(tokstr); + return node; } /** - * \brief Get a ConfNode by key. - * - * \param key The lookup key of the node to find. - * - * \retval The node matching the key or NULL if not found. + * \brief Get the root configuration node. */ ConfNode * -ConfGetNode(char *key) +ConfGetRootNode(void) { - ConfNode lookup; - ConfNode *node; - - lookup.name = key; - - node = HashTableLookup(conf_hash, &lookup, sizeof(lookup)); - return node; + return root; } /** @@ -216,37 +141,65 @@ ConfGetNode(char *key) int ConfSet(char *name, char *val, int allow_override) { - ConfNode lookup_key, *conf_node; + ConfNode *parent = root; + ConfNode *node; + char *token; + char *saveptr; - lookup_key.name = name; - conf_node = HashTableLookup(conf_hash, &lookup_key, sizeof(lookup_key)); - if (conf_node != NULL) { - if (!conf_node->allow_override) { + /* First check if the node already exists. */ + node = ConfGetNode(name); + if (node != NULL) { + if (!node->allow_override) { return 0; } - HashTableRemove(conf_hash, conf_node, sizeof(*conf_node)); - } - - conf_node = ConfNodeNew(); - if (conf_node == NULL) { - return 0; + else { + if (node->val != NULL) + free(node->val); + node->val = strdup(val); + node->allow_override = allow_override; + return 1; + } } - conf_node->name = strdup(name); - conf_node->val = strdup(val); - conf_node->allow_override = allow_override; - - if (HashTableAdd(conf_hash, conf_node, sizeof(*conf_node)) != 0) { - fprintf(stderr, "ERROR: Failed to set configuration parameter %s\n", - name); - exit(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 a configuration value. + * \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. @@ -258,21 +211,13 @@ ConfSet(char *name, char *val, int allow_override) int ConfGet(char *name, char **vptr) { - ConfNode lookup_key; - ConfNode *conf_node; - - if (conf_hash == NULL) - return 0; - - lookup_key.name = name; - - conf_node = HashTableLookup(conf_hash, &lookup_key, sizeof(lookup_key)); - if (conf_node == NULL) { + ConfNode *node = ConfGetNode(name); + if (node == NULL) { SCLogDebug("failed to lookup configuration parameter '%s'", name); return 0; } else { - *vptr = conf_node->val; + *vptr = node->val; return 1; } } @@ -339,6 +284,17 @@ ConfGetBool(char *name, int *val) 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. * @@ -350,13 +306,15 @@ ConfGetBool(char *name, int *val) int ConfRemove(char *name) { - ConfNode cn; + ConfNode *node; - cn.name = name; - if (HashTableRemove(conf_hash, &cn, sizeof(cn)) == 0) - return 1; - else + node = ConfGetNode(name); + if (node == NULL) return 0; + else { + ConfNodeRemove(node); + return 1; + } } /** @@ -365,8 +323,8 @@ ConfRemove(char *name) void ConfCreateContextBackup(void) { - backup_conf_hash = conf_hash; - conf_hash = NULL; + root_backup = root; + root = NULL; return; } @@ -378,7 +336,7 @@ ConfCreateContextBackup(void) void ConfRestoreContextBackup(void) { - conf_hash = backup_conf_hash; + root = root_backup; return; } @@ -389,11 +347,8 @@ ConfRestoreContextBackup(void) void ConfDeInit(void) { - if (conf_hash == NULL) - return; - - HashTableFree(conf_hash); - conf_hash = NULL; + if (root != NULL) + ConfNodeFree(root); SCLogDebug("configuration module de-initialized"); } @@ -428,16 +383,14 @@ ConfNodeDump(ConfNode *node, const char *prefix) level++; TAILQ_FOREACH(child, &node->head, next) { name[level] = strdup(child->name); - // if (child->val != NULL) { - if (prefix == NULL) { - printf("%s = %s\n", ConfPrintNameArray(name, level), - child->val); - } - else { - printf("%s.%s = %s\n", prefix, - ConfPrintNameArray(name, level), child->val); - } - //} + 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]); } @@ -450,22 +403,7 @@ ConfNodeDump(ConfNode *node, const char *prefix) void ConfDump(void) { - HashTableBucket *b; - ConfNode *cn; - uint32_t u; - - for (u = 0; u < conf_hash->array_size; u++) { - if (conf_hash->array[u] != NULL) { - b = (HashTableBucket *)conf_hash->array[u]; - while (b != NULL) { - cn = (ConfNode *)b->data; - if (cn->val != NULL) - printf("%s = %s\n", cn->name, cn->val); - ConfNodeDump(cn, cn->name); - b = b->next; - } - } - } + ConfNodeDump(root, NULL); } /** @@ -553,38 +491,6 @@ ConfTestSetAndGet(void) return 1; } -static int -ConfTestSetGetNode(void) -{ - ConfNode *set; - ConfNode *get; - char key[] = "some-key"; - char val[] = "some-val"; - - set = ConfNodeNew(); - if (set == NULL) - return 0; - set->name = strdup(key); - set->val = strdup(val); - if (ConfSetNode(set) != 1) - return 0; - - get = ConfGetNode(key); - if (get == NULL) - return 0; - if (strcmp(get->name, key) != 0) - return 0; - if (strcmp(get->val, val) != 0) - return 0; - - ConfRemove(key); - get = ConfGetNode(key); - if (get != NULL) - return 0; - - return 1; -} - /** * Test that overriding a value is allowed provided allow_override is * true and that the config parameter gets the new value. @@ -647,13 +553,14 @@ ConfTestOverrideValue2(void) static int ConfTestGetInt(void) { - char name[] = "some-int"; + 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; @@ -816,11 +723,37 @@ ConfNodeLookupChildValueTest(void) 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("ConfTestSetGetNode", ConfTestSetGetNode, 1); UtRegisterTest("ConfTestSetAndGet", ConfTestSetAndGet, 1); UtRegisterTest("ConfTestOverrideValue1", ConfTestOverrideValue1, 1); UtRegisterTest("ConfTestOverrideValue2", ConfTestOverrideValue2, 1); @@ -828,6 +761,7 @@ ConfRegisterTests(void) UtRegisterTest("ConfTestGetBool", ConfTestGetBool, 1); UtRegisterTest("ConfNodeLookupChildTest", ConfNodeLookupChildTest, 1); UtRegisterTest("ConfNodeLookupChildValueTest", ConfNodeLookupChildValueTest, 1); + UtRegisterTest("ConfNodeRemoveTest", ConfNodeRemoveTest, 1); } #endif /* UNITTESTS */ diff --git a/src/conf.h b/src/conf.h index be2ca3fe64..de68372de9 100644 --- a/src/conf.h +++ b/src/conf.h @@ -19,6 +19,7 @@ typedef struct ConfNode_ { int is_seq; int allow_override; + struct ConfNode_ *parent; TAILQ_HEAD(, ConfNode_) head; TAILQ_ENTRY(ConfNode_) next; } ConfNode; @@ -30,6 +31,8 @@ typedef struct ConfNode_ { #define DEFAULT_LOG_DIR "/var/log/suricata" void ConfInit(void); +void ConfDeInit(void); +ConfNode *ConfGetRootNode(void); int ConfGet(char *name, char **vptr); int ConfGetInt(char *name, intmax_t *val); int ConfGetBool(char *name, int *val); @@ -38,13 +41,12 @@ void ConfDump(void); void ConfNodeDump(ConfNode *node, const char *prefix); ConfNode *ConfNodeNew(void); void ConfNodeFree(ConfNode *); -int ConfSetNode(ConfNode *node); ConfNode *ConfGetNode(char *key); void ConfCreateContextBackup(void); void ConfRestoreContextBackup(void); -void ConfDeInit(void); ConfNode *ConfNodeLookupChild(ConfNode *node, const char *key); const char *ConfNodeLookupChildValue(ConfNode *node, const char *key); +void ConfNodeRemove(ConfNode *); void ConfRegisterTests(); #endif /* ! __CONF_H__ */