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