1 /* Copyright (C) 2007-2010 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
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);
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
Mangle(char * string)68 Mangle(char *string)
69 {
70 char *c;
71
72 while ((c = strchr(string, '_')))
73 *c = '-';
74
75 return;
76 }
77
78 /**
79 * \brief Set the directory name of the configuration file.
80 *
81 * \param filename The configuration filename.
82 */
83 static void
ConfYamlSetConfDirname(const char * filename)84 ConfYamlSetConfDirname(const char *filename)
85 {
86 char *ep;
87
88 ep = strrchr(filename, '\\');
89 if (ep == NULL)
90 ep = strrchr(filename, '/');
91
92 if (ep == NULL) {
93 conf_dirname = SCStrdup(".");
94 if (conf_dirname == NULL) {
95 FatalError(SC_ERR_FATAL,
96 "ERROR: Failed to allocate memory while loading configuration.");
97 }
98 }
99 else {
100 conf_dirname = SCStrdup(filename);
101 if (conf_dirname == NULL) {
102 FatalError(SC_ERR_FATAL,
103 "ERROR: Failed to allocate memory while loading configuration.");
104 }
105 conf_dirname[ep - filename] = '\0';
106 }
107 }
108
109 /**
110 * \brief Include a file in the configuration.
111 *
112 * \param parent The configuration node the included configuration will be
113 * placed at.
114 * \param filename The filename to include.
115 *
116 * \retval 0 on success, -1 on failure.
117 */
118 static int
ConfYamlHandleInclude(ConfNode * parent,const char * filename)119 ConfYamlHandleInclude(ConfNode *parent, const char *filename)
120 {
121 yaml_parser_t parser;
122 char include_filename[PATH_MAX];
123 FILE *file = NULL;
124 int ret = -1;
125
126 if (yaml_parser_initialize(&parser) != 1) {
127 SCLogError(SC_ERR_CONF_YAML_ERROR, "Failed to initialize YAML parser");
128 return -1;
129 }
130
131 if (PathIsAbsolute(filename)) {
132 strlcpy(include_filename, filename, sizeof(include_filename));
133 }
134 else {
135 snprintf(include_filename, sizeof(include_filename), "%s/%s",
136 conf_dirname, filename);
137 }
138
139 file = fopen(include_filename, "r");
140 if (file == NULL) {
141 SCLogError(SC_ERR_FOPEN,
142 "Failed to open configuration include file %s: %s",
143 include_filename, strerror(errno));
144 goto done;
145 }
146
147 yaml_parser_set_input_file(&parser, file);
148
149 if (ConfYamlParse(&parser, parent, 0, 0) != 0) {
150 SCLogError(SC_ERR_CONF_YAML_ERROR,
151 "Failed to include configuration file %s", filename);
152 goto done;
153 }
154
155 ret = 0;
156
157 done:
158 yaml_parser_delete(&parser);
159 if (file != NULL) {
160 fclose(file);
161 }
162
163 return ret;
164 }
165
166 /**
167 * \brief Parse a YAML layer.
168 *
169 * \param parser A pointer to an active yaml_parser_t.
170 * \param parent The parent configuration node.
171 *
172 * \retval 0 on success, -1 on failure.
173 */
174 static int
ConfYamlParse(yaml_parser_t * parser,ConfNode * parent,int inseq,int rlevel)175 ConfYamlParse(yaml_parser_t *parser, ConfNode *parent, int inseq, int rlevel)
176 {
177 ConfNode *node = parent;
178 yaml_event_t event;
179 memset(&event, 0, sizeof(event));
180 int done = 0;
181 int state = 0;
182 int seq_idx = 0;
183 int retval = 0;
184
185 if (rlevel++ > RECURSION_LIMIT) {
186 SCLogError(SC_ERR_CONF_YAML_ERROR, "Recursion limit reached while parsing "
187 "configuration file, aborting.");
188 return -1;
189 }
190
191 while (!done) {
192 if (!yaml_parser_parse(parser, &event)) {
193 SCLogError(SC_ERR_CONF_YAML_ERROR,
194 "Failed to parse configuration file at line %" PRIuMAX ": %s\n",
195 (uintmax_t)parser->problem_mark.line, parser->problem);
196 retval = -1;
197 break;
198 }
199
200 if (event.type == YAML_DOCUMENT_START_EVENT) {
201 SCLogDebug("event.type=YAML_DOCUMENT_START_EVENT; state=%d", state);
202 /* Verify YAML version - its more likely to be a valid
203 * Suricata configuration file if the version is
204 * correct. */
205 yaml_version_directive_t *ver =
206 event.data.document_start.version_directive;
207 if (ver == NULL) {
208 SCLogError(SC_ERR_CONF_YAML_ERROR, "ERROR: Invalid configuration file.");
209 SCLogError(SC_ERR_CONF_YAML_ERROR,
210 "The configuration file must begin with the following two lines: %%YAML 1.1 and ---");
211 goto fail;
212 }
213 int major = ver->major;
214 int minor = ver->minor;
215 if (!(major == YAML_VERSION_MAJOR && minor == YAML_VERSION_MINOR)) {
216 SCLogError(SC_ERR_CONF_YAML_ERROR, "ERROR: Invalid YAML version. Must be 1.1");
217 goto fail;
218 }
219 }
220 else if (event.type == YAML_SCALAR_EVENT) {
221 char *value = (char *)event.data.scalar.value;
222 char *tag = (char *)event.data.scalar.tag;
223 SCLogDebug("event.type=YAML_SCALAR_EVENT; state=%d; value=%s; "
224 "tag=%s; inseq=%d", state, value, tag, inseq);
225
226 /* Skip over empty scalar values while in KEY state. This
227 * tends to only happen on an empty file, where a scalar
228 * event probably shouldn't fire anyways. */
229 if (state == CONF_KEY && strlen(value) == 0) {
230 goto next;
231 }
232
233 if (inseq) {
234 char sequence_node_name[DEFAULT_NAME_LEN];
235 snprintf(sequence_node_name, DEFAULT_NAME_LEN, "%d", seq_idx++);
236 ConfNode *seq_node = ConfNodeLookupChild(parent,
237 sequence_node_name);
238 if (seq_node != NULL) {
239 /* The sequence node has already been set, probably
240 * from the command line. Remove it so it gets
241 * re-added in the expected order for iteration.
242 */
243 TAILQ_REMOVE(&parent->head, seq_node, next);
244 }
245 else {
246 seq_node = ConfNodeNew();
247 if (unlikely(seq_node == NULL)) {
248 goto fail;
249 }
250 seq_node->name = SCStrdup(sequence_node_name);
251 if (unlikely(seq_node->name == NULL)) {
252 SCFree(seq_node);
253 goto fail;
254 }
255 seq_node->val = SCStrdup(value);
256 if (unlikely(seq_node->val == NULL)) {
257 SCFree(seq_node->name);
258 goto fail;
259 }
260 }
261 TAILQ_INSERT_TAIL(&parent->head, seq_node, next);
262 }
263 else {
264 if (state == CONF_INCLUDE) {
265 SCLogInfo("Including configuration file %s.", value);
266 if (ConfYamlHandleInclude(parent, value) != 0) {
267 goto fail;
268 }
269 state = CONF_KEY;
270 }
271 else if (state == CONF_KEY) {
272
273 if (strcmp(value, "include") == 0) {
274 state = CONF_INCLUDE;
275 goto next;
276 }
277
278 if (parent->is_seq) {
279 if (parent->val == NULL) {
280 parent->val = SCStrdup(value);
281 if (parent->val && strchr(parent->val, '_'))
282 Mangle(parent->val);
283 }
284 }
285 ConfNode *existing = ConfNodeLookupChild(parent, value);
286 if (existing != NULL) {
287 if (!existing->final) {
288 SCLogInfo("Configuration node '%s' redefined.",
289 existing->name);
290 ConfNodePrune(existing);
291 }
292 node = existing;
293 }
294 else {
295 node = ConfNodeNew();
296 node->name = SCStrdup(value);
297 if (node->name && strchr(node->name, '_')) {
298 if (!(parent->name &&
299 ((strcmp(parent->name, "address-groups") == 0) ||
300 (strcmp(parent->name, "port-groups") == 0)))) {
301 Mangle(node->name);
302 if (mangle_errors < MANGLE_ERRORS_MAX) {
303 SCLogWarning(SC_WARN_DEPRECATED,
304 "%s is deprecated. Please use %s on line %"PRIuMAX".",
305 value, node->name, (uintmax_t)parser->mark.line+1);
306 mangle_errors++;
307 if (mangle_errors >= MANGLE_ERRORS_MAX)
308 SCLogWarning(SC_WARN_DEPRECATED, "not showing more "
309 "parameter name warnings.");
310 }
311 }
312 }
313 TAILQ_INSERT_TAIL(&parent->head, node, next);
314 }
315 state = CONF_VAL;
316 }
317 else {
318 if ((tag != NULL) && (strcmp(tag, "!include") == 0)) {
319 SCLogInfo("Including configuration file %s at "
320 "parent node %s.", value, node->name);
321 if (ConfYamlHandleInclude(node, value) != 0)
322 goto fail;
323 }
324 else if (!node->final) {
325 if (node->val != NULL)
326 SCFree(node->val);
327 node->val = SCStrdup(value);
328 }
329 state = CONF_KEY;
330 }
331 }
332 }
333 else if (event.type == YAML_SEQUENCE_START_EVENT) {
334 SCLogDebug("event.type=YAML_SEQUENCE_START_EVENT; state=%d", state);
335 if (ConfYamlParse(parser, node, 1, rlevel) != 0)
336 goto fail;
337 node->is_seq = 1;
338 state = CONF_KEY;
339 }
340 else if (event.type == YAML_SEQUENCE_END_EVENT) {
341 SCLogDebug("event.type=YAML_SEQUENCE_END_EVENT; state=%d", state);
342 done = 1;
343 }
344 else if (event.type == YAML_MAPPING_START_EVENT) {
345 SCLogDebug("event.type=YAML_MAPPING_START_EVENT; state=%d", state);
346 if (inseq) {
347 char sequence_node_name[DEFAULT_NAME_LEN];
348 snprintf(sequence_node_name, DEFAULT_NAME_LEN, "%d", seq_idx++);
349 ConfNode *seq_node = ConfNodeLookupChild(node,
350 sequence_node_name);
351 if (seq_node != NULL) {
352 /* The sequence node has already been set, probably
353 * from the command line. Remove it so it gets
354 * re-added in the expected order for iteration.
355 */
356 TAILQ_REMOVE(&node->head, seq_node, next);
357 }
358 else {
359 seq_node = ConfNodeNew();
360 if (unlikely(seq_node == NULL)) {
361 goto fail;
362 }
363 seq_node->name = SCStrdup(sequence_node_name);
364 if (unlikely(seq_node->name == NULL)) {
365 SCFree(seq_node);
366 goto fail;
367 }
368 }
369 seq_node->is_seq = 1;
370 TAILQ_INSERT_TAIL(&node->head, seq_node, next);
371 if (ConfYamlParse(parser, seq_node, 0, rlevel) != 0)
372 goto fail;
373 }
374 else {
375 if (ConfYamlParse(parser, node, inseq, rlevel) != 0)
376 goto fail;
377 }
378 state = CONF_KEY;
379 }
380 else if (event.type == YAML_MAPPING_END_EVENT) {
381 SCLogDebug("event.type=YAML_MAPPING_END_EVENT; state=%d", state);
382 done = 1;
383 }
384 else if (event.type == YAML_STREAM_END_EVENT) {
385 SCLogDebug("event.type=YAML_STREAM_END_EVENT; state=%d", state);
386 done = 1;
387 }
388
389 next:
390 yaml_event_delete(&event);
391 continue;
392
393 fail:
394 yaml_event_delete(&event);
395 retval = -1;
396 break;
397 }
398
399 rlevel--;
400 return retval;
401 }
402
403 /**
404 * \brief Load configuration from a YAML file.
405 *
406 * This function will load a configuration file. On failure -1 will
407 * be returned and it is suggested that the program then exit. Any
408 * errors while loading the configuration file will have already been
409 * logged.
410 *
411 * \param filename Filename of configuration file to load.
412 *
413 * \retval 0 on success, -1 on failure.
414 */
415 int
ConfYamlLoadFile(const char * filename)416 ConfYamlLoadFile(const char *filename)
417 {
418 FILE *infile;
419 yaml_parser_t parser;
420 int ret;
421 ConfNode *root = ConfGetRootNode();
422
423 if (yaml_parser_initialize(&parser) != 1) {
424 SCLogError(SC_ERR_FATAL, "failed to initialize yaml parser.");
425 return -1;
426 }
427
428 struct stat stat_buf;
429 if (stat(filename, &stat_buf) == 0) {
430 if (stat_buf.st_mode & S_IFDIR) {
431 SCLogError(SC_ERR_FATAL, "yaml argument is not a file but a directory: %s. "
432 "Please specify the yaml file in your -c option.", filename);
433 yaml_parser_delete(&parser);
434 return -1;
435 }
436 }
437
438 // coverity[toctou : FALSE]
439 infile = fopen(filename, "r");
440 if (infile == NULL) {
441 SCLogError(SC_ERR_FATAL, "failed to open file: %s: %s", filename,
442 strerror(errno));
443 yaml_parser_delete(&parser);
444 return -1;
445 }
446
447 if (conf_dirname == NULL) {
448 ConfYamlSetConfDirname(filename);
449 }
450
451 yaml_parser_set_input_file(&parser, infile);
452 ret = ConfYamlParse(&parser, root, 0, 0);
453 yaml_parser_delete(&parser);
454 fclose(infile);
455
456 return ret;
457 }
458
459 /**
460 * \brief Load configuration from a YAML string.
461 */
462 int
ConfYamlLoadString(const char * string,size_t len)463 ConfYamlLoadString(const char *string, size_t len)
464 {
465 ConfNode *root = ConfGetRootNode();
466 yaml_parser_t parser;
467 int ret;
468
469 if (yaml_parser_initialize(&parser) != 1) {
470 fprintf(stderr, "Failed to initialize yaml parser.\n");
471 exit(EXIT_FAILURE);
472 }
473 yaml_parser_set_input_string(&parser, (const unsigned char *)string, len);
474 ret = ConfYamlParse(&parser, root, 0, 0);
475 yaml_parser_delete(&parser);
476
477 return ret;
478 }
479
480 /**
481 * \brief Load configuration from a YAML file, insert in tree at 'prefix'
482 *
483 * This function will load a configuration file and insert it into the
484 * config tree at 'prefix'. This means that if this is called with prefix
485 * "abc" and the file contains a parameter "def", it will be loaded as
486 * "abc.def".
487 *
488 * \param filename Filename of configuration file to load.
489 * \param prefix Name prefix to use.
490 *
491 * \retval 0 on success, -1 on failure.
492 */
493 int
ConfYamlLoadFileWithPrefix(const char * filename,const char * prefix)494 ConfYamlLoadFileWithPrefix(const char *filename, const char *prefix)
495 {
496 FILE *infile;
497 yaml_parser_t parser;
498 int ret;
499 ConfNode *root = ConfGetNode(prefix);
500
501 if (yaml_parser_initialize(&parser) != 1) {
502 SCLogError(SC_ERR_FATAL, "failed to initialize yaml parser.");
503 return -1;
504 }
505
506 struct stat stat_buf;
507 /* coverity[toctou] */
508 if (stat(filename, &stat_buf) == 0) {
509 if (stat_buf.st_mode & S_IFDIR) {
510 SCLogError(SC_ERR_FATAL, "yaml argument is not a file but a directory: %s. "
511 "Please specify the yaml file in your -c option.", filename);
512 return -1;
513 }
514 }
515
516 /* coverity[toctou] */
517 infile = fopen(filename, "r");
518 if (infile == NULL) {
519 SCLogError(SC_ERR_FATAL, "failed to open file: %s: %s", filename,
520 strerror(errno));
521 yaml_parser_delete(&parser);
522 return -1;
523 }
524
525 if (conf_dirname == NULL) {
526 ConfYamlSetConfDirname(filename);
527 }
528
529 if (root == NULL) {
530 /* if node at 'prefix' doesn't yet exist, add a place holder */
531 ConfSet(prefix, "<prefix root node>");
532 root = ConfGetNode(prefix);
533 if (root == NULL) {
534 fclose(infile);
535 yaml_parser_delete(&parser);
536 return -1;
537 }
538 }
539 yaml_parser_set_input_file(&parser, infile);
540 ret = ConfYamlParse(&parser, root, 0, 0);
541 yaml_parser_delete(&parser);
542 fclose(infile);
543
544 return ret;
545 }
546
547 #ifdef UNITTESTS
548
549 static int
ConfYamlSequenceTest(void)550 ConfYamlSequenceTest(void)
551 {
552 char input[] = "\
553 %YAML 1.1\n\
554 ---\n\
555 rule-files:\n\
556 - netbios.rules\n\
557 - x11.rules\n\
558 \n\
559 default-log-dir: /tmp\n\
560 ";
561
562 ConfCreateContextBackup();
563 ConfInit();
564
565 ConfYamlLoadString(input, strlen(input));
566
567 ConfNode *node;
568 node = ConfGetNode("rule-files");
569 if (node == NULL)
570 return 0;
571 if (!ConfNodeIsSequence(node))
572 return 0;
573 if (TAILQ_EMPTY(&node->head))
574 return 0;
575 int i = 0;
576 ConfNode *filename;
577 TAILQ_FOREACH(filename, &node->head, next) {
578 if (i == 0) {
579 if (strcmp(filename->val, "netbios.rules") != 0)
580 return 0;
581 if (ConfNodeIsSequence(filename))
582 return 0;
583 if (filename->is_seq != 0)
584 return 0;
585 }
586 else if (i == 1) {
587 if (strcmp(filename->val, "x11.rules") != 0)
588 return 0;
589 if (ConfNodeIsSequence(filename))
590 return 0;
591 }
592 else {
593 return 0;
594 }
595 i++;
596 }
597
598 ConfDeInit();
599 ConfRestoreContextBackup();
600
601 return 1;
602 }
603
604 static int
ConfYamlLoggingOutputTest(void)605 ConfYamlLoggingOutputTest(void)
606 {
607 char input[] = "\
608 %YAML 1.1\n\
609 ---\n\
610 logging:\n\
611 output:\n\
612 - interface: console\n\
613 log-level: error\n\
614 - interface: syslog\n\
615 facility: local4\n\
616 log-level: info\n\
617 ";
618
619 ConfCreateContextBackup();
620 ConfInit();
621
622 ConfYamlLoadString(input, strlen(input));
623
624 ConfNode *outputs;
625 outputs = ConfGetNode("logging.output");
626 if (outputs == NULL)
627 return 0;
628
629 ConfNode *output;
630 ConfNode *output_param;
631
632 output = TAILQ_FIRST(&outputs->head);
633 if (output == NULL)
634 return 0;
635 if (strcmp(output->name, "0") != 0)
636 return 0;
637 output_param = TAILQ_FIRST(&output->head);
638 if (output_param == NULL)
639 return 0;
640 if (strcmp(output_param->name, "interface") != 0)
641 return 0;
642 if (strcmp(output_param->val, "console") != 0)
643 return 0;
644 output_param = TAILQ_NEXT(output_param, next);
645 if (strcmp(output_param->name, "log-level") != 0)
646 return 0;
647 if (strcmp(output_param->val, "error") != 0)
648 return 0;
649
650 output = TAILQ_NEXT(output, next);
651 if (output == NULL)
652 return 0;
653 if (strcmp(output->name, "1") != 0)
654 return 0;
655 output_param = TAILQ_FIRST(&output->head);
656 if (output_param == NULL)
657 return 0;
658 if (strcmp(output_param->name, "interface") != 0)
659 return 0;
660 if (strcmp(output_param->val, "syslog") != 0)
661 return 0;
662 output_param = TAILQ_NEXT(output_param, next);
663 if (strcmp(output_param->name, "facility") != 0)
664 return 0;
665 if (strcmp(output_param->val, "local4") != 0)
666 return 0;
667 output_param = TAILQ_NEXT(output_param, next);
668 if (strcmp(output_param->name, "log-level") != 0)
669 return 0;
670 if (strcmp(output_param->val, "info") != 0)
671 return 0;
672
673 ConfDeInit();
674 ConfRestoreContextBackup();
675
676 return 1;
677 }
678
679 /**
680 * Try to load something that is not a valid YAML file.
681 */
682 static int
ConfYamlNonYamlFileTest(void)683 ConfYamlNonYamlFileTest(void)
684 {
685 ConfCreateContextBackup();
686 ConfInit();
687
688 if (ConfYamlLoadFile("/etc/passwd") != -1)
689 return 0;
690
691 ConfDeInit();
692 ConfRestoreContextBackup();
693
694 return 1;
695 }
696
697 static int
ConfYamlBadYamlVersionTest(void)698 ConfYamlBadYamlVersionTest(void)
699 {
700 char input[] = "\
701 %YAML 9.9\n\
702 ---\n\
703 logging:\n\
704 output:\n\
705 - interface: console\n\
706 log-level: error\n\
707 - interface: syslog\n\
708 facility: local4\n\
709 log-level: info\n\
710 ";
711
712 ConfCreateContextBackup();
713 ConfInit();
714
715 if (ConfYamlLoadString(input, strlen(input)) != -1)
716 return 0;
717
718 ConfDeInit();
719 ConfRestoreContextBackup();
720
721 return 1;
722 }
723
724 static int
ConfYamlSecondLevelSequenceTest(void)725 ConfYamlSecondLevelSequenceTest(void)
726 {
727 char input[] = "\
728 %YAML 1.1\n\
729 ---\n\
730 libhtp:\n\
731 server-config:\n\
732 - apache-php:\n\
733 address: [\"192.168.1.0/24\"]\n\
734 personality: [\"Apache_2_2\", \"PHP_5_3\"]\n\
735 path-parsing: [\"compress_separators\", \"lowercase\"]\n\
736 - iis-php:\n\
737 address:\n\
738 - 192.168.0.0/24\n\
739 \n\
740 personality:\n\
741 - IIS_7_0\n\
742 - PHP_5_3\n\
743 \n\
744 path-parsing:\n\
745 - compress_separators\n\
746 ";
747
748 ConfCreateContextBackup();
749 ConfInit();
750
751 if (ConfYamlLoadString(input, strlen(input)) != 0)
752 return 0;
753
754 ConfNode *outputs;
755 outputs = ConfGetNode("libhtp.server-config");
756 if (outputs == NULL)
757 return 0;
758
759 ConfNode *node;
760
761 node = TAILQ_FIRST(&outputs->head);
762 if (node == NULL)
763 return 0;
764 if (strcmp(node->name, "0") != 0)
765 return 0;
766 node = TAILQ_FIRST(&node->head);
767 if (node == NULL)
768 return 0;
769 if (strcmp(node->name, "apache-php") != 0)
770 return 0;
771
772 node = ConfNodeLookupChild(node, "address");
773 if (node == NULL)
774 return 0;
775 node = TAILQ_FIRST(&node->head);
776 if (node == NULL)
777 return 0;
778 if (strcmp(node->name, "0") != 0)
779 return 0;
780 if (strcmp(node->val, "192.168.1.0/24") != 0)
781 return 0;
782
783 ConfDeInit();
784 ConfRestoreContextBackup();
785
786 return 1;
787 }
788
789 /**
790 * Test file inclusion support.
791 */
792 static int
ConfYamlFileIncludeTest(void)793 ConfYamlFileIncludeTest(void)
794 {
795 int ret = 0;
796 FILE *config_file;
797
798 const char config_filename[] = "ConfYamlFileIncludeTest-config.yaml";
799 const char config_file_contents[] =
800 "%YAML 1.1\n"
801 "---\n"
802 "# Include something at the root level.\n"
803 "include: ConfYamlFileIncludeTest-include.yaml\n"
804 "# Test including under a mapping.\n"
805 "mapping: !include ConfYamlFileIncludeTest-include.yaml\n";
806
807 const char include_filename[] = "ConfYamlFileIncludeTest-include.yaml";
808 const char include_file_contents[] =
809 "%YAML 1.1\n"
810 "---\n"
811 "host-mode: auto\n"
812 "unix-command:\n"
813 " enabled: no\n";
814
815 ConfCreateContextBackup();
816 ConfInit();
817
818 /* Write out the test files. */
819 if ((config_file = fopen(config_filename, "w")) == NULL) {
820 goto cleanup;
821 }
822 if (fwrite(config_file_contents, strlen(config_file_contents), 1,
823 config_file) != 1) {
824 goto cleanup;
825 }
826 fclose(config_file);
827 if ((config_file = fopen(include_filename, "w")) == NULL) {
828 goto cleanup;
829 }
830 if (fwrite(include_file_contents, strlen(include_file_contents), 1,
831 config_file) != 1) {
832 goto cleanup;
833 }
834 fclose(config_file);
835
836 /* Reset conf_dirname. */
837 if (conf_dirname != NULL) {
838 SCFree(conf_dirname);
839 conf_dirname = NULL;
840 }
841
842 if (ConfYamlLoadFile("ConfYamlFileIncludeTest-config.yaml") != 0)
843 goto cleanup;
844
845 /* Check values that should have been loaded into the root of the
846 * configuration. */
847 ConfNode *node;
848 node = ConfGetNode("host-mode");
849 if (node == NULL)
850 goto cleanup;
851 if (strcmp(node->val, "auto") != 0)
852 goto cleanup;
853 node = ConfGetNode("unix-command.enabled");
854 if (node == NULL)
855 goto cleanup;
856 if (strcmp(node->val, "no") != 0)
857 goto cleanup;
858
859 /* Check for values that were included under a mapping. */
860 node = ConfGetNode("mapping.host-mode");
861 if (node == NULL)
862 goto cleanup;
863 if (strcmp(node->val, "auto") != 0)
864 goto cleanup;
865 node = ConfGetNode("mapping.unix-command.enabled");
866 if (node == NULL)
867 goto cleanup;
868 if (strcmp(node->val, "no") != 0)
869 goto cleanup;
870
871 ConfDeInit();
872 ConfRestoreContextBackup();
873
874 ret = 1;
875
876 cleanup:
877 unlink(config_filename);
878 unlink(include_filename);
879
880 return ret;
881 }
882
883 /**
884 * Test that a configuration section is overridden but subsequent
885 * occurrences.
886 */
887 static int
ConfYamlOverrideTest(void)888 ConfYamlOverrideTest(void)
889 {
890 char config[] =
891 "%YAML 1.1\n"
892 "---\n"
893 "some-log-dir: /var/log\n"
894 "some-log-dir: /tmp\n"
895 "\n"
896 "parent:\n"
897 " child0:\n"
898 " key: value\n"
899 "parent:\n"
900 " child1:\n"
901 " key: value\n"
902 ;
903 const char *value;
904
905 ConfCreateContextBackup();
906 ConfInit();
907
908 if (ConfYamlLoadString(config, strlen(config)) != 0)
909 return 0;
910 if (!ConfGet("some-log-dir", &value))
911 return 0;
912 if (strcmp(value, "/tmp") != 0)
913 return 0;
914
915 /* Test that parent.child0 does not exist, but child1 does. */
916 if (ConfGetNode("parent.child0") != NULL)
917 return 0;
918 if (!ConfGet("parent.child1.key", &value))
919 return 0;
920 if (strcmp(value, "value") != 0)
921 return 0;
922
923 ConfDeInit();
924 ConfRestoreContextBackup();
925
926 return 1;
927 }
928
929 /**
930 * Test that a configuration parameter loaded from YAML doesn't
931 * override a 'final' value that may be set on the command line.
932 */
933 static int
ConfYamlOverrideFinalTest(void)934 ConfYamlOverrideFinalTest(void)
935 {
936 ConfCreateContextBackup();
937 ConfInit();
938
939 char config[] =
940 "%YAML 1.1\n"
941 "---\n"
942 "default-log-dir: /var/log\n";
943
944 /* Set the log directory as if it was set on the command line. */
945 if (!ConfSetFinal("default-log-dir", "/tmp"))
946 return 0;
947 if (ConfYamlLoadString(config, strlen(config)) != 0)
948 return 0;
949
950 const char *default_log_dir;
951
952 if (!ConfGet("default-log-dir", &default_log_dir))
953 return 0;
954 if (strcmp(default_log_dir, "/tmp") != 0) {
955 fprintf(stderr, "final value was reassigned\n");
956 return 0;
957 }
958
959 ConfDeInit();
960 ConfRestoreContextBackup();
961
962 return 1;
963 }
964
965 #endif /* UNITTESTS */
966
967 void
ConfYamlRegisterTests(void)968 ConfYamlRegisterTests(void)
969 {
970 #ifdef UNITTESTS
971 UtRegisterTest("ConfYamlSequenceTest", ConfYamlSequenceTest);
972 UtRegisterTest("ConfYamlLoggingOutputTest", ConfYamlLoggingOutputTest);
973 UtRegisterTest("ConfYamlNonYamlFileTest", ConfYamlNonYamlFileTest);
974 UtRegisterTest("ConfYamlBadYamlVersionTest", ConfYamlBadYamlVersionTest);
975 UtRegisterTest("ConfYamlSecondLevelSequenceTest",
976 ConfYamlSecondLevelSequenceTest);
977 UtRegisterTest("ConfYamlFileIncludeTest", ConfYamlFileIncludeTest);
978 UtRegisterTest("ConfYamlOverrideTest", ConfYamlOverrideTest);
979 UtRegisterTest("ConfYamlOverrideFinalTest", ConfYamlOverrideFinalTest);
980 #endif /* UNITTESTS */
981 }
982