Coverage Report

Created: 2026-11-12 06:59

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/suricata8/src/conf-yaml-loader.c
Line
Count
Source
1
/* Copyright (C) 2007-2023 Open Information Security Foundation
2
 *
3
 * You can copy, redistribute or modify this Program under the terms of
4
 * the GNU General Public License version 2 as published by the Free
5
 * Software Foundation.
6
 *
7
 * This program is distributed in the hope that it will be useful,
8
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10
 * GNU General Public License for more details.
11
 *
12
 * You should have received a copy of the GNU General Public License
13
 * version 2 along with this program; if not, write to the Free Software
14
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
15
 * 02110-1301, USA.
16
 */
17
18
/**
19
 * \file
20
 *
21
 * \author Endace Technology Limited - Jason Ish <jason.ish@endace.com>
22
 *
23
 * YAML configuration loader.
24
 */
25
26
#include "suricata-common.h"
27
#include "conf.h"
28
#include "conf-yaml-loader.h"
29
#include <yaml.h>
30
#include "util-path.h"
31
#include "util-debug.h"
32
#include "util-unittest.h"
33
34
935k
#define YAML_VERSION_MAJOR 1
35
467k
#define YAML_VERSION_MINOR 1
36
37
/* The maximum level of recursion allowed while parsing the YAML
38
 * file. */
39
2.30M
#define RECURSION_LIMIT 128
40
41
/* Sometimes we'll have to create a node name on the fly (integer
42
 * conversion, etc), so this is a default length to allocate that will
43
 * work most of the time. */
44
607k
#define DEFAULT_NAME_LEN 16
45
46
7.65k
#define MANGLE_ERRORS_MAX 10
47
static int mangle_errors = 0;
48
49
static char *conf_dirname = NULL;
50
51
static int ConfYamlParse(
52
        yaml_parser_t *parser, SCConfNode *parent, int inseq, int rlevel, int state);
53
54
/* Configuration processing states. */
55
enum conf_state {
56
    CONF_KEY = 0,
57
    CONF_VAL,
58
    CONF_INCLUDE,
59
};
60
61
/**
62
 * \brief Mangle unsupported characters.
63
 *
64
 * \param string A pointer to an null terminated string.
65
 *
66
 * \retval none
67
 */
68
static void
69
Mangle(char *string)
70
8.84k
{
71
8.84k
    char *c;
72
73
49.1k
    while ((c = strchr(string, '_')))
74
40.3k
        *c = '-';
75
8.84k
}
76
77
/**
78
 * \brief Set the directory name of the configuration file.
79
 *
80
 * \param filename The configuration filename.
81
 */
82
static void
83
ConfYamlSetConfDirname(const char *filename)
84
0
{
85
0
    const char *ep;
86
87
0
    ep = strrchr(filename, '\\');
88
0
    if (ep == NULL)
89
0
        ep = strrchr(filename, '/');
90
91
0
    if (ep == NULL) {
92
0
        conf_dirname = SCStrdup(".");
93
0
        if (conf_dirname == NULL) {
94
0
            FatalError("ERROR: Failed to allocate memory while loading configuration.");
95
0
        }
96
0
    }
97
0
    else {
98
0
        conf_dirname = SCStrdup(filename);
99
0
        if (conf_dirname == NULL) {
100
0
            FatalError("ERROR: Failed to allocate memory while loading configuration.");
101
0
        }
102
0
        conf_dirname[ep - filename] = '\0';
103
0
    }
104
0
}
105
106
/**
107
 * \brief Include a file in the configuration.
108
 *
109
 * \param parent The configuration node the included configuration will be
110
 *          placed at.
111
 * \param filename The filename to include.
112
 *
113
 * \retval 0 on success, -1 on failure.
114
 */
115
int SCConfYamlHandleInclude(SCConfNode *parent, const char *filename)
116
114
{
117
114
    yaml_parser_t parser;
118
114
    char include_filename[PATH_MAX];
119
114
    FILE *file = NULL;
120
114
    int ret = -1;
121
122
114
    if (yaml_parser_initialize(&parser) != 1) {
123
0
        SCLogError("Failed to initialize YAML parser");
124
0
        return -1;
125
0
    }
126
127
114
    if (PathIsAbsolute(filename)) {
128
8
        strlcpy(include_filename, filename, sizeof(include_filename));
129
8
    }
130
106
    else {
131
106
        snprintf(include_filename, sizeof(include_filename), "%s/%s",
132
106
            conf_dirname, filename);
133
106
    }
134
135
114
    file = fopen(include_filename, "r");
136
114
    if (file == NULL) {
137
108
        SCLogError("Failed to open configuration include file %s: %s", include_filename,
138
108
                strerror(errno));
139
108
        goto done;
140
108
    }
141
142
6
    yaml_parser_set_input_file(&parser, file);
143
144
6
    if (ConfYamlParse(&parser, parent, 0, 0, 0) != 0) {
145
6
        SCLogError("Failed to include configuration file %s", filename);
146
6
        goto done;
147
6
    }
148
149
0
    ret = 0;
150
151
114
done:
152
114
    yaml_parser_delete(&parser);
153
114
    if (file != NULL) {
154
6
        fclose(file);
155
6
    }
156
157
114
    return ret;
158
0
}
159
160
/**
161
 * \brief Parse a YAML layer.
162
 *
163
 * \param parser A pointer to an active yaml_parser_t.
164
 * \param parent The parent configuration node.
165
 *
166
 * \retval 0 on success, -1 on failure.
167
 */
168
static int ConfYamlParse(
169
        yaml_parser_t *parser, SCConfNode *parent, int inseq, int rlevel, int state)
170
2.30M
{
171
2.30M
    SCConfNode *node = parent;
172
2.30M
    yaml_event_t event;
173
2.30M
    memset(&event, 0, sizeof(event));
174
2.30M
    int done = 0;
175
2.30M
    int seq_idx = 0;
176
2.30M
    int retval = 0;
177
2.30M
    int was_empty = -1;
178
2.30M
    int include_count = 0;
179
180
2.30M
    if (rlevel++ > RECURSION_LIMIT) {
181
23
        SCLogError("Recursion limit reached while parsing "
182
23
                   "configuration file, aborting.");
183
23
        return -1;
184
23
    }
185
186
12.3M
    while (!done) {
187
10.1M
        if (!yaml_parser_parse(parser, &event)) {
188
8.48k
            SCLogError("Failed to parse configuration file at line %" PRIuMAX ": %s",
189
8.48k
                    (uintmax_t)parser->problem_mark.line, parser->problem);
190
8.48k
            retval = -1;
191
8.48k
            break;
192
8.48k
        }
193
194
10.1M
        if (event.type == YAML_DOCUMENT_START_EVENT) {
195
473k
            SCLogDebug("event.type=YAML_DOCUMENT_START_EVENT; state=%d", state);
196
            /* Verify YAML version - its more likely to be a valid
197
             * Suricata configuration file if the version is
198
             * correct. */
199
473k
            yaml_version_directive_t *ver =
200
473k
                event.data.document_start.version_directive;
201
473k
            if (ver == NULL) {
202
5.58k
                SCLogError("ERROR: Invalid configuration file.");
203
5.58k
                SCLogError("The configuration file must begin with the following two lines: %%YAML "
204
5.58k
                           "1.1 and ---");
205
5.58k
                goto fail;
206
5.58k
            }
207
467k
            int major = ver->major;
208
467k
            int minor = ver->minor;
209
467k
            if (!(major == YAML_VERSION_MAJOR && minor == YAML_VERSION_MINOR)) {
210
5
                SCLogError("ERROR: Invalid YAML version.  Must be 1.1");
211
5
                goto fail;
212
5
            }
213
467k
        }
214
9.64M
        else if (event.type == YAML_SCALAR_EVENT) {
215
4.54M
            char *value = (char *)event.data.scalar.value;
216
4.54M
            char *tag = (char *)event.data.scalar.tag;
217
4.54M
            SCLogDebug("event.type=YAML_SCALAR_EVENT; state=%d; value=%s; "
218
4.54M
                "tag=%s; inseq=%d", state, value, tag, inseq);
219
220
            /* Skip over empty scalar values while in KEY state. This
221
             * tends to only happen on an empty file, where a scalar
222
             * event probably shouldn't fire anyways. */
223
4.54M
            if (state == CONF_KEY && strlen(value) == 0) {
224
1.83M
                goto next;
225
1.83M
            }
226
227
            /* If the value is unquoted, certain strings in YAML represent NULL. */
228
2.71M
            if ((inseq || state == CONF_VAL) &&
229
1.27M
                    event.data.scalar.style == YAML_PLAIN_SCALAR_STYLE) {
230
1.26M
                if (strlen(value) == 0 || strcmp(value, "~") == 0 || strcmp(value, "null") == 0 ||
231
1.06M
                        strcmp(value, "Null") == 0 || strcmp(value, "NULL") == 0) {
232
1.06M
                    value = NULL;
233
1.06M
                }
234
1.26M
            }
235
236
2.71M
            if (inseq) {
237
266k
                if (state == CONF_INCLUDE) {
238
77.5k
                    if (value != NULL) {
239
17
                        SCLogInfo("Including configuration file %s.", value);
240
17
                        if (SCConfYamlHandleInclude(parent, value) != 0) {
241
17
                            goto fail;
242
17
                        }
243
17
                    }
244
77.5k
                    goto next;
245
77.5k
                }
246
188k
                char sequence_node_name[DEFAULT_NAME_LEN];
247
188k
                snprintf(sequence_node_name, DEFAULT_NAME_LEN, "%d", seq_idx++);
248
188k
                SCConfNode *seq_node = NULL;
249
188k
                if (was_empty < 0) {
250
                    // initialize was_empty
251
9.40k
                    if (TAILQ_EMPTY(&parent->head)) {
252
3.15k
                        was_empty = 1;
253
6.25k
                    } else {
254
6.25k
                        was_empty = 0;
255
6.25k
                    }
256
9.40k
                }
257
                // we only check if the node's list was not empty at first
258
188k
                if (was_empty == 0) {
259
24.0k
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
260
                    // do not fuzz quadratic-complexity overlong sequence of scalars
261
24.0k
                    if (seq_idx > 256) {
262
3
                        goto fail;
263
3
                    }
264
24.0k
#endif
265
24.0k
                    seq_node = SCConfNodeLookupChild(parent, sequence_node_name);
266
24.0k
                }
267
188k
                if (seq_node != NULL) {
268
                    /* The sequence node has already been set, probably
269
                     * from the command line.  Remove it so it gets
270
                     * re-added in the expected order for iteration.
271
                     */
272
21.1k
                    TAILQ_REMOVE(&parent->head, seq_node, next);
273
21.1k
                }
274
167k
                else {
275
167k
                    seq_node = SCConfNodeNew();
276
167k
                    if (unlikely(seq_node == NULL)) {
277
0
                        goto fail;
278
0
                    }
279
167k
                    seq_node->name = SCStrdup(sequence_node_name);
280
167k
                    if (unlikely(seq_node->name == NULL)) {
281
0
                        SCFree(seq_node);
282
0
                        goto fail;
283
0
                    }
284
167k
                    if (value != NULL) {
285
123k
                        seq_node->val = SCStrdup(value);
286
123k
                        if (unlikely(seq_node->val == NULL)) {
287
0
                            SCFree(seq_node->name);
288
0
                            goto fail;
289
0
                        }
290
123k
                    } else {
291
43.7k
                        seq_node->val = NULL;
292
43.7k
                    }
293
167k
                }
294
188k
                TAILQ_INSERT_TAIL(&parent->head, seq_node, next);
295
188k
            }
296
2.44M
            else {
297
2.44M
                if (state == CONF_INCLUDE) {
298
95
                    SCLogInfo("Including configuration file %s.", value);
299
95
                    if (SCConfYamlHandleInclude(parent, value) != 0) {
300
95
                        goto fail;
301
95
                    }
302
0
                    state = CONF_KEY;
303
0
                }
304
2.44M
                else if (state == CONF_KEY) {
305
306
1.43M
                    if (strcmp(value, "include") == 0) {
307
76.6k
                        state = CONF_INCLUDE;
308
76.6k
                        if (++include_count > 1) {
309
76.0k
                            SCLogWarning("Multipline \"include\" fields at the same level are "
310
76.0k
                                         "deprecated and will not work in Suricata 8, please move "
311
76.0k
                                         "to an array of include files: line: %zu",
312
76.0k
                                    parser->mark.line);
313
76.0k
                        }
314
76.6k
                        goto next;
315
76.6k
                    }
316
317
1.36M
                    if (parent->is_seq) {
318
1.33M
                        if (parent->val == NULL) {
319
125k
                            parent->val = SCStrdup(value);
320
125k
                            if (parent->val && strchr(parent->val, '_'))
321
1.22k
                                Mangle(parent->val);
322
125k
                        }
323
1.33M
                    }
324
325
1.36M
                    if (strchr(value, '.') != NULL) {
326
679k
                        node = SCConfNodeGetNodeOrCreate(parent, value, 0);
327
679k
                        if (node == NULL) {
328
                            /* Error message already logged. */
329
75
                            goto fail;
330
75
                        }
331
683k
                    } else {
332
683k
                        SCConfNode *existing = SCConfNodeLookupChild(parent, value);
333
683k
                        if (existing != NULL) {
334
521k
                            if (!existing->final) {
335
521k
                                SCLogInfo("Configuration node '%s' redefined.", existing->name);
336
521k
                                SCConfNodePrune(existing);
337
521k
                            }
338
521k
                            node = existing;
339
521k
                        } else {
340
161k
                            node = SCConfNodeNew();
341
161k
                            if (unlikely(node == NULL)) {
342
0
                                goto fail;
343
0
                            }
344
161k
                            node->name = SCStrdup(value);
345
161k
                            node->parent = parent;
346
161k
                            if (node->name && strchr(node->name, '_')) {
347
8.39k
                                if (!(parent->name &&
348
6.15k
                                            ((strcmp(parent->name, "address-groups") == 0) ||
349
7.62k
                                                    (strcmp(parent->name, "port-groups") == 0)))) {
350
7.62k
                                    Mangle(node->name);
351
7.62k
                                    if (mangle_errors < MANGLE_ERRORS_MAX) {
352
38
                                        SCLogWarning(
353
38
                                                "%s is deprecated. Please use %s on line %" PRIuMAX
354
38
                                                ".",
355
38
                                                value, node->name,
356
38
                                                (uintmax_t)parser->mark.line + 1);
357
38
                                        mangle_errors++;
358
38
                                        if (mangle_errors >= MANGLE_ERRORS_MAX)
359
2
                                            SCLogWarning("not showing more "
360
38
                                                         "parameter name warnings.");
361
38
                                    }
362
7.62k
                                }
363
8.39k
                            }
364
161k
                            TAILQ_INSERT_TAIL(&parent->head, node, next);
365
161k
                        }
366
683k
                    }
367
1.36M
                    state = CONF_VAL;
368
1.36M
                }
369
1.00M
                else {
370
1.00M
                    if (value != NULL && (tag != NULL) && (strcmp(tag, "!include") == 0)) {
371
2
                        SCLogInfo("Including configuration file %s at "
372
2
                            "parent node %s.", value, node->name);
373
2
                        if (SCConfYamlHandleInclude(node, value) != 0)
374
2
                            goto fail;
375
1.00M
                    } else if (!node->final && value != NULL) {
376
61.4k
                        if (node->val != NULL)
377
11.5k
                            SCFree(node->val);
378
61.4k
                        node->val = SCStrdup(value);
379
61.4k
                    }
380
1.00M
                    state = CONF_KEY;
381
1.00M
                }
382
2.44M
            }
383
2.71M
        }
384
5.09M
        else if (event.type == YAML_SEQUENCE_START_EVENT) {
385
1.13M
            SCLogDebug("event.type=YAML_SEQUENCE_START_EVENT; state=%d", state);
386
            /* If we're processing a list of includes, use the current parent. */
387
1.13M
            if (ConfYamlParse(parser, state == CONF_INCLUDE ? parent : node, 1, rlevel,
388
1.13M
                        state == CONF_INCLUDE ? CONF_INCLUDE : 0) != 0)
389
10.7k
                goto fail;
390
1.12M
            if (state != CONF_INCLUDE)
391
1.04M
                node->is_seq = 1;
392
1.12M
            state = CONF_KEY;
393
1.12M
        }
394
3.96M
        else if (event.type == YAML_SEQUENCE_END_EVENT) {
395
1.12M
            SCLogDebug("event.type=YAML_SEQUENCE_END_EVENT; state=%d", state);
396
1.12M
            done = 1;
397
1.12M
        }
398
2.83M
        else if (event.type == YAML_MAPPING_START_EVENT) {
399
1.14M
            SCLogDebug("event.type=YAML_MAPPING_START_EVENT; state=%d", state);
400
1.14M
            if (state == CONF_INCLUDE) {
401
7
                SCLogError("Include fields cannot be a mapping: line %zu", parser->mark.line);
402
7
                goto fail;
403
7
            }
404
1.14M
            if (inseq) {
405
418k
                char sequence_node_name[DEFAULT_NAME_LEN];
406
418k
                snprintf(sequence_node_name, DEFAULT_NAME_LEN, "%d", seq_idx++);
407
418k
                SCConfNode *seq_node = NULL;
408
418k
                if (was_empty < 0) {
409
                    // initialize was_empty
410
298k
                    if (TAILQ_EMPTY(&node->head)) {
411
13.4k
                        was_empty = 1;
412
284k
                    } else {
413
284k
                        was_empty = 0;
414
284k
                    }
415
298k
                }
416
                // we only check if the node's list was not empty at first
417
418k
                if (was_empty == 0) {
418
291k
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
419
                    // do not fuzz quadratic-complexity overlong sequence of scalars
420
291k
                    if (seq_idx > 256) {
421
3
                        goto fail;
422
3
                    }
423
291k
#endif
424
291k
                    seq_node = SCConfNodeLookupChild(node, sequence_node_name);
425
291k
                }
426
418k
                if (seq_node != NULL) {
427
                    /* The sequence node has already been set, probably
428
                     * from the command line.  Remove it so it gets
429
                     * re-added in the expected order for iteration.
430
                     */
431
290k
                    TAILQ_REMOVE(&node->head, seq_node, next);
432
290k
                }
433
128k
                else {
434
128k
                    seq_node = SCConfNodeNew();
435
128k
                    if (unlikely(seq_node == NULL)) {
436
0
                        goto fail;
437
0
                    }
438
128k
                    seq_node->name = SCStrdup(sequence_node_name);
439
128k
                    if (unlikely(seq_node->name == NULL)) {
440
0
                        SCFree(seq_node);
441
0
                        goto fail;
442
0
                    }
443
128k
                }
444
418k
                seq_node->is_seq = 1;
445
418k
                TAILQ_INSERT_TAIL(&node->head, seq_node, next);
446
418k
                if (ConfYamlParse(parser, seq_node, 0, rlevel, 0) != 0)
447
2.64k
                    goto fail;
448
418k
            }
449
730k
            else {
450
730k
                if (ConfYamlParse(parser, node, inseq, rlevel, 0) != 0)
451
12.6k
                    goto fail;
452
730k
            }
453
1.13M
            state = CONF_KEY;
454
1.13M
        }
455
1.68M
        else if (event.type == YAML_MAPPING_END_EVENT) {
456
1.13M
            SCLogDebug("event.type=YAML_MAPPING_END_EVENT; state=%d", state);
457
1.13M
            done = 1;
458
1.13M
        }
459
554k
        else if (event.type == YAML_STREAM_END_EVENT) {
460
2.51k
            SCLogDebug("event.type=YAML_STREAM_END_EVENT; state=%d", state);
461
2.51k
            done = 1;
462
2.51k
        }
463
464
10.0M
    next:
465
10.0M
        yaml_event_delete(&event);
466
10.0M
        continue;
467
468
31.9k
    fail:
469
31.9k
        yaml_event_delete(&event);
470
31.9k
        retval = -1;
471
31.9k
        break;
472
10.1M
    }
473
474
2.30M
    rlevel--;
475
2.30M
    return retval;
476
2.30M
}
477
478
/**
479
 * \brief Load configuration from a YAML file.
480
 *
481
 * This function will load a configuration file.  On failure -1 will
482
 * be returned and it is suggested that the program then exit.  Any
483
 * errors while loading the configuration file will have already been
484
 * logged.
485
 *
486
 * \param filename Filename of configuration file to load.
487
 *
488
 * \retval 0 on success, -1 on failure.
489
 */
490
int SCConfYamlLoadFile(const char *filename)
491
0
{
492
0
    FILE *infile;
493
0
    yaml_parser_t parser;
494
0
    int ret;
495
0
    SCConfNode *root = SCConfGetRootNode();
496
497
0
    if (yaml_parser_initialize(&parser) != 1) {
498
0
        SCLogError("failed to initialize yaml parser.");
499
0
        return -1;
500
0
    }
501
502
0
    struct stat stat_buf;
503
0
    if (stat(filename, &stat_buf) == 0) {
504
0
        if (stat_buf.st_mode & S_IFDIR) {
505
0
            SCLogError("yaml argument is not a file but a directory: %s. "
506
0
                       "Please specify the yaml file in your -c option.",
507
0
                    filename);
508
0
            yaml_parser_delete(&parser);
509
0
            return -1;
510
0
        }
511
0
    }
512
513
    // coverity[toctou : FALSE]
514
0
    infile = fopen(filename, "r");
515
0
    if (infile == NULL) {
516
0
        SCLogError("failed to open file: %s: %s", filename, strerror(errno));
517
0
        yaml_parser_delete(&parser);
518
0
        return -1;
519
0
    }
520
521
0
    if (conf_dirname == NULL) {
522
0
        ConfYamlSetConfDirname(filename);
523
0
    }
524
525
0
    yaml_parser_set_input_file(&parser, infile);
526
0
    ret = ConfYamlParse(&parser, root, 0, 0, 0);
527
0
    yaml_parser_delete(&parser);
528
0
    fclose(infile);
529
530
0
    return ret;
531
0
}
532
533
/**
534
 * \brief Load configuration from a YAML string.
535
 */
536
int SCConfYamlLoadString(const char *string, size_t len)
537
8.14k
{
538
8.14k
    SCConfNode *root = SCConfGetRootNode();
539
8.14k
    yaml_parser_t parser;
540
8.14k
    int ret;
541
542
8.14k
    if (yaml_parser_initialize(&parser) != 1) {
543
0
        fprintf(stderr, "Failed to initialize yaml parser.\n");
544
0
        exit(EXIT_FAILURE);
545
0
    }
546
8.14k
    yaml_parser_set_input_string(&parser, (const unsigned char *)string, len);
547
8.14k
    ret = ConfYamlParse(&parser, root, 0, 0, 0);
548
8.14k
    yaml_parser_delete(&parser);
549
550
8.14k
    return ret;
551
8.14k
}
552
553
/**
554
 * \brief Load configuration from a YAML file, insert in tree at 'prefix'
555
 *
556
 * This function will load a configuration file and insert it into the
557
 * config tree at 'prefix'. This means that if this is called with prefix
558
 * "abc" and the file contains a parameter "def", it will be loaded as
559
 * "abc.def".
560
 *
561
 * \param filename Filename of configuration file to load.
562
 * \param prefix Name prefix to use.
563
 *
564
 * \retval 0 on success, -1 on failure.
565
 */
566
int SCConfYamlLoadFileWithPrefix(const char *filename, const char *prefix)
567
0
{
568
0
    FILE *infile;
569
0
    yaml_parser_t parser;
570
0
    int ret;
571
0
    SCConfNode *root = SCConfGetNode(prefix);
572
573
0
    struct stat stat_buf;
574
    /* coverity[toctou] */
575
0
    if (stat(filename, &stat_buf) == 0) {
576
0
        if (stat_buf.st_mode & S_IFDIR) {
577
0
            SCLogError("yaml argument is not a file but a directory: %s. "
578
0
                       "Please specify the yaml file in your -c option.",
579
0
                    filename);
580
0
            return -1;
581
0
        }
582
0
    }
583
584
0
    if (yaml_parser_initialize(&parser) != 1) {
585
0
        SCLogError("failed to initialize yaml parser.");
586
0
        return -1;
587
0
    }
588
589
    /* coverity[toctou] */
590
0
    infile = fopen(filename, "r");
591
0
    if (infile == NULL) {
592
0
        SCLogError("failed to open file: %s: %s", filename, strerror(errno));
593
0
        yaml_parser_delete(&parser);
594
0
        return -1;
595
0
    }
596
597
0
    if (conf_dirname == NULL) {
598
0
        ConfYamlSetConfDirname(filename);
599
0
    }
600
601
0
    if (root == NULL) {
602
        /* if node at 'prefix' doesn't yet exist, add a place holder */
603
0
        SCConfSet(prefix, "<prefix root node>");
604
0
        root = SCConfGetNode(prefix);
605
0
        if (root == NULL) {
606
0
            fclose(infile);
607
0
            yaml_parser_delete(&parser);
608
0
            return -1;
609
0
        }
610
0
    }
611
0
    yaml_parser_set_input_file(&parser, infile);
612
0
    ret = ConfYamlParse(&parser, root, 0, 0, 0);
613
0
    yaml_parser_delete(&parser);
614
0
    fclose(infile);
615
616
0
    return ret;
617
0
}
618
619
#ifdef UNITTESTS
620
621
static int
622
ConfYamlSequenceTest(void)
623
{
624
    char input[] = "\
625
%YAML 1.1\n\
626
---\n\
627
rule-files:\n\
628
  - netbios.rules\n\
629
  - x11.rules\n\
630
\n\
631
default-log-dir: /tmp\n\
632
";
633
634
    SCConfCreateContextBackup();
635
    SCConfInit();
636
637
    SCConfYamlLoadString(input, strlen(input));
638
639
    SCConfNode *node;
640
    node = SCConfGetNode("rule-files");
641
    FAIL_IF_NULL(node);
642
    FAIL_IF_NOT(SCConfNodeIsSequence(node));
643
    FAIL_IF(TAILQ_EMPTY(&node->head));
644
    int i = 0;
645
    SCConfNode *filename;
646
    TAILQ_FOREACH(filename, &node->head, next) {
647
        if (i == 0) {
648
            FAIL_IF(strcmp(filename->val, "netbios.rules") != 0);
649
            FAIL_IF(SCConfNodeIsSequence(filename));
650
            FAIL_IF(filename->is_seq != 0);
651
        }
652
        else if (i == 1) {
653
            FAIL_IF(strcmp(filename->val, "x11.rules") != 0);
654
            FAIL_IF(SCConfNodeIsSequence(filename));
655
        }
656
        FAIL_IF(i > 1);
657
        i++;
658
    }
659
660
    SCConfDeInit();
661
    SCConfRestoreContextBackup();
662
    PASS;
663
}
664
665
static int
666
ConfYamlLoggingOutputTest(void)
667
{
668
    char input[] = "\
669
%YAML 1.1\n\
670
---\n\
671
logging:\n\
672
  output:\n\
673
    - interface: console\n\
674
      log-level: error\n\
675
    - interface: syslog\n\
676
      facility: local4\n\
677
      log-level: info\n\
678
";
679
680
    SCConfCreateContextBackup();
681
    SCConfInit();
682
683
    SCConfYamlLoadString(input, strlen(input));
684
685
    SCConfNode *outputs;
686
    outputs = SCConfGetNode("logging.output");
687
    FAIL_IF_NULL(outputs);
688
689
    SCConfNode *output;
690
    SCConfNode *output_param;
691
692
    output = TAILQ_FIRST(&outputs->head);
693
    FAIL_IF_NULL(output);
694
    FAIL_IF(strcmp(output->name, "0") != 0);
695
696
    output_param = TAILQ_FIRST(&output->head);
697
    FAIL_IF_NULL(output_param);
698
    FAIL_IF(strcmp(output_param->name, "interface") != 0);
699
    FAIL_IF(strcmp(output_param->val, "console") != 0);
700
701
    output_param = TAILQ_NEXT(output_param, next);
702
    FAIL_IF(strcmp(output_param->name, "log-level") != 0);
703
    FAIL_IF(strcmp(output_param->val, "error") != 0);
704
705
    output = TAILQ_NEXT(output, next);
706
    FAIL_IF_NULL(output);
707
    FAIL_IF(strcmp(output->name, "1") != 0);
708
709
    output_param = TAILQ_FIRST(&output->head);
710
    FAIL_IF_NULL(output_param);
711
    FAIL_IF(strcmp(output_param->name, "interface") != 0);
712
    FAIL_IF(strcmp(output_param->val, "syslog") != 0);
713
714
    output_param = TAILQ_NEXT(output_param, next);
715
    FAIL_IF(strcmp(output_param->name, "facility") != 0);
716
    FAIL_IF(strcmp(output_param->val, "local4") != 0);
717
718
    output_param = TAILQ_NEXT(output_param, next);
719
    FAIL_IF(strcmp(output_param->name, "log-level") != 0);
720
    FAIL_IF(strcmp(output_param->val, "info") != 0);
721
722
    SCConfDeInit();
723
    SCConfRestoreContextBackup();
724
725
    PASS;
726
}
727
728
/**
729
 * Try to load something that is not a valid YAML file.
730
 */
731
static int
732
ConfYamlNonYamlFileTest(void)
733
{
734
    SCConfCreateContextBackup();
735
    SCConfInit();
736
737
    FAIL_IF(SCConfYamlLoadFile("/etc/passwd") != -1);
738
739
    SCConfDeInit();
740
    SCConfRestoreContextBackup();
741
742
    PASS;
743
}
744
745
static int
746
ConfYamlBadYamlVersionTest(void)
747
{
748
    char input[] = "\
749
%YAML 9.9\n\
750
---\n\
751
logging:\n\
752
  output:\n\
753
    - interface: console\n\
754
      log-level: error\n\
755
    - interface: syslog\n\
756
      facility: local4\n\
757
      log-level: info\n\
758
";
759
760
    SCConfCreateContextBackup();
761
    SCConfInit();
762
763
    FAIL_IF(SCConfYamlLoadString(input, strlen(input)) != -1);
764
765
    SCConfDeInit();
766
    SCConfRestoreContextBackup();
767
768
    PASS;
769
}
770
771
static int
772
ConfYamlSecondLevelSequenceTest(void)
773
{
774
    char input[] = "\
775
%YAML 1.1\n\
776
---\n\
777
libhtp:\n\
778
  server-config:\n\
779
    - apache-php:\n\
780
        address: [\"192.168.1.0/24\"]\n\
781
        personality: [\"Apache_2_2\", \"PHP_5_3\"]\n\
782
        path-parsing: [\"compress_separators\", \"lowercase\"]\n\
783
    - iis-php:\n\
784
        address:\n\
785
          - 192.168.0.0/24\n\
786
\n\
787
        personality:\n\
788
          - IIS_7_0\n\
789
          - PHP_5_3\n\
790
\n\
791
        path-parsing:\n\
792
          - compress_separators\n\
793
";
794
795
    SCConfCreateContextBackup();
796
    SCConfInit();
797
798
    FAIL_IF(SCConfYamlLoadString(input, strlen(input)) != 0);
799
800
    SCConfNode *outputs;
801
    outputs = SCConfGetNode("libhtp.server-config");
802
    FAIL_IF_NULL(outputs);
803
804
    SCConfNode *node;
805
806
    node = TAILQ_FIRST(&outputs->head);
807
    FAIL_IF_NULL(node);
808
    FAIL_IF(strcmp(node->name, "0") != 0);
809
810
    node = TAILQ_FIRST(&node->head);
811
    FAIL_IF_NULL(node);
812
    FAIL_IF(strcmp(node->name, "apache-php") != 0);
813
814
    node = SCConfNodeLookupChild(node, "address");
815
    FAIL_IF_NULL(node);
816
817
    node = TAILQ_FIRST(&node->head);
818
    FAIL_IF_NULL(node);
819
    FAIL_IF(strcmp(node->name, "0") != 0);
820
    FAIL_IF(strcmp(node->val, "192.168.1.0/24") != 0);
821
822
    SCConfDeInit();
823
    SCConfRestoreContextBackup();
824
825
    PASS;
826
}
827
828
/**
829
 * Test file inclusion support.
830
 */
831
static int
832
ConfYamlFileIncludeTest(void)
833
{
834
    FILE *config_file;
835
836
    const char config_filename[] = "ConfYamlFileIncludeTest-config.yaml";
837
    const char config_file_contents[] =
838
        "%YAML 1.1\n"
839
        "---\n"
840
        "# Include something at the root level.\n"
841
        "include: ConfYamlFileIncludeTest-include.yaml\n"
842
        "# Test including under a mapping.\n"
843
        "mapping: !include ConfYamlFileIncludeTest-include.yaml\n";
844
845
    const char include_filename[] = "ConfYamlFileIncludeTest-include.yaml";
846
    const char include_file_contents[] =
847
        "%YAML 1.1\n"
848
        "---\n"
849
        "host-mode: auto\n"
850
        "unix-command:\n"
851
        "  enabled: no\n";
852
853
    SCConfCreateContextBackup();
854
    SCConfInit();
855
856
    /* Write out the test files. */
857
    FAIL_IF_NULL((config_file = fopen(config_filename, "w")));
858
    FAIL_IF(fwrite(config_file_contents, strlen(config_file_contents), 1, config_file) != 1);
859
    fclose(config_file);
860
861
    FAIL_IF_NULL((config_file = fopen(include_filename, "w")));
862
    FAIL_IF(fwrite(include_file_contents, strlen(include_file_contents), 1, config_file) != 1);
863
    fclose(config_file);
864
865
    /* Reset conf_dirname. */
866
    if (conf_dirname != NULL) {
867
        SCFree(conf_dirname);
868
        conf_dirname = NULL;
869
    }
870
871
    FAIL_IF(SCConfYamlLoadFile("ConfYamlFileIncludeTest-config.yaml") != 0);
872
873
    /* Check values that should have been loaded into the root of the
874
     * configuration. */
875
    SCConfNode *node;
876
    node = SCConfGetNode("host-mode");
877
    FAIL_IF_NULL(node);
878
    FAIL_IF(strcmp(node->val, "auto") != 0);
879
880
    node = SCConfGetNode("unix-command.enabled");
881
    FAIL_IF_NULL(node);
882
    FAIL_IF(strcmp(node->val, "no") != 0);
883
884
    /* Check for values that were included under a mapping. */
885
    node = SCConfGetNode("mapping.host-mode");
886
    FAIL_IF_NULL(node);
887
    FAIL_IF(strcmp(node->val, "auto") != 0);
888
889
    node = SCConfGetNode("mapping.unix-command.enabled");
890
    FAIL_IF_NULL(node);
891
    FAIL_IF(strcmp(node->val, "no") != 0);
892
893
    SCConfDeInit();
894
    SCConfRestoreContextBackup();
895
896
    unlink(config_filename);
897
    unlink(include_filename);
898
899
    PASS;
900
}
901
902
/**
903
 * Test that a configuration section is overridden but subsequent
904
 * occurrences.
905
 */
906
static int
907
ConfYamlOverrideTest(void)
908
{
909
    char config[] = "%YAML 1.1\n"
910
                    "---\n"
911
                    "some-log-dir: /var/log\n"
912
                    "some-log-dir: /tmp\n"
913
                    "\n"
914
                    "parent:\n"
915
                    "  child0:\n"
916
                    "    key: value\n"
917
                    "parent:\n"
918
                    "  child1:\n"
919
                    "    key: value\n"
920
                    "vars:\n"
921
                    "  address-groups:\n"
922
                    "    HOME_NET: \"[192.168.0.0/16,10.0.0.0/8,172.16.0.0/12]\"\n"
923
                    "    EXTERNAL_NET: any\n"
924
                    "vars.address-groups.HOME_NET: \"10.10.10.10/32\"\n";
925
    const char *value;
926
927
    SCConfCreateContextBackup();
928
    SCConfInit();
929
930
    FAIL_IF(SCConfYamlLoadString(config, strlen(config)) != 0);
931
    FAIL_IF_NOT(SCConfGet("some-log-dir", &value));
932
    FAIL_IF(strcmp(value, "/tmp") != 0);
933
934
    /* Test that parent.child0 does not exist, but child1 does. */
935
    FAIL_IF_NOT_NULL(SCConfGetNode("parent.child0"));
936
    FAIL_IF_NOT(SCConfGet("parent.child1.key", &value));
937
    FAIL_IF(strcmp(value, "value") != 0);
938
939
    /* First check that vars.address-groups.EXTERNAL_NET has the
940
     * expected parent of vars.address-groups and save this
941
     * pointer. We want to make sure that the overrided value has the
942
     * same parent later on. */
943
    SCConfNode *vars_address_groups = SCConfGetNode("vars.address-groups");
944
    FAIL_IF_NULL(vars_address_groups);
945
    SCConfNode *vars_address_groups_external_net =
946
            SCConfGetNode("vars.address-groups.EXTERNAL_NET");
947
    FAIL_IF_NULL(vars_address_groups_external_net);
948
    FAIL_IF_NOT(vars_address_groups_external_net->parent == vars_address_groups);
949
950
    /* Now check that HOME_NET has the overrided value. */
951
    SCConfNode *vars_address_groups_home_net = SCConfGetNode("vars.address-groups.HOME_NET");
952
    FAIL_IF_NULL(vars_address_groups_home_net);
953
    FAIL_IF(strcmp(vars_address_groups_home_net->val, "10.10.10.10/32") != 0);
954
955
    /* And check that it has the correct parent. */
956
    FAIL_IF_NOT(vars_address_groups_home_net->parent == vars_address_groups);
957
958
    SCConfDeInit();
959
    SCConfRestoreContextBackup();
960
961
    PASS;
962
}
963
964
/**
965
 * Test that a configuration parameter loaded from YAML doesn't
966
 * override a 'final' value that may be set on the command line.
967
 */
968
static int
969
ConfYamlOverrideFinalTest(void)
970
{
971
    SCConfCreateContextBackup();
972
    SCConfInit();
973
974
    char config[] =
975
        "%YAML 1.1\n"
976
        "---\n"
977
        "default-log-dir: /var/log\n";
978
979
    /* Set the log directory as if it was set on the command line. */
980
    FAIL_IF_NOT(SCConfSetFinal("default-log-dir", "/tmp"));
981
    FAIL_IF(SCConfYamlLoadString(config, strlen(config)) != 0);
982
983
    const char *default_log_dir;
984
985
    FAIL_IF_NOT(SCConfGet("default-log-dir", &default_log_dir));
986
    FAIL_IF(strcmp(default_log_dir, "/tmp") != 0);
987
988
    SCConfDeInit();
989
    SCConfRestoreContextBackup();
990
991
    PASS;
992
}
993
994
static int ConfYamlNull(void)
995
{
996
    SCConfCreateContextBackup();
997
    SCConfInit();
998
999
    char config[] = "%YAML 1.1\n"
1000
                    "---\n"
1001
                    "quoted-tilde: \"~\"\n"
1002
                    "unquoted-tilde: ~\n"
1003
                    "quoted-null: \"null\"\n"
1004
                    "unquoted-null: null\n"
1005
                    "quoted-Null: \"Null\"\n"
1006
                    "unquoted-Null: Null\n"
1007
                    "quoted-NULL: \"NULL\"\n"
1008
                    "unquoted-NULL: NULL\n"
1009
                    "empty-quoted: \"\"\n"
1010
                    "empty-unquoted: \n"
1011
                    "list: [\"null\", null, \"Null\", Null, \"NULL\", NULL, \"~\", ~]\n";
1012
    FAIL_IF(SCConfYamlLoadString(config, strlen(config)) != 0);
1013
1014
    const char *val;
1015
1016
    FAIL_IF_NOT(SCConfGet("quoted-tilde", &val));
1017
    FAIL_IF_NULL(val);
1018
    FAIL_IF_NOT(SCConfGet("unquoted-tilde", &val));
1019
    FAIL_IF_NOT_NULL(val);
1020
1021
    FAIL_IF_NOT(SCConfGet("quoted-null", &val));
1022
    FAIL_IF_NULL(val);
1023
    FAIL_IF_NOT(SCConfGet("unquoted-null", &val));
1024
    FAIL_IF_NOT_NULL(val);
1025
1026
    FAIL_IF_NOT(SCConfGet("quoted-Null", &val));
1027
    FAIL_IF_NULL(val);
1028
    FAIL_IF_NOT(SCConfGet("unquoted-Null", &val));
1029
    FAIL_IF_NOT_NULL(val);
1030
1031
    FAIL_IF_NOT(SCConfGet("quoted-NULL", &val));
1032
    FAIL_IF_NULL(val);
1033
    FAIL_IF_NOT(SCConfGet("unquoted-NULL", &val));
1034
    FAIL_IF_NOT_NULL(val);
1035
1036
    FAIL_IF_NOT(SCConfGet("empty-quoted", &val));
1037
    FAIL_IF_NULL(val);
1038
    FAIL_IF_NOT(SCConfGet("empty-unquoted", &val));
1039
    FAIL_IF_NOT_NULL(val);
1040
1041
    FAIL_IF_NOT(SCConfGet("list.0", &val));
1042
    FAIL_IF_NULL(val);
1043
    FAIL_IF_NOT(SCConfGet("list.1", &val));
1044
    FAIL_IF_NOT_NULL(val);
1045
1046
    FAIL_IF_NOT(SCConfGet("list.2", &val));
1047
    FAIL_IF_NULL(val);
1048
    FAIL_IF_NOT(SCConfGet("list.3", &val));
1049
    FAIL_IF_NOT_NULL(val);
1050
1051
    FAIL_IF_NOT(SCConfGet("list.4", &val));
1052
    FAIL_IF_NULL(val);
1053
    FAIL_IF_NOT(SCConfGet("list.5", &val));
1054
    FAIL_IF_NOT_NULL(val);
1055
1056
    FAIL_IF_NOT(SCConfGet("list.6", &val));
1057
    FAIL_IF_NULL(val);
1058
    FAIL_IF_NOT(SCConfGet("list.7", &val));
1059
    FAIL_IF_NOT_NULL(val);
1060
1061
    SCConfDeInit();
1062
    SCConfRestoreContextBackup();
1063
1064
    PASS;
1065
}
1066
1067
#endif /* UNITTESTS */
1068
1069
void SCConfYamlRegisterTests(void)
1070
0
{
1071
#ifdef UNITTESTS
1072
    UtRegisterTest("ConfYamlSequenceTest", ConfYamlSequenceTest);
1073
    UtRegisterTest("ConfYamlLoggingOutputTest", ConfYamlLoggingOutputTest);
1074
    UtRegisterTest("ConfYamlNonYamlFileTest", ConfYamlNonYamlFileTest);
1075
    UtRegisterTest("ConfYamlBadYamlVersionTest", ConfYamlBadYamlVersionTest);
1076
    UtRegisterTest("ConfYamlSecondLevelSequenceTest",
1077
                   ConfYamlSecondLevelSequenceTest);
1078
    UtRegisterTest("ConfYamlFileIncludeTest", ConfYamlFileIncludeTest);
1079
    UtRegisterTest("ConfYamlOverrideTest", ConfYamlOverrideTest);
1080
    UtRegisterTest("ConfYamlOverrideFinalTest", ConfYamlOverrideFinalTest);
1081
    UtRegisterTest("ConfYamlNull", ConfYamlNull);
1082
#endif /* UNITTESTS */
1083
0
}