Coverage Report

Created: 2026-06-07 07:05

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