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