mirror of https://github.com/OISF/suricata
				
				
				
			
			You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			984 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			C
		
	
			
		
		
	
	
			984 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			C
		
	
/* Copyright (C) 2007-2010 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);
 | 
						|
 | 
						|
/* 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) {
 | 
						|
            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 = NULL;
 | 
						|
    int ret = -1;
 | 
						|
 | 
						|
    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));
 | 
						|
        goto done;
 | 
						|
    }
 | 
						|
 | 
						|
    yaml_parser_set_input_file(&parser, file);
 | 
						|
 | 
						|
    if (ConfYamlParse(&parser, parent, 0, 0) != 0) {
 | 
						|
        SCLogError(SC_ERR_CONF_YAML_ERROR,
 | 
						|
            "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)
 | 
						|
{
 | 
						|
    ConfNode *node = parent;
 | 
						|
    yaml_event_t event;
 | 
						|
    memset(&event, 0, sizeof(event));
 | 
						|
    int done = 0;
 | 
						|
    int state = 0;
 | 
						|
    int seq_idx = 0;
 | 
						|
    int retval = 0;
 | 
						|
 | 
						|
    if (rlevel++ > RECURSION_LIMIT) {
 | 
						|
        SCLogError(SC_ERR_CONF_YAML_ERROR, "Recursion limit reached while parsing "
 | 
						|
                "configuration file, aborting.");
 | 
						|
        return -1;
 | 
						|
    }
 | 
						|
 | 
						|
    while (!done) {
 | 
						|
        if (!yaml_parser_parse(parser, &event)) {
 | 
						|
            SCLogError(SC_ERR_CONF_YAML_ERROR,
 | 
						|
                "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(SC_ERR_CONF_YAML_ERROR, "ERROR: Invalid configuration file.");
 | 
						|
                SCLogError(SC_ERR_CONF_YAML_ERROR,
 | 
						|
                           "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(SC_ERR_CONF_YAML_ERROR, "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 (inseq) {
 | 
						|
                char sequence_node_name[DEFAULT_NAME_LEN];
 | 
						|
                snprintf(sequence_node_name, DEFAULT_NAME_LEN, "%d", seq_idx++);
 | 
						|
                ConfNode *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;
 | 
						|
                    }
 | 
						|
                    seq_node->val = SCStrdup(value);
 | 
						|
                    if (unlikely(seq_node->val == NULL)) {
 | 
						|
                        SCFree(seq_node->name);
 | 
						|
                        goto fail;
 | 
						|
                    }
 | 
						|
                }
 | 
						|
                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;
 | 
						|
                        goto next;
 | 
						|
                    }
 | 
						|
 | 
						|
                    if (parent->is_seq) {
 | 
						|
                        if (parent->val == NULL) {
 | 
						|
                            parent->val = SCStrdup(value);
 | 
						|
                            if (parent->val && strchr(parent->val, '_'))
 | 
						|
                                Mangle(parent->val);
 | 
						|
                        }
 | 
						|
                    }
 | 
						|
                    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);
 | 
						|
                        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(SC_WARN_DEPRECATED,
 | 
						|
                                            "%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(SC_WARN_DEPRECATED, "not showing more "
 | 
						|
                                                "parameter name warnings.");
 | 
						|
                                }
 | 
						|
                            }
 | 
						|
                        }
 | 
						|
                        TAILQ_INSERT_TAIL(&parent->head, node, next);
 | 
						|
                    }
 | 
						|
                    state = CONF_VAL;
 | 
						|
                }
 | 
						|
                else {
 | 
						|
                    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->final) {
 | 
						|
                        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 (ConfYamlParse(parser, node, 1, rlevel) != 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 (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)
 | 
						|
                    goto fail;
 | 
						|
            }
 | 
						|
            else {
 | 
						|
                if (ConfYamlParse(parser, node, inseq, rlevel) != 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(SC_ERR_FATAL, "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(SC_ERR_FATAL, "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(SC_ERR_FATAL, "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);
 | 
						|
    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);
 | 
						|
    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(SC_ERR_FATAL, "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(SC_ERR_FATAL, "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(SC_ERR_FATAL, "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);
 | 
						|
    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");
 | 
						|
    if (node == NULL)
 | 
						|
        return 0;
 | 
						|
    if (!ConfNodeIsSequence(node))
 | 
						|
        return 0;
 | 
						|
    if (TAILQ_EMPTY(&node->head))
 | 
						|
        return 0;
 | 
						|
    int i = 0;
 | 
						|
    ConfNode *filename;
 | 
						|
    TAILQ_FOREACH(filename, &node->head, next) {
 | 
						|
        if (i == 0) {
 | 
						|
            if (strcmp(filename->val, "netbios.rules") != 0)
 | 
						|
                return 0;
 | 
						|
            if (ConfNodeIsSequence(filename))
 | 
						|
                return 0;
 | 
						|
            if (filename->is_seq != 0)
 | 
						|
                return 0;
 | 
						|
        }
 | 
						|
        else if (i == 1) {
 | 
						|
            if (strcmp(filename->val, "x11.rules") != 0)
 | 
						|
                return 0;
 | 
						|
            if (ConfNodeIsSequence(filename))
 | 
						|
                return 0;
 | 
						|
        }
 | 
						|
        else {
 | 
						|
            return 0;
 | 
						|
        }
 | 
						|
        i++;
 | 
						|
    }
 | 
						|
 | 
						|
    ConfDeInit();
 | 
						|
    ConfRestoreContextBackup();
 | 
						|
 | 
						|
    return 1;
 | 
						|
}
 | 
						|
 | 
						|
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");
 | 
						|
    if (outputs == NULL)
 | 
						|
        return 0;
 | 
						|
 | 
						|
    ConfNode *output;
 | 
						|
    ConfNode *output_param;
 | 
						|
 | 
						|
    output = TAILQ_FIRST(&outputs->head);
 | 
						|
    if (output == NULL)
 | 
						|
        return 0;
 | 
						|
    if (strcmp(output->name, "0") != 0)
 | 
						|
        return 0;
 | 
						|
    output_param = TAILQ_FIRST(&output->head);
 | 
						|
    if (output_param == NULL)
 | 
						|
        return 0;
 | 
						|
    if (strcmp(output_param->name, "interface") != 0)
 | 
						|
        return 0;
 | 
						|
    if (strcmp(output_param->val, "console") != 0)
 | 
						|
        return 0;
 | 
						|
    output_param = TAILQ_NEXT(output_param, next);
 | 
						|
    if (strcmp(output_param->name, "log-level") != 0)
 | 
						|
        return 0;
 | 
						|
    if (strcmp(output_param->val, "error") != 0)
 | 
						|
        return 0;
 | 
						|
 | 
						|
    output = TAILQ_NEXT(output, next);
 | 
						|
    if (output == NULL)
 | 
						|
        return 0;
 | 
						|
    if (strcmp(output->name, "1") != 0)
 | 
						|
        return 0;
 | 
						|
    output_param = TAILQ_FIRST(&output->head);
 | 
						|
    if (output_param == NULL)
 | 
						|
        return 0;
 | 
						|
    if (strcmp(output_param->name, "interface") != 0)
 | 
						|
        return 0;
 | 
						|
    if (strcmp(output_param->val, "syslog") != 0)
 | 
						|
        return 0;
 | 
						|
    output_param = TAILQ_NEXT(output_param, next);
 | 
						|
    if (strcmp(output_param->name, "facility") != 0)
 | 
						|
        return 0;
 | 
						|
    if (strcmp(output_param->val, "local4") != 0)
 | 
						|
        return 0;
 | 
						|
    output_param = TAILQ_NEXT(output_param, next);
 | 
						|
    if (strcmp(output_param->name, "log-level") != 0)
 | 
						|
        return 0;
 | 
						|
    if (strcmp(output_param->val, "info") != 0)
 | 
						|
        return 0;
 | 
						|
 | 
						|
    ConfDeInit();
 | 
						|
    ConfRestoreContextBackup();
 | 
						|
 | 
						|
    return 1;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Try to load something that is not a valid YAML file.
 | 
						|
 */
 | 
						|
static int
 | 
						|
ConfYamlNonYamlFileTest(void)
 | 
						|
{
 | 
						|
    ConfCreateContextBackup();
 | 
						|
    ConfInit();
 | 
						|
 | 
						|
    if (ConfYamlLoadFile("/etc/passwd") != -1)
 | 
						|
        return 0;
 | 
						|
 | 
						|
    ConfDeInit();
 | 
						|
    ConfRestoreContextBackup();
 | 
						|
 | 
						|
    return 1;
 | 
						|
}
 | 
						|
 | 
						|
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();
 | 
						|
 | 
						|
    if (ConfYamlLoadString(input, strlen(input)) != -1)
 | 
						|
        return 0;
 | 
						|
 | 
						|
    ConfDeInit();
 | 
						|
    ConfRestoreContextBackup();
 | 
						|
 | 
						|
    return 1;
 | 
						|
}
 | 
						|
 | 
						|
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();
 | 
						|
 | 
						|
    if (ConfYamlLoadString(input, strlen(input)) != 0)
 | 
						|
        return 0;
 | 
						|
 | 
						|
    ConfNode *outputs;
 | 
						|
    outputs = ConfGetNode("libhtp.server-config");
 | 
						|
    if (outputs == NULL)
 | 
						|
        return 0;
 | 
						|
 | 
						|
    ConfNode *node;
 | 
						|
 | 
						|
    node = TAILQ_FIRST(&outputs->head);
 | 
						|
    if (node == NULL)
 | 
						|
        return 0;
 | 
						|
    if (strcmp(node->name, "0") != 0)
 | 
						|
        return 0;
 | 
						|
    node = TAILQ_FIRST(&node->head);
 | 
						|
    if (node == NULL)
 | 
						|
        return 0;
 | 
						|
    if (strcmp(node->name, "apache-php") != 0)
 | 
						|
        return 0;
 | 
						|
 | 
						|
    node = ConfNodeLookupChild(node, "address");
 | 
						|
    if (node == NULL)
 | 
						|
        return 0;
 | 
						|
    node = TAILQ_FIRST(&node->head);
 | 
						|
    if (node == NULL)
 | 
						|
        return 0;
 | 
						|
    if (strcmp(node->name, "0") != 0)
 | 
						|
        return 0;
 | 
						|
    if (strcmp(node->val, "192.168.1.0/24") != 0)
 | 
						|
        return 0;
 | 
						|
 | 
						|
    ConfDeInit();
 | 
						|
    ConfRestoreContextBackup();
 | 
						|
 | 
						|
    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;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * 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"
 | 
						|
        ;
 | 
						|
    const char *value;
 | 
						|
 | 
						|
    ConfCreateContextBackup();
 | 
						|
    ConfInit();
 | 
						|
 | 
						|
    if (ConfYamlLoadString(config, strlen(config)) != 0)
 | 
						|
        return 0;
 | 
						|
    if (!ConfGet("some-log-dir", &value))
 | 
						|
        return 0;
 | 
						|
    if (strcmp(value, "/tmp") != 0)
 | 
						|
        return 0;
 | 
						|
 | 
						|
    /* Test that parent.child0 does not exist, but child1 does. */
 | 
						|
    if (ConfGetNode("parent.child0") != NULL)
 | 
						|
        return 0;
 | 
						|
    if (!ConfGet("parent.child1.key", &value))
 | 
						|
        return 0;
 | 
						|
    if (strcmp(value, "value") != 0)
 | 
						|
        return 0;
 | 
						|
 | 
						|
    ConfDeInit();
 | 
						|
    ConfRestoreContextBackup();
 | 
						|
 | 
						|
    return 1;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * 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. */
 | 
						|
    if (!ConfSetFinal("default-log-dir", "/tmp"))
 | 
						|
        return 0;
 | 
						|
    if (ConfYamlLoadString(config, strlen(config)) != 0)
 | 
						|
        return 0;
 | 
						|
 | 
						|
    const char *default_log_dir;
 | 
						|
 | 
						|
    if (!ConfGet("default-log-dir", &default_log_dir))
 | 
						|
        return 0;
 | 
						|
    if (strcmp(default_log_dir, "/tmp") != 0) {
 | 
						|
        fprintf(stderr, "final value was reassigned\n");
 | 
						|
        return 0;
 | 
						|
    }
 | 
						|
 | 
						|
    ConfDeInit();
 | 
						|
    ConfRestoreContextBackup();
 | 
						|
 | 
						|
    return 1;
 | 
						|
}
 | 
						|
 | 
						|
#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);
 | 
						|
#endif /* UNITTESTS */
 | 
						|
}
 |