suricata
conf-yaml-loader.c
Go to the documentation of this file.
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,
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 {
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
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) {
96  "ERROR: Failed to allocate memory while loading configuration.");
97  }
98  }
99  else {
100  conf_dirname = SCStrdup(filename);
101  if (conf_dirname == NULL) {
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
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) {
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) {
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
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  int was_empty = -1;
185 
186  if (rlevel++ > RECURSION_LIMIT) {
187  SCLogError(SC_ERR_CONF_YAML_ERROR, "Recursion limit reached while parsing "
188  "configuration file, aborting.");
189  return -1;
190  }
191 
192  while (!done) {
193  if (!yaml_parser_parse(parser, &event)) {
195  "Failed to parse configuration file at line %" PRIuMAX ": %s\n",
196  (uintmax_t)parser->problem_mark.line, parser->problem);
197  retval = -1;
198  break;
199  }
200 
201  if (event.type == YAML_DOCUMENT_START_EVENT) {
202  SCLogDebug("event.type=YAML_DOCUMENT_START_EVENT; state=%d", state);
203  /* Verify YAML version - its more likely to be a valid
204  * Suricata configuration file if the version is
205  * correct. */
206  yaml_version_directive_t *ver =
207  event.data.document_start.version_directive;
208  if (ver == NULL) {
209  SCLogError(SC_ERR_CONF_YAML_ERROR, "ERROR: Invalid configuration file.");
211  "The configuration file must begin with the following two lines: %%YAML 1.1 and ---");
212  goto fail;
213  }
214  int major = ver->major;
215  int minor = ver->minor;
216  if (!(major == YAML_VERSION_MAJOR && minor == YAML_VERSION_MINOR)) {
217  SCLogError(SC_ERR_CONF_YAML_ERROR, "ERROR: Invalid YAML version. Must be 1.1");
218  goto fail;
219  }
220  }
221  else if (event.type == YAML_SCALAR_EVENT) {
222  char *value = (char *)event.data.scalar.value;
223  char *tag = (char *)event.data.scalar.tag;
224  SCLogDebug("event.type=YAML_SCALAR_EVENT; state=%d; value=%s; "
225  "tag=%s; inseq=%d", state, value, tag, inseq);
226 
227  /* Skip over empty scalar values while in KEY state. This
228  * tends to only happen on an empty file, where a scalar
229  * event probably shouldn't fire anyways. */
230  if (state == CONF_KEY && strlen(value) == 0) {
231  goto next;
232  }
233 
234  /* If the value is unquoted, certain strings in YAML represent NULL. */
235  if ((inseq || state == CONF_VAL) &&
236  event.data.scalar.style == YAML_PLAIN_SCALAR_STYLE) {
237  if (strlen(value) == 0 || strcmp(value, "~") == 0 || strcmp(value, "null") == 0 ||
238  strcmp(value, "Null") == 0 || strcmp(value, "NULL") == 0) {
239  value = NULL;
240  }
241  }
242 
243  if (inseq) {
244  char sequence_node_name[DEFAULT_NAME_LEN];
245  snprintf(sequence_node_name, DEFAULT_NAME_LEN, "%d", seq_idx++);
246  ConfNode *seq_node = NULL;
247  if (was_empty < 0) {
248  // initialize was_empty
249  if (TAILQ_EMPTY(&parent->head)) {
250  was_empty = 1;
251  } else {
252  was_empty = 0;
253  }
254  }
255  // we only check if the node's list was not empty at first
256  if (was_empty == 0) {
257  seq_node = ConfNodeLookupChild(parent, sequence_node_name);
258  }
259  if (seq_node != NULL) {
260  /* The sequence node has already been set, probably
261  * from the command line. Remove it so it gets
262  * re-added in the expected order for iteration.
263  */
264  TAILQ_REMOVE(&parent->head, seq_node, next);
265  }
266  else {
267  seq_node = ConfNodeNew();
268  if (unlikely(seq_node == NULL)) {
269  goto fail;
270  }
271  seq_node->name = SCStrdup(sequence_node_name);
272  if (unlikely(seq_node->name == NULL)) {
273  SCFree(seq_node);
274  goto fail;
275  }
276  if (value != NULL) {
277  seq_node->val = SCStrdup(value);
278  if (unlikely(seq_node->val == NULL)) {
279  SCFree(seq_node->name);
280  goto fail;
281  }
282  } else {
283  seq_node->val = NULL;
284  }
285  }
286  TAILQ_INSERT_TAIL(&parent->head, seq_node, next);
287  }
288  else {
289  if (state == CONF_INCLUDE) {
290  SCLogInfo("Including configuration file %s.", value);
291  if (ConfYamlHandleInclude(parent, value) != 0) {
292  goto fail;
293  }
294  state = CONF_KEY;
295  }
296  else if (state == CONF_KEY) {
297 
298  if (strcmp(value, "include") == 0) {
299  state = CONF_INCLUDE;
300  goto next;
301  }
302 
303  if (parent->is_seq) {
304  if (parent->val == NULL) {
305  parent->val = SCStrdup(value);
306  if (parent->val && strchr(parent->val, '_'))
307  Mangle(parent->val);
308  }
309  }
310  ConfNode *existing = ConfNodeLookupChild(parent, value);
311  if (existing != NULL) {
312  if (!existing->final) {
313  SCLogInfo("Configuration node '%s' redefined.",
314  existing->name);
315  ConfNodePrune(existing);
316  }
317  node = existing;
318  }
319  else {
320  node = ConfNodeNew();
321  node->name = SCStrdup(value);
322  if (node->name && strchr(node->name, '_')) {
323  if (!(parent->name &&
324  ((strcmp(parent->name, "address-groups") == 0) ||
325  (strcmp(parent->name, "port-groups") == 0)))) {
326  Mangle(node->name);
327  if (mangle_errors < MANGLE_ERRORS_MAX) {
329  "%s is deprecated. Please use %s on line %"PRIuMAX".",
330  value, node->name, (uintmax_t)parser->mark.line+1);
331  mangle_errors++;
332  if (mangle_errors >= MANGLE_ERRORS_MAX)
333  SCLogWarning(SC_WARN_DEPRECATED, "not showing more "
334  "parameter name warnings.");
335  }
336  }
337  }
338  TAILQ_INSERT_TAIL(&parent->head, node, next);
339  }
340  state = CONF_VAL;
341  }
342  else {
343  if (value != NULL && (tag != NULL) && (strcmp(tag, "!include") == 0)) {
344  SCLogInfo("Including configuration file %s at "
345  "parent node %s.", value, node->name);
346  if (ConfYamlHandleInclude(node, value) != 0)
347  goto fail;
348  } else if (!node->final && value != NULL) {
349  if (node->val != NULL)
350  SCFree(node->val);
351  node->val = SCStrdup(value);
352  }
353  state = CONF_KEY;
354  }
355  }
356  }
357  else if (event.type == YAML_SEQUENCE_START_EVENT) {
358  SCLogDebug("event.type=YAML_SEQUENCE_START_EVENT; state=%d", state);
359  if (ConfYamlParse(parser, node, 1, rlevel) != 0)
360  goto fail;
361  node->is_seq = 1;
362  state = CONF_KEY;
363  }
364  else if (event.type == YAML_SEQUENCE_END_EVENT) {
365  SCLogDebug("event.type=YAML_SEQUENCE_END_EVENT; state=%d", state);
366  done = 1;
367  }
368  else if (event.type == YAML_MAPPING_START_EVENT) {
369  SCLogDebug("event.type=YAML_MAPPING_START_EVENT; state=%d", state);
370  if (inseq) {
371  char sequence_node_name[DEFAULT_NAME_LEN];
372  snprintf(sequence_node_name, DEFAULT_NAME_LEN, "%d", seq_idx++);
373  ConfNode *seq_node = ConfNodeLookupChild(node,
374  sequence_node_name);
375  if (seq_node != NULL) {
376  /* The sequence node has already been set, probably
377  * from the command line. Remove it so it gets
378  * re-added in the expected order for iteration.
379  */
380  TAILQ_REMOVE(&node->head, seq_node, next);
381  }
382  else {
383  seq_node = ConfNodeNew();
384  if (unlikely(seq_node == NULL)) {
385  goto fail;
386  }
387  seq_node->name = SCStrdup(sequence_node_name);
388  if (unlikely(seq_node->name == NULL)) {
389  SCFree(seq_node);
390  goto fail;
391  }
392  }
393  seq_node->is_seq = 1;
394  TAILQ_INSERT_TAIL(&node->head, seq_node, next);
395  if (ConfYamlParse(parser, seq_node, 0, rlevel) != 0)
396  goto fail;
397  }
398  else {
399  if (ConfYamlParse(parser, node, inseq, rlevel) != 0)
400  goto fail;
401  }
402  state = CONF_KEY;
403  }
404  else if (event.type == YAML_MAPPING_END_EVENT) {
405  SCLogDebug("event.type=YAML_MAPPING_END_EVENT; state=%d", state);
406  done = 1;
407  }
408  else if (event.type == YAML_STREAM_END_EVENT) {
409  SCLogDebug("event.type=YAML_STREAM_END_EVENT; state=%d", state);
410  done = 1;
411  }
412 
413  next:
414  yaml_event_delete(&event);
415  continue;
416 
417  fail:
418  yaml_event_delete(&event);
419  retval = -1;
420  break;
421  }
422 
423  rlevel--;
424  return retval;
425 }
426 
427 /**
428  * \brief Load configuration from a YAML file.
429  *
430  * This function will load a configuration file. On failure -1 will
431  * be returned and it is suggested that the program then exit. Any
432  * errors while loading the configuration file will have already been
433  * logged.
434  *
435  * \param filename Filename of configuration file to load.
436  *
437  * \retval 0 on success, -1 on failure.
438  */
439 int
440 ConfYamlLoadFile(const char *filename)
441 {
442  FILE *infile;
443  yaml_parser_t parser;
444  int ret;
445  ConfNode *root = ConfGetRootNode();
446 
447  if (yaml_parser_initialize(&parser) != 1) {
448  SCLogError(SC_ERR_FATAL, "failed to initialize yaml parser.");
449  return -1;
450  }
451 
452  struct stat stat_buf;
453  if (stat(filename, &stat_buf) == 0) {
454  if (stat_buf.st_mode & S_IFDIR) {
455  SCLogError(SC_ERR_FATAL, "yaml argument is not a file but a directory: %s. "
456  "Please specify the yaml file in your -c option.", filename);
457  yaml_parser_delete(&parser);
458  return -1;
459  }
460  }
461 
462  // coverity[toctou : FALSE]
463  infile = fopen(filename, "r");
464  if (infile == NULL) {
465  SCLogError(SC_ERR_FATAL, "failed to open file: %s: %s", filename,
466  strerror(errno));
467  yaml_parser_delete(&parser);
468  return -1;
469  }
470 
471  if (conf_dirname == NULL) {
472  ConfYamlSetConfDirname(filename);
473  }
474 
475  yaml_parser_set_input_file(&parser, infile);
476  ret = ConfYamlParse(&parser, root, 0, 0);
477  yaml_parser_delete(&parser);
478  fclose(infile);
479 
480  return ret;
481 }
482 
483 /**
484  * \brief Load configuration from a YAML string.
485  */
486 int
487 ConfYamlLoadString(const char *string, size_t len)
488 {
489  ConfNode *root = ConfGetRootNode();
490  yaml_parser_t parser;
491  int ret;
492 
493  if (yaml_parser_initialize(&parser) != 1) {
494  fprintf(stderr, "Failed to initialize yaml parser.\n");
495  exit(EXIT_FAILURE);
496  }
497  yaml_parser_set_input_string(&parser, (const unsigned char *)string, len);
498  ret = ConfYamlParse(&parser, root, 0, 0);
499  yaml_parser_delete(&parser);
500 
501  return ret;
502 }
503 
504 /**
505  * \brief Load configuration from a YAML file, insert in tree at 'prefix'
506  *
507  * This function will load a configuration file and insert it into the
508  * config tree at 'prefix'. This means that if this is called with prefix
509  * "abc" and the file contains a parameter "def", it will be loaded as
510  * "abc.def".
511  *
512  * \param filename Filename of configuration file to load.
513  * \param prefix Name prefix to use.
514  *
515  * \retval 0 on success, -1 on failure.
516  */
517 int
518 ConfYamlLoadFileWithPrefix(const char *filename, const char *prefix)
519 {
520  FILE *infile;
521  yaml_parser_t parser;
522  int ret;
523  ConfNode *root = ConfGetNode(prefix);
524 
525  if (yaml_parser_initialize(&parser) != 1) {
526  SCLogError(SC_ERR_FATAL, "failed to initialize yaml parser.");
527  return -1;
528  }
529 
530  struct stat stat_buf;
531  /* coverity[toctou] */
532  if (stat(filename, &stat_buf) == 0) {
533  if (stat_buf.st_mode & S_IFDIR) {
534  SCLogError(SC_ERR_FATAL, "yaml argument is not a file but a directory: %s. "
535  "Please specify the yaml file in your -c option.", filename);
536  return -1;
537  }
538  }
539 
540  /* coverity[toctou] */
541  infile = fopen(filename, "r");
542  if (infile == NULL) {
543  SCLogError(SC_ERR_FATAL, "failed to open file: %s: %s", filename,
544  strerror(errno));
545  yaml_parser_delete(&parser);
546  return -1;
547  }
548 
549  if (conf_dirname == NULL) {
550  ConfYamlSetConfDirname(filename);
551  }
552 
553  if (root == NULL) {
554  /* if node at 'prefix' doesn't yet exist, add a place holder */
555  ConfSet(prefix, "<prefix root node>");
556  root = ConfGetNode(prefix);
557  if (root == NULL) {
558  fclose(infile);
559  yaml_parser_delete(&parser);
560  return -1;
561  }
562  }
563  yaml_parser_set_input_file(&parser, infile);
564  ret = ConfYamlParse(&parser, root, 0, 0);
565  yaml_parser_delete(&parser);
566  fclose(infile);
567 
568  return ret;
569 }
570 
571 #ifdef UNITTESTS
572 
573 static int
574 ConfYamlSequenceTest(void)
575 {
576  char input[] = "\
577 %YAML 1.1\n\
578 ---\n\
579 rule-files:\n\
580  - netbios.rules\n\
581  - x11.rules\n\
582 \n\
583 default-log-dir: /tmp\n\
584 ";
585 
587  ConfInit();
588 
589  ConfYamlLoadString(input, strlen(input));
590 
591  ConfNode *node;
592  node = ConfGetNode("rule-files");
593  FAIL_IF_NULL(node);
595  FAIL_IF(TAILQ_EMPTY(&node->head));
596  int i = 0;
597  ConfNode *filename;
598  TAILQ_FOREACH(filename, &node->head, next) {
599  if (i == 0) {
600  FAIL_IF(strcmp(filename->val, "netbios.rules") != 0);
601  FAIL_IF(ConfNodeIsSequence(filename));
602  FAIL_IF(filename->is_seq != 0);
603  }
604  else if (i == 1) {
605  FAIL_IF(strcmp(filename->val, "x11.rules") != 0);
606  FAIL_IF(ConfNodeIsSequence(filename));
607  }
608  FAIL_IF(i > 1);
609  i++;
610  }
611 
612  ConfDeInit();
614  PASS;
615 }
616 
617 static int
618 ConfYamlLoggingOutputTest(void)
619 {
620  char input[] = "\
621 %YAML 1.1\n\
622 ---\n\
623 logging:\n\
624  output:\n\
625  - interface: console\n\
626  log-level: error\n\
627  - interface: syslog\n\
628  facility: local4\n\
629  log-level: info\n\
630 ";
631 
633  ConfInit();
634 
635  ConfYamlLoadString(input, strlen(input));
636 
637  ConfNode *outputs;
638  outputs = ConfGetNode("logging.output");
639  FAIL_IF_NULL(outputs);
640 
641  ConfNode *output;
642  ConfNode *output_param;
643 
644  output = TAILQ_FIRST(&outputs->head);
645  FAIL_IF_NULL(output);
646  FAIL_IF(strcmp(output->name, "0") != 0);
647 
648  output_param = TAILQ_FIRST(&output->head);
649  FAIL_IF_NULL(output_param);
650  FAIL_IF(strcmp(output_param->name, "interface") != 0);
651  FAIL_IF(strcmp(output_param->val, "console") != 0);
652 
653  output_param = TAILQ_NEXT(output_param, next);
654  FAIL_IF(strcmp(output_param->name, "log-level") != 0);
655  FAIL_IF(strcmp(output_param->val, "error") != 0);
656 
657  output = TAILQ_NEXT(output, next);
658  FAIL_IF_NULL(output);
659  FAIL_IF(strcmp(output->name, "1") != 0);
660 
661  output_param = TAILQ_FIRST(&output->head);
662  FAIL_IF_NULL(output_param);
663  FAIL_IF(strcmp(output_param->name, "interface") != 0);
664  FAIL_IF(strcmp(output_param->val, "syslog") != 0);
665 
666  output_param = TAILQ_NEXT(output_param, next);
667  FAIL_IF(strcmp(output_param->name, "facility") != 0);
668  FAIL_IF(strcmp(output_param->val, "local4") != 0);
669 
670  output_param = TAILQ_NEXT(output_param, next);
671  FAIL_IF(strcmp(output_param->name, "log-level") != 0);
672  FAIL_IF(strcmp(output_param->val, "info") != 0);
673 
674  ConfDeInit();
676 
677  PASS;
678 }
679 
680 /**
681  * Try to load something that is not a valid YAML file.
682  */
683 static int
684 ConfYamlNonYamlFileTest(void)
685 {
687  ConfInit();
688 
689  FAIL_IF(ConfYamlLoadFile("/etc/passwd") != -1);
690 
691  ConfDeInit();
693 
694  PASS;
695 }
696 
697 static int
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 
713  ConfInit();
714 
715  FAIL_IF(ConfYamlLoadString(input, strlen(input)) != -1);
716 
717  ConfDeInit();
719 
720  PASS;
721 }
722 
723 static int
724 ConfYamlSecondLevelSequenceTest(void)
725 {
726  char input[] = "\
727 %YAML 1.1\n\
728 ---\n\
729 libhtp:\n\
730  server-config:\n\
731  - apache-php:\n\
732  address: [\"192.168.1.0/24\"]\n\
733  personality: [\"Apache_2_2\", \"PHP_5_3\"]\n\
734  path-parsing: [\"compress_separators\", \"lowercase\"]\n\
735  - iis-php:\n\
736  address:\n\
737  - 192.168.0.0/24\n\
738 \n\
739  personality:\n\
740  - IIS_7_0\n\
741  - PHP_5_3\n\
742 \n\
743  path-parsing:\n\
744  - compress_separators\n\
745 ";
746 
748  ConfInit();
749 
750  FAIL_IF(ConfYamlLoadString(input, strlen(input)) != 0);
751 
752  ConfNode *outputs;
753  outputs = ConfGetNode("libhtp.server-config");
754  FAIL_IF_NULL(outputs);
755 
756  ConfNode *node;
757 
758  node = TAILQ_FIRST(&outputs->head);
759  FAIL_IF_NULL(node);
760  FAIL_IF(strcmp(node->name, "0") != 0);
761 
762  node = TAILQ_FIRST(&node->head);
763  FAIL_IF_NULL(node);
764  FAIL_IF(strcmp(node->name, "apache-php") != 0);
765 
766  node = ConfNodeLookupChild(node, "address");
767  FAIL_IF_NULL(node);
768 
769  node = TAILQ_FIRST(&node->head);
770  FAIL_IF_NULL(node);
771  FAIL_IF(strcmp(node->name, "0") != 0);
772  FAIL_IF(strcmp(node->val, "192.168.1.0/24") != 0);
773 
774  ConfDeInit();
776 
777  PASS;
778 }
779 
780 /**
781  * Test file inclusion support.
782  */
783 static int
784 ConfYamlFileIncludeTest(void)
785 {
786  FILE *config_file;
787 
788  const char config_filename[] = "ConfYamlFileIncludeTest-config.yaml";
789  const char config_file_contents[] =
790  "%YAML 1.1\n"
791  "---\n"
792  "# Include something at the root level.\n"
793  "include: ConfYamlFileIncludeTest-include.yaml\n"
794  "# Test including under a mapping.\n"
795  "mapping: !include ConfYamlFileIncludeTest-include.yaml\n";
796 
797  const char include_filename[] = "ConfYamlFileIncludeTest-include.yaml";
798  const char include_file_contents[] =
799  "%YAML 1.1\n"
800  "---\n"
801  "host-mode: auto\n"
802  "unix-command:\n"
803  " enabled: no\n";
804 
806  ConfInit();
807 
808  /* Write out the test files. */
809  FAIL_IF_NULL((config_file = fopen(config_filename, "w")));
810  FAIL_IF(fwrite(config_file_contents, strlen(config_file_contents), 1, config_file) != 1);
811  fclose(config_file);
812 
813  FAIL_IF_NULL((config_file = fopen(include_filename, "w")));
814  FAIL_IF(fwrite(include_file_contents, strlen(include_file_contents), 1, config_file) != 1);
815  fclose(config_file);
816 
817  /* Reset conf_dirname. */
818  if (conf_dirname != NULL) {
819  SCFree(conf_dirname);
820  conf_dirname = NULL;
821  }
822 
823  FAIL_IF(ConfYamlLoadFile("ConfYamlFileIncludeTest-config.yaml") != 0);
824 
825  /* Check values that should have been loaded into the root of the
826  * configuration. */
827  ConfNode *node;
828  node = ConfGetNode("host-mode");
829  FAIL_IF_NULL(node);
830  FAIL_IF(strcmp(node->val, "auto") != 0);
831 
832  node = ConfGetNode("unix-command.enabled");
833  FAIL_IF_NULL(node);
834  FAIL_IF(strcmp(node->val, "no") != 0);
835 
836  /* Check for values that were included under a mapping. */
837  node = ConfGetNode("mapping.host-mode");
838  FAIL_IF_NULL(node);
839  FAIL_IF(strcmp(node->val, "auto") != 0);
840 
841  node = ConfGetNode("mapping.unix-command.enabled");
842  FAIL_IF_NULL(node);
843  FAIL_IF(strcmp(node->val, "no") != 0);
844 
845  ConfDeInit();
847 
848  unlink(config_filename);
849  unlink(include_filename);
850 
851  PASS;
852 }
853 
854 /**
855  * Test that a configuration section is overridden but subsequent
856  * occurrences.
857  */
858 static int
859 ConfYamlOverrideTest(void)
860 {
861  char config[] =
862  "%YAML 1.1\n"
863  "---\n"
864  "some-log-dir: /var/log\n"
865  "some-log-dir: /tmp\n"
866  "\n"
867  "parent:\n"
868  " child0:\n"
869  " key: value\n"
870  "parent:\n"
871  " child1:\n"
872  " key: value\n"
873  ;
874  const char *value;
875 
877  ConfInit();
878 
879  FAIL_IF(ConfYamlLoadString(config, strlen(config)) != 0);
880  FAIL_IF_NOT(ConfGet("some-log-dir", &value));
881  FAIL_IF(strcmp(value, "/tmp") != 0);
882 
883  /* Test that parent.child0 does not exist, but child1 does. */
884  FAIL_IF_NOT_NULL(ConfGetNode("parent.child0"));
885  FAIL_IF_NOT(ConfGet("parent.child1.key", &value));
886  FAIL_IF(strcmp(value, "value") != 0);
887 
888  ConfDeInit();
890 
891  PASS;
892 }
893 
894 /**
895  * Test that a configuration parameter loaded from YAML doesn't
896  * override a 'final' value that may be set on the command line.
897  */
898 static int
899 ConfYamlOverrideFinalTest(void)
900 {
902  ConfInit();
903 
904  char config[] =
905  "%YAML 1.1\n"
906  "---\n"
907  "default-log-dir: /var/log\n";
908 
909  /* Set the log directory as if it was set on the command line. */
910  FAIL_IF_NOT(ConfSetFinal("default-log-dir", "/tmp"));
911  FAIL_IF(ConfYamlLoadString(config, strlen(config)) != 0);
912 
913  const char *default_log_dir;
914 
915  FAIL_IF_NOT(ConfGet("default-log-dir", &default_log_dir));
916  FAIL_IF(strcmp(default_log_dir, "/tmp") != 0);
917 
918  ConfDeInit();
920 
921  PASS;
922 }
923 
924 static int ConfYamlNull(void)
925 {
927  ConfInit();
928 
929  char config[] = "%YAML 1.1\n"
930  "---\n"
931  "quoted-tilde: \"~\"\n"
932  "unquoted-tilde: ~\n"
933  "quoted-null: \"null\"\n"
934  "unquoted-null: null\n"
935  "quoted-Null: \"Null\"\n"
936  "unquoted-Null: Null\n"
937  "quoted-NULL: \"NULL\"\n"
938  "unquoted-NULL: NULL\n"
939  "empty-quoted: \"\"\n"
940  "empty-unquoted: \n"
941  "list: [\"null\", null, \"Null\", Null, \"NULL\", NULL, \"~\", ~]\n";
942  FAIL_IF(ConfYamlLoadString(config, strlen(config)) != 0);
943 
944  const char *val;
945 
946  FAIL_IF_NOT(ConfGet("quoted-tilde", &val));
947  FAIL_IF_NULL(val);
948  FAIL_IF_NOT(ConfGet("unquoted-tilde", &val));
949  FAIL_IF_NOT_NULL(val);
950 
951  FAIL_IF_NOT(ConfGet("quoted-null", &val));
952  FAIL_IF_NULL(val);
953  FAIL_IF_NOT(ConfGet("unquoted-null", &val));
954  FAIL_IF_NOT_NULL(val);
955 
956  FAIL_IF_NOT(ConfGet("quoted-Null", &val));
957  FAIL_IF_NULL(val);
958  FAIL_IF_NOT(ConfGet("unquoted-Null", &val));
959  FAIL_IF_NOT_NULL(val);
960 
961  FAIL_IF_NOT(ConfGet("quoted-NULL", &val));
962  FAIL_IF_NULL(val);
963  FAIL_IF_NOT(ConfGet("unquoted-NULL", &val));
964  FAIL_IF_NOT_NULL(val);
965 
966  FAIL_IF_NOT(ConfGet("empty-quoted", &val));
967  FAIL_IF_NULL(val);
968  FAIL_IF_NOT(ConfGet("empty-unquoted", &val));
969  FAIL_IF_NOT_NULL(val);
970 
971  FAIL_IF_NOT(ConfGet("list.0", &val));
972  FAIL_IF_NULL(val);
973  FAIL_IF_NOT(ConfGet("list.1", &val));
974  FAIL_IF_NOT_NULL(val);
975 
976  FAIL_IF_NOT(ConfGet("list.2", &val));
977  FAIL_IF_NULL(val);
978  FAIL_IF_NOT(ConfGet("list.3", &val));
979  FAIL_IF_NOT_NULL(val);
980 
981  FAIL_IF_NOT(ConfGet("list.4", &val));
982  FAIL_IF_NULL(val);
983  FAIL_IF_NOT(ConfGet("list.5", &val));
984  FAIL_IF_NOT_NULL(val);
985 
986  FAIL_IF_NOT(ConfGet("list.6", &val));
987  FAIL_IF_NULL(val);
988  FAIL_IF_NOT(ConfGet("list.7", &val));
989  FAIL_IF_NOT_NULL(val);
990 
991  ConfDeInit();
993 
994  PASS;
995 }
996 
997 #endif /* UNITTESTS */
998 
999 void
1001 {
1002 #ifdef UNITTESTS
1003  UtRegisterTest("ConfYamlSequenceTest", ConfYamlSequenceTest);
1004  UtRegisterTest("ConfYamlLoggingOutputTest", ConfYamlLoggingOutputTest);
1005  UtRegisterTest("ConfYamlNonYamlFileTest", ConfYamlNonYamlFileTest);
1006  UtRegisterTest("ConfYamlBadYamlVersionTest", ConfYamlBadYamlVersionTest);
1007  UtRegisterTest("ConfYamlSecondLevelSequenceTest",
1008  ConfYamlSecondLevelSequenceTest);
1009  UtRegisterTest("ConfYamlFileIncludeTest", ConfYamlFileIncludeTest);
1010  UtRegisterTest("ConfYamlOverrideTest", ConfYamlOverrideTest);
1011  UtRegisterTest("ConfYamlOverrideFinalTest", ConfYamlOverrideFinalTest);
1012  UtRegisterTest("ConfYamlNull", ConfYamlNull);
1013 #endif /* UNITTESTS */
1014 }
len
uint8_t len
Definition: app-layer-dnp3.h:2
FAIL_IF_NULL
#define FAIL_IF_NULL(expr)
Fail a test if expression evaluates to NULL.
Definition: util-unittest.h:89
ConfNode_::val
char * val
Definition: conf.h:34
unlikely
#define unlikely(expr)
Definition: util-optimize.h:35
UtRegisterTest
void UtRegisterTest(const char *name, int(*TestFn)(void))
Register unit test.
Definition: util-unittest.c:103
YAML_VERSION_MAJOR
#define YAML_VERSION_MAJOR
Definition: conf-yaml-loader.c:34
SCLogDebug
#define SCLogDebug(...)
Definition: util-debug.h:298
next
struct HtpBodyChunk_ * next
Definition: app-layer-htp.h:0
ConfGetNode
ConfNode * ConfGetNode(const char *name)
Get a ConfNode by name.
Definition: conf.c:175
ConfNodeNew
ConfNode * ConfNodeNew(void)
Allocate a new configuration node.
Definition: conf.c:133
ConfYamlLoadFileWithPrefix
int ConfYamlLoadFileWithPrefix(const char *filename, const char *prefix)
Load configuration from a YAML file, insert in tree at 'prefix'.
Definition: conf-yaml-loader.c:518
TAILQ_EMPTY
#define TAILQ_EMPTY(head)
Definition: queue.h:249
TAILQ_FOREACH
#define TAILQ_FOREACH(var, head, field)
Definition: queue.h:253
ConfSetFinal
int ConfSetFinal(const char *name, const char *val)
Set a final configuration value.
Definition: conf.c:298
TAILQ_INSERT_TAIL
#define TAILQ_INSERT_TAIL(head, elm, field)
Definition: queue.h:295
YAML_VERSION_MINOR
#define YAML_VERSION_MINOR
Definition: conf-yaml-loader.c:35
conf_state
conf_state
Definition: conf-yaml-loader.c:54
util-unittest.h
ConfNode_::is_seq
int is_seq
Definition: conf.h:36
FAIL_IF_NOT
#define FAIL_IF_NOT(expr)
Fail a test if expression evaluates to false.
Definition: util-unittest.h:82
strlcpy
size_t strlcpy(char *dst, const char *src, size_t siz)
Definition: util-strlcpyu.c:43
tag
uint32_t tag
Definition: decode-vntag.h:0
ConfGet
int ConfGet(const char *name, const char **vptr)
Retrieve the value of a configuration node.
Definition: conf.c:330
ConfGetRootNode
ConfNode * ConfGetRootNode(void)
Get the root configuration node.
Definition: conf.c:202
TAILQ_REMOVE
#define TAILQ_REMOVE(head, elm, field)
Definition: queue.h:313
FAIL_IF_NOT_NULL
#define FAIL_IF_NOT_NULL(expr)
Fail a test if expression evaluates to non-NULL.
Definition: util-unittest.h:96
util-debug.h
TAILQ_FIRST
#define TAILQ_FIRST(head)
Definition: queue.h:251
CONF_KEY
@ CONF_KEY
Definition: conf-yaml-loader.c:55
ConfNodePrune
void ConfNodePrune(ConfNode *node)
Prune a configuration node.
Definition: conf.c:942
SC_ERR_CONF_YAML_ERROR
@ SC_ERR_CONF_YAML_ERROR
Definition: util-error.h:274
PASS
#define PASS
Pass the test.
Definition: util-unittest.h:105
ConfYamlLoadFile
int ConfYamlLoadFile(const char *filename)
Load configuration from a YAML file.
Definition: conf-yaml-loader.c:440
RECURSION_LIMIT
#define RECURSION_LIMIT
Definition: conf-yaml-loader.c:39
ConfNode_::final
int final
Definition: conf.h:39
ConfYamlLoadString
int ConfYamlLoadString(const char *string, size_t len)
Load configuration from a YAML string.
Definition: conf-yaml-loader.c:487
SC_WARN_DEPRECATED
@ SC_WARN_DEPRECATED
Definition: util-error.h:235
SC_ERR_FOPEN
@ SC_ERR_FOPEN
Definition: util-error.h:74
conf-yaml-loader.h
conf.h
ConfCreateContextBackup
void ConfCreateContextBackup(void)
Creates a backup of the conf_hash hash_table used by the conf API.
Definition: conf.c:698
MANGLE_ERRORS_MAX
#define MANGLE_ERRORS_MAX
Definition: conf-yaml-loader.c:46
SCLogInfo
#define SCLogInfo(...)
Macro used to log INFORMATIONAL messages.
Definition: util-debug.h:217
ConfNodeLookupChild
ConfNode * ConfNodeLookupChild(const ConfNode *node, const char *name)
Lookup a child configuration node by name.
Definition: conf.c:814
FAIL_IF
#define FAIL_IF(expr)
Fail a test if expression evaluates to true.
Definition: util-unittest.h:71
ConfYamlRegisterTests
void ConfYamlRegisterTests(void)
Definition: conf-yaml-loader.c:1000
suricata-common.h
util-path.h
ConfNode_::name
char * name
Definition: conf.h:33
TAILQ_NEXT
#define TAILQ_NEXT(elm, field)
Definition: queue.h:308
PathIsAbsolute
int PathIsAbsolute(const char *path)
Check if a path is absolute.
Definition: util-path.c:45
SCLogError
#define SCLogError(err_code,...)
Macro used to log ERROR messages.
Definition: util-debug.h:257
ConfNodeIsSequence
int ConfNodeIsSequence(const ConfNode *node)
Check if a node is a sequence or node.
Definition: conf.c:974
ConfRestoreContextBackup
void ConfRestoreContextBackup(void)
Restores the backup of the hash_table present in backup_conf_hash back to conf_hash.
Definition: conf.c:710
SCStrdup
#define SCStrdup(s)
Definition: util-mem.h:56
FatalError
#define FatalError(x,...)
Definition: util-debug.h:532
ConfInit
void ConfInit(void)
Initialize the configuration system.
Definition: conf.c:113
SCLogWarning
#define SCLogWarning(err_code,...)
Macro used to log WARNING messages.
Definition: util-debug.h:244
SCFree
#define SCFree(p)
Definition: util-mem.h:61
ConfNode_
Definition: conf.h:32
SC_ERR_FATAL
@ SC_ERR_FATAL
Definition: util-error.h:203
ConfDeInit
void ConfDeInit(void)
De-initializes the configuration system.
Definition: conf.c:721
ConfSet
int ConfSet(const char *name, const char *val)
Set a configuration value.
Definition: conf.c:219
CONF_VAL
@ CONF_VAL
Definition: conf-yaml-loader.c:56
DEFAULT_NAME_LEN
#define DEFAULT_NAME_LEN
Definition: conf-yaml-loader.c:44
CONF_INCLUDE
@ CONF_INCLUDE
Definition: conf-yaml-loader.c:57