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
47 static int mangle_errors = 0;
48 
49 static char *conf_dirname = NULL;
50 
51 static int ConfYamlParse(yaml_parser_t *parser, ConfNode *parent, int inseq, int rlevel, int state);
52 
53 /* Configuration processing states. */
54 enum conf_state {
55  CONF_KEY = 0,
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) {
95  FatalError("ERROR: Failed to allocate memory while loading configuration.");
96  }
97  }
98  else {
99  conf_dirname = SCStrdup(filename);
100  if (conf_dirname == NULL) {
101  FatalError("ERROR: Failed to allocate memory while loading configuration.");
102  }
103  conf_dirname[ep - filename] = '\0';
104  }
105 }
106 
107 /**
108  * \brief Include a file in the configuration.
109  *
110  * \param parent The configuration node the included configuration will be
111  * placed at.
112  * \param filename The filename to include.
113  *
114  * \retval 0 on success, -1 on failure.
115  */
116 int ConfYamlHandleInclude(ConfNode *parent, const char *filename)
117 {
118  yaml_parser_t parser;
119  char include_filename[PATH_MAX];
120  FILE *file = NULL;
121  int ret = -1;
122 
123  if (yaml_parser_initialize(&parser) != 1) {
124  SCLogError("Failed to initialize YAML parser");
125  return -1;
126  }
127 
128  if (PathIsAbsolute(filename)) {
129  strlcpy(include_filename, filename, sizeof(include_filename));
130  }
131  else {
132  snprintf(include_filename, sizeof(include_filename), "%s/%s",
133  conf_dirname, filename);
134  }
135 
136  file = fopen(include_filename, "r");
137  if (file == NULL) {
138  SCLogError("Failed to open configuration include file %s: %s", include_filename,
139  strerror(errno));
140  goto done;
141  }
142 
143  yaml_parser_set_input_file(&parser, file);
144 
145  if (ConfYamlParse(&parser, parent, 0, 0, 0) != 0) {
146  SCLogError("Failed to include configuration file %s", filename);
147  goto done;
148  }
149 
150  ret = 0;
151 
152 done:
153  yaml_parser_delete(&parser);
154  if (file != NULL) {
155  fclose(file);
156  }
157 
158  return ret;
159 }
160 
161 /**
162  * \brief Parse a YAML layer.
163  *
164  * \param parser A pointer to an active yaml_parser_t.
165  * \param parent The parent configuration node.
166  *
167  * \retval 0 on success, -1 on failure.
168  */
169 static int ConfYamlParse(yaml_parser_t *parser, ConfNode *parent, int inseq, int rlevel, int state)
170 {
171  ConfNode *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\n",
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 (ConfYamlHandleInclude(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  ConfNode *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 = ConfNodeLookupChild(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 = ConfNodeNew();
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 (ConfYamlHandleInclude(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 = ConfNodeGetNodeOrCreate(parent, value, 0);
321  if (node == NULL) {
322  /* Error message already logged. */
323  goto fail;
324  }
325  } else {
326  ConfNode *existing = ConfNodeLookupChild(parent, value);
327  if (existing != NULL) {
328  if (!existing->final) {
329  SCLogInfo("Configuration node '%s' redefined.", existing->name);
330  ConfNodePrune(existing);
331  }
332  node = existing;
333  } else {
334  node = ConfNodeNew();
335  node->name = SCStrdup(value);
336  node->parent = parent;
337  if (node->name && strchr(node->name, '_')) {
338  if (!(parent->name &&
339  ((strcmp(parent->name, "address-groups") == 0) ||
340  (strcmp(parent->name, "port-groups") == 0)))) {
341  Mangle(node->name);
342  if (mangle_errors < MANGLE_ERRORS_MAX) {
343  SCLogWarning(
344  "%s is deprecated. Please use %s on line %" PRIuMAX
345  ".",
346  value, node->name,
347  (uintmax_t)parser->mark.line + 1);
348  mangle_errors++;
349  if (mangle_errors >= MANGLE_ERRORS_MAX)
350  SCLogWarning("not showing more "
351  "parameter name warnings.");
352  }
353  }
354  }
355  TAILQ_INSERT_TAIL(&parent->head, node, next);
356  }
357  }
358  state = CONF_VAL;
359  }
360  else {
361  if (value != NULL && (tag != NULL) && (strcmp(tag, "!include") == 0)) {
362  SCLogInfo("Including configuration file %s at "
363  "parent node %s.", value, node->name);
364  if (ConfYamlHandleInclude(node, value) != 0)
365  goto fail;
366  } else if (!node->final && value != NULL) {
367  if (node->val != NULL)
368  SCFree(node->val);
369  node->val = SCStrdup(value);
370  }
371  state = CONF_KEY;
372  }
373  }
374  }
375  else if (event.type == YAML_SEQUENCE_START_EVENT) {
376  SCLogDebug("event.type=YAML_SEQUENCE_START_EVENT; state=%d", state);
377  if (ConfYamlParse(parser, node, 1, rlevel, state == CONF_INCLUDE ? CONF_INCLUDE : 0) !=
378  0)
379  goto fail;
380  node->is_seq = 1;
381  state = CONF_KEY;
382  }
383  else if (event.type == YAML_SEQUENCE_END_EVENT) {
384  SCLogDebug("event.type=YAML_SEQUENCE_END_EVENT; state=%d", state);
385  done = 1;
386  }
387  else if (event.type == YAML_MAPPING_START_EVENT) {
388  SCLogDebug("event.type=YAML_MAPPING_START_EVENT; state=%d", state);
389  if (state == CONF_INCLUDE) {
390  SCLogError("Include fields cannot be a mapping: line %zu", parser->mark.line);
391  goto fail;
392  }
393  if (inseq) {
394  char sequence_node_name[DEFAULT_NAME_LEN];
395  snprintf(sequence_node_name, DEFAULT_NAME_LEN, "%d", seq_idx++);
396  ConfNode *seq_node = ConfNodeLookupChild(node,
397  sequence_node_name);
398  if (seq_node != NULL) {
399  /* The sequence node has already been set, probably
400  * from the command line. Remove it so it gets
401  * re-added in the expected order for iteration.
402  */
403  TAILQ_REMOVE(&node->head, seq_node, next);
404  }
405  else {
406  seq_node = ConfNodeNew();
407  if (unlikely(seq_node == NULL)) {
408  goto fail;
409  }
410  seq_node->name = SCStrdup(sequence_node_name);
411  if (unlikely(seq_node->name == NULL)) {
412  SCFree(seq_node);
413  goto fail;
414  }
415  }
416  seq_node->is_seq = 1;
417  TAILQ_INSERT_TAIL(&node->head, seq_node, next);
418  if (ConfYamlParse(parser, seq_node, 0, rlevel, 0) != 0)
419  goto fail;
420  }
421  else {
422  if (ConfYamlParse(parser, node, inseq, rlevel, 0) != 0)
423  goto fail;
424  }
425  state = CONF_KEY;
426  }
427  else if (event.type == YAML_MAPPING_END_EVENT) {
428  SCLogDebug("event.type=YAML_MAPPING_END_EVENT; state=%d", state);
429  done = 1;
430  }
431  else if (event.type == YAML_STREAM_END_EVENT) {
432  SCLogDebug("event.type=YAML_STREAM_END_EVENT; state=%d", state);
433  done = 1;
434  }
435 
436  next:
437  yaml_event_delete(&event);
438  continue;
439 
440  fail:
441  yaml_event_delete(&event);
442  retval = -1;
443  break;
444  }
445 
446  rlevel--;
447  return retval;
448 }
449 
450 /**
451  * \brief Load configuration from a YAML file.
452  *
453  * This function will load a configuration file. On failure -1 will
454  * be returned and it is suggested that the program then exit. Any
455  * errors while loading the configuration file will have already been
456  * logged.
457  *
458  * \param filename Filename of configuration file to load.
459  *
460  * \retval 0 on success, -1 on failure.
461  */
462 int
463 ConfYamlLoadFile(const char *filename)
464 {
465  FILE *infile;
466  yaml_parser_t parser;
467  int ret;
468  ConfNode *root = ConfGetRootNode();
469 
470  if (yaml_parser_initialize(&parser) != 1) {
471  SCLogError("failed to initialize yaml parser.");
472  return -1;
473  }
474 
475  struct stat stat_buf;
476  if (stat(filename, &stat_buf) == 0) {
477  if (stat_buf.st_mode & S_IFDIR) {
478  SCLogError("yaml argument is not a file but a directory: %s. "
479  "Please specify the yaml file in your -c option.",
480  filename);
481  yaml_parser_delete(&parser);
482  return -1;
483  }
484  }
485 
486  // coverity[toctou : FALSE]
487  infile = fopen(filename, "r");
488  if (infile == NULL) {
489  SCLogError("failed to open file: %s: %s", filename, strerror(errno));
490  yaml_parser_delete(&parser);
491  return -1;
492  }
493 
494  if (conf_dirname == NULL) {
495  ConfYamlSetConfDirname(filename);
496  }
497 
498  yaml_parser_set_input_file(&parser, infile);
499  ret = ConfYamlParse(&parser, root, 0, 0, 0);
500  yaml_parser_delete(&parser);
501  fclose(infile);
502 
503  return ret;
504 }
505 
506 /**
507  * \brief Load configuration from a YAML string.
508  */
509 int
510 ConfYamlLoadString(const char *string, size_t len)
511 {
512  ConfNode *root = ConfGetRootNode();
513  yaml_parser_t parser;
514  int ret;
515 
516  if (yaml_parser_initialize(&parser) != 1) {
517  fprintf(stderr, "Failed to initialize yaml parser.\n");
518  exit(EXIT_FAILURE);
519  }
520  yaml_parser_set_input_string(&parser, (const unsigned char *)string, len);
521  ret = ConfYamlParse(&parser, root, 0, 0, 0);
522  yaml_parser_delete(&parser);
523 
524  return ret;
525 }
526 
527 /**
528  * \brief Load configuration from a YAML file, insert in tree at 'prefix'
529  *
530  * This function will load a configuration file and insert it into the
531  * config tree at 'prefix'. This means that if this is called with prefix
532  * "abc" and the file contains a parameter "def", it will be loaded as
533  * "abc.def".
534  *
535  * \param filename Filename of configuration file to load.
536  * \param prefix Name prefix to use.
537  *
538  * \retval 0 on success, -1 on failure.
539  */
540 int
541 ConfYamlLoadFileWithPrefix(const char *filename, const char *prefix)
542 {
543  FILE *infile;
544  yaml_parser_t parser;
545  int ret;
546  ConfNode *root = ConfGetNode(prefix);
547 
548  if (yaml_parser_initialize(&parser) != 1) {
549  SCLogError("failed to initialize yaml parser.");
550  return -1;
551  }
552 
553  struct stat stat_buf;
554  /* coverity[toctou] */
555  if (stat(filename, &stat_buf) == 0) {
556  if (stat_buf.st_mode & S_IFDIR) {
557  SCLogError("yaml argument is not a file but a directory: %s. "
558  "Please specify the yaml file in your -c option.",
559  filename);
560  return -1;
561  }
562  }
563 
564  /* coverity[toctou] */
565  infile = fopen(filename, "r");
566  if (infile == NULL) {
567  SCLogError("failed to open file: %s: %s", filename, strerror(errno));
568  yaml_parser_delete(&parser);
569  return -1;
570  }
571 
572  if (conf_dirname == NULL) {
573  ConfYamlSetConfDirname(filename);
574  }
575 
576  if (root == NULL) {
577  /* if node at 'prefix' doesn't yet exist, add a place holder */
578  ConfSet(prefix, "<prefix root node>");
579  root = ConfGetNode(prefix);
580  if (root == NULL) {
581  fclose(infile);
582  yaml_parser_delete(&parser);
583  return -1;
584  }
585  }
586  yaml_parser_set_input_file(&parser, infile);
587  ret = ConfYamlParse(&parser, root, 0, 0, 0);
588  yaml_parser_delete(&parser);
589  fclose(infile);
590 
591  return ret;
592 }
593 
594 #ifdef UNITTESTS
595 
596 static int
597 ConfYamlSequenceTest(void)
598 {
599  char input[] = "\
600 %YAML 1.1\n\
601 ---\n\
602 rule-files:\n\
603  - netbios.rules\n\
604  - x11.rules\n\
605 \n\
606 default-log-dir: /tmp\n\
607 ";
608 
610  ConfInit();
611 
612  ConfYamlLoadString(input, strlen(input));
613 
614  ConfNode *node;
615  node = ConfGetNode("rule-files");
616  FAIL_IF_NULL(node);
618  FAIL_IF(TAILQ_EMPTY(&node->head));
619  int i = 0;
620  ConfNode *filename;
621  TAILQ_FOREACH(filename, &node->head, next) {
622  if (i == 0) {
623  FAIL_IF(strcmp(filename->val, "netbios.rules") != 0);
624  FAIL_IF(ConfNodeIsSequence(filename));
625  FAIL_IF(filename->is_seq != 0);
626  }
627  else if (i == 1) {
628  FAIL_IF(strcmp(filename->val, "x11.rules") != 0);
629  FAIL_IF(ConfNodeIsSequence(filename));
630  }
631  FAIL_IF(i > 1);
632  i++;
633  }
634 
635  ConfDeInit();
637  PASS;
638 }
639 
640 static int
641 ConfYamlLoggingOutputTest(void)
642 {
643  char input[] = "\
644 %YAML 1.1\n\
645 ---\n\
646 logging:\n\
647  output:\n\
648  - interface: console\n\
649  log-level: error\n\
650  - interface: syslog\n\
651  facility: local4\n\
652  log-level: info\n\
653 ";
654 
656  ConfInit();
657 
658  ConfYamlLoadString(input, strlen(input));
659 
660  ConfNode *outputs;
661  outputs = ConfGetNode("logging.output");
662  FAIL_IF_NULL(outputs);
663 
664  ConfNode *output;
665  ConfNode *output_param;
666 
667  output = TAILQ_FIRST(&outputs->head);
668  FAIL_IF_NULL(output);
669  FAIL_IF(strcmp(output->name, "0") != 0);
670 
671  output_param = TAILQ_FIRST(&output->head);
672  FAIL_IF_NULL(output_param);
673  FAIL_IF(strcmp(output_param->name, "interface") != 0);
674  FAIL_IF(strcmp(output_param->val, "console") != 0);
675 
676  output_param = TAILQ_NEXT(output_param, next);
677  FAIL_IF(strcmp(output_param->name, "log-level") != 0);
678  FAIL_IF(strcmp(output_param->val, "error") != 0);
679 
680  output = TAILQ_NEXT(output, next);
681  FAIL_IF_NULL(output);
682  FAIL_IF(strcmp(output->name, "1") != 0);
683 
684  output_param = TAILQ_FIRST(&output->head);
685  FAIL_IF_NULL(output_param);
686  FAIL_IF(strcmp(output_param->name, "interface") != 0);
687  FAIL_IF(strcmp(output_param->val, "syslog") != 0);
688 
689  output_param = TAILQ_NEXT(output_param, next);
690  FAIL_IF(strcmp(output_param->name, "facility") != 0);
691  FAIL_IF(strcmp(output_param->val, "local4") != 0);
692 
693  output_param = TAILQ_NEXT(output_param, next);
694  FAIL_IF(strcmp(output_param->name, "log-level") != 0);
695  FAIL_IF(strcmp(output_param->val, "info") != 0);
696 
697  ConfDeInit();
699 
700  PASS;
701 }
702 
703 /**
704  * Try to load something that is not a valid YAML file.
705  */
706 static int
707 ConfYamlNonYamlFileTest(void)
708 {
710  ConfInit();
711 
712  FAIL_IF(ConfYamlLoadFile("/etc/passwd") != -1);
713 
714  ConfDeInit();
716 
717  PASS;
718 }
719 
720 static int
721 ConfYamlBadYamlVersionTest(void)
722 {
723  char input[] = "\
724 %YAML 9.9\n\
725 ---\n\
726 logging:\n\
727  output:\n\
728  - interface: console\n\
729  log-level: error\n\
730  - interface: syslog\n\
731  facility: local4\n\
732  log-level: info\n\
733 ";
734 
736  ConfInit();
737 
738  FAIL_IF(ConfYamlLoadString(input, strlen(input)) != -1);
739 
740  ConfDeInit();
742 
743  PASS;
744 }
745 
746 static int
747 ConfYamlSecondLevelSequenceTest(void)
748 {
749  char input[] = "\
750 %YAML 1.1\n\
751 ---\n\
752 libhtp:\n\
753  server-config:\n\
754  - apache-php:\n\
755  address: [\"192.168.1.0/24\"]\n\
756  personality: [\"Apache_2_2\", \"PHP_5_3\"]\n\
757  path-parsing: [\"compress_separators\", \"lowercase\"]\n\
758  - iis-php:\n\
759  address:\n\
760  - 192.168.0.0/24\n\
761 \n\
762  personality:\n\
763  - IIS_7_0\n\
764  - PHP_5_3\n\
765 \n\
766  path-parsing:\n\
767  - compress_separators\n\
768 ";
769 
771  ConfInit();
772 
773  FAIL_IF(ConfYamlLoadString(input, strlen(input)) != 0);
774 
775  ConfNode *outputs;
776  outputs = ConfGetNode("libhtp.server-config");
777  FAIL_IF_NULL(outputs);
778 
779  ConfNode *node;
780 
781  node = TAILQ_FIRST(&outputs->head);
782  FAIL_IF_NULL(node);
783  FAIL_IF(strcmp(node->name, "0") != 0);
784 
785  node = TAILQ_FIRST(&node->head);
786  FAIL_IF_NULL(node);
787  FAIL_IF(strcmp(node->name, "apache-php") != 0);
788 
789  node = ConfNodeLookupChild(node, "address");
790  FAIL_IF_NULL(node);
791 
792  node = TAILQ_FIRST(&node->head);
793  FAIL_IF_NULL(node);
794  FAIL_IF(strcmp(node->name, "0") != 0);
795  FAIL_IF(strcmp(node->val, "192.168.1.0/24") != 0);
796 
797  ConfDeInit();
799 
800  PASS;
801 }
802 
803 /**
804  * Test file inclusion support.
805  */
806 static int
807 ConfYamlFileIncludeTest(void)
808 {
809  FILE *config_file;
810 
811  const char config_filename[] = "ConfYamlFileIncludeTest-config.yaml";
812  const char config_file_contents[] =
813  "%YAML 1.1\n"
814  "---\n"
815  "# Include something at the root level.\n"
816  "include: ConfYamlFileIncludeTest-include.yaml\n"
817  "# Test including under a mapping.\n"
818  "mapping: !include ConfYamlFileIncludeTest-include.yaml\n";
819 
820  const char include_filename[] = "ConfYamlFileIncludeTest-include.yaml";
821  const char include_file_contents[] =
822  "%YAML 1.1\n"
823  "---\n"
824  "host-mode: auto\n"
825  "unix-command:\n"
826  " enabled: no\n";
827 
829  ConfInit();
830 
831  /* Write out the test files. */
832  FAIL_IF_NULL((config_file = fopen(config_filename, "w")));
833  FAIL_IF(fwrite(config_file_contents, strlen(config_file_contents), 1, config_file) != 1);
834  fclose(config_file);
835 
836  FAIL_IF_NULL((config_file = fopen(include_filename, "w")));
837  FAIL_IF(fwrite(include_file_contents, strlen(include_file_contents), 1, config_file) != 1);
838  fclose(config_file);
839 
840  /* Reset conf_dirname. */
841  if (conf_dirname != NULL) {
842  SCFree(conf_dirname);
843  conf_dirname = NULL;
844  }
845 
846  FAIL_IF(ConfYamlLoadFile("ConfYamlFileIncludeTest-config.yaml") != 0);
847 
848  /* Check values that should have been loaded into the root of the
849  * configuration. */
850  ConfNode *node;
851  node = ConfGetNode("host-mode");
852  FAIL_IF_NULL(node);
853  FAIL_IF(strcmp(node->val, "auto") != 0);
854 
855  node = ConfGetNode("unix-command.enabled");
856  FAIL_IF_NULL(node);
857  FAIL_IF(strcmp(node->val, "no") != 0);
858 
859  /* Check for values that were included under a mapping. */
860  node = ConfGetNode("mapping.host-mode");
861  FAIL_IF_NULL(node);
862  FAIL_IF(strcmp(node->val, "auto") != 0);
863 
864  node = ConfGetNode("mapping.unix-command.enabled");
865  FAIL_IF_NULL(node);
866  FAIL_IF(strcmp(node->val, "no") != 0);
867 
868  ConfDeInit();
870 
871  unlink(config_filename);
872  unlink(include_filename);
873 
874  PASS;
875 }
876 
877 /**
878  * Test that a configuration section is overridden but subsequent
879  * occurrences.
880  */
881 static int
882 ConfYamlOverrideTest(void)
883 {
884  char config[] = "%YAML 1.1\n"
885  "---\n"
886  "some-log-dir: /var/log\n"
887  "some-log-dir: /tmp\n"
888  "\n"
889  "parent:\n"
890  " child0:\n"
891  " key: value\n"
892  "parent:\n"
893  " child1:\n"
894  " key: value\n"
895  "vars:\n"
896  " address-groups:\n"
897  " HOME_NET: \"[192.168.0.0/16,10.0.0.0/8,172.16.0.0/12]\"\n"
898  " EXTERNAL_NET: any\n"
899  "vars.address-groups.HOME_NET: \"10.10.10.10/32\"\n";
900  const char *value;
901 
903  ConfInit();
904 
905  FAIL_IF(ConfYamlLoadString(config, strlen(config)) != 0);
906  FAIL_IF_NOT(ConfGet("some-log-dir", &value));
907  FAIL_IF(strcmp(value, "/tmp") != 0);
908 
909  /* Test that parent.child0 does not exist, but child1 does. */
910  FAIL_IF_NOT_NULL(ConfGetNode("parent.child0"));
911  FAIL_IF_NOT(ConfGet("parent.child1.key", &value));
912  FAIL_IF(strcmp(value, "value") != 0);
913 
914  /* First check that vars.address-groups.EXTERNAL_NET has the
915  * expected parent of vars.address-groups and save this
916  * pointer. We want to make sure that the overrided value has the
917  * same parent later on. */
918  ConfNode *vars_address_groups = ConfGetNode("vars.address-groups");
919  FAIL_IF_NULL(vars_address_groups);
920  ConfNode *vars_address_groups_external_net = ConfGetNode("vars.address-groups.EXTERNAL_NET");
921  FAIL_IF_NULL(vars_address_groups_external_net);
922  FAIL_IF_NOT(vars_address_groups_external_net->parent == vars_address_groups);
923 
924  /* Now check that HOME_NET has the overrided value. */
925  ConfNode *vars_address_groups_home_net = ConfGetNode("vars.address-groups.HOME_NET");
926  FAIL_IF_NULL(vars_address_groups_home_net);
927  FAIL_IF(strcmp(vars_address_groups_home_net->val, "10.10.10.10/32") != 0);
928 
929  /* And check that it has the correct parent. */
930  FAIL_IF_NOT(vars_address_groups_home_net->parent == vars_address_groups);
931 
932  ConfDeInit();
934 
935  PASS;
936 }
937 
938 /**
939  * Test that a configuration parameter loaded from YAML doesn't
940  * override a 'final' value that may be set on the command line.
941  */
942 static int
943 ConfYamlOverrideFinalTest(void)
944 {
946  ConfInit();
947 
948  char config[] =
949  "%YAML 1.1\n"
950  "---\n"
951  "default-log-dir: /var/log\n";
952 
953  /* Set the log directory as if it was set on the command line. */
954  FAIL_IF_NOT(ConfSetFinal("default-log-dir", "/tmp"));
955  FAIL_IF(ConfYamlLoadString(config, strlen(config)) != 0);
956 
957  const char *default_log_dir;
958 
959  FAIL_IF_NOT(ConfGet("default-log-dir", &default_log_dir));
960  FAIL_IF(strcmp(default_log_dir, "/tmp") != 0);
961 
962  ConfDeInit();
964 
965  PASS;
966 }
967 
968 static int ConfYamlNull(void)
969 {
971  ConfInit();
972 
973  char config[] = "%YAML 1.1\n"
974  "---\n"
975  "quoted-tilde: \"~\"\n"
976  "unquoted-tilde: ~\n"
977  "quoted-null: \"null\"\n"
978  "unquoted-null: null\n"
979  "quoted-Null: \"Null\"\n"
980  "unquoted-Null: Null\n"
981  "quoted-NULL: \"NULL\"\n"
982  "unquoted-NULL: NULL\n"
983  "empty-quoted: \"\"\n"
984  "empty-unquoted: \n"
985  "list: [\"null\", null, \"Null\", Null, \"NULL\", NULL, \"~\", ~]\n";
986  FAIL_IF(ConfYamlLoadString(config, strlen(config)) != 0);
987 
988  const char *val;
989 
990  FAIL_IF_NOT(ConfGet("quoted-tilde", &val));
991  FAIL_IF_NULL(val);
992  FAIL_IF_NOT(ConfGet("unquoted-tilde", &val));
993  FAIL_IF_NOT_NULL(val);
994 
995  FAIL_IF_NOT(ConfGet("quoted-null", &val));
996  FAIL_IF_NULL(val);
997  FAIL_IF_NOT(ConfGet("unquoted-null", &val));
998  FAIL_IF_NOT_NULL(val);
999 
1000  FAIL_IF_NOT(ConfGet("quoted-Null", &val));
1001  FAIL_IF_NULL(val);
1002  FAIL_IF_NOT(ConfGet("unquoted-Null", &val));
1003  FAIL_IF_NOT_NULL(val);
1004 
1005  FAIL_IF_NOT(ConfGet("quoted-NULL", &val));
1006  FAIL_IF_NULL(val);
1007  FAIL_IF_NOT(ConfGet("unquoted-NULL", &val));
1008  FAIL_IF_NOT_NULL(val);
1009 
1010  FAIL_IF_NOT(ConfGet("empty-quoted", &val));
1011  FAIL_IF_NULL(val);
1012  FAIL_IF_NOT(ConfGet("empty-unquoted", &val));
1013  FAIL_IF_NOT_NULL(val);
1014 
1015  FAIL_IF_NOT(ConfGet("list.0", &val));
1016  FAIL_IF_NULL(val);
1017  FAIL_IF_NOT(ConfGet("list.1", &val));
1018  FAIL_IF_NOT_NULL(val);
1019 
1020  FAIL_IF_NOT(ConfGet("list.2", &val));
1021  FAIL_IF_NULL(val);
1022  FAIL_IF_NOT(ConfGet("list.3", &val));
1023  FAIL_IF_NOT_NULL(val);
1024 
1025  FAIL_IF_NOT(ConfGet("list.4", &val));
1026  FAIL_IF_NULL(val);
1027  FAIL_IF_NOT(ConfGet("list.5", &val));
1028  FAIL_IF_NOT_NULL(val);
1029 
1030  FAIL_IF_NOT(ConfGet("list.6", &val));
1031  FAIL_IF_NULL(val);
1032  FAIL_IF_NOT(ConfGet("list.7", &val));
1033  FAIL_IF_NOT_NULL(val);
1034 
1035  ConfDeInit();
1037 
1038  PASS;
1039 }
1040 
1041 #endif /* UNITTESTS */
1042 
1043 void
1045 {
1046 #ifdef UNITTESTS
1047  UtRegisterTest("ConfYamlSequenceTest", ConfYamlSequenceTest);
1048  UtRegisterTest("ConfYamlLoggingOutputTest", ConfYamlLoggingOutputTest);
1049  UtRegisterTest("ConfYamlNonYamlFileTest", ConfYamlNonYamlFileTest);
1050  UtRegisterTest("ConfYamlBadYamlVersionTest", ConfYamlBadYamlVersionTest);
1051  UtRegisterTest("ConfYamlSecondLevelSequenceTest",
1052  ConfYamlSecondLevelSequenceTest);
1053  UtRegisterTest("ConfYamlFileIncludeTest", ConfYamlFileIncludeTest);
1054  UtRegisterTest("ConfYamlOverrideTest", ConfYamlOverrideTest);
1055  UtRegisterTest("ConfYamlOverrideFinalTest", ConfYamlOverrideFinalTest);
1056  UtRegisterTest("ConfYamlNull", ConfYamlNull);
1057 #endif /* UNITTESTS */
1058 }
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:269
next
struct HtpBodyChunk_ * next
Definition: app-layer-htp.h:0
ConfGetNode
ConfNode * ConfGetNode(const char *name)
Get a ConfNode by name.
Definition: conf.c:181
ConfNodeNew
ConfNode * ConfNodeNew(void)
Allocate a new configuration node.
Definition: conf.c:139
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:541
TAILQ_EMPTY
#define TAILQ_EMPTY(head)
Definition: queue.h:248
TAILQ_FOREACH
#define TAILQ_FOREACH(var, head, field)
Definition: queue.h:252
ConfYamlHandleInclude
int ConfYamlHandleInclude(ConfNode *parent, const char *filename)
Include a file in the configuration.
Definition: conf-yaml-loader.c:116
ConfSetFinal
int ConfSetFinal(const char *name, const char *val)
Set a final configuration value.
Definition: conf.c:303
TAILQ_INSERT_TAIL
#define TAILQ_INSERT_TAIL(head, elm, field)
Definition: queue.h:294
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:335
ConfGetRootNode
ConfNode * ConfGetRootNode(void)
Get the root configuration node.
Definition: conf.c:207
TAILQ_REMOVE
#define TAILQ_REMOVE(head, elm, field)
Definition: queue.h:312
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:250
CONF_KEY
@ CONF_KEY
Definition: conf-yaml-loader.c:55
ConfNodePrune
void ConfNodePrune(ConfNode *node)
Prune a configuration node.
Definition: conf.c:912
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:463
ConfNode_::parent
struct ConfNode_ * parent
Definition: conf.h:41
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:510
SCLogWarning
#define SCLogWarning(...)
Macro used to log WARNING messages.
Definition: util-debug.h:249
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:668
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:224
ConfNodeLookupChild
ConfNode * ConfNodeLookupChild(const ConfNode *node, const char *name)
Lookup a child configuration node by name.
Definition: conf.c:784
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:1044
suricata-common.h
util-path.h
ConfNode_::name
char * name
Definition: conf.h:33
TAILQ_NEXT
#define TAILQ_NEXT(elm, field)
Definition: queue.h:307
PathIsAbsolute
int PathIsAbsolute(const char *path)
Check if a path is absolute.
Definition: util-path.c:44
ConfNodeIsSequence
int ConfNodeIsSequence(const ConfNode *node)
Check if a node is a sequence or node.
Definition: conf.c:944
ConfRestoreContextBackup
void ConfRestoreContextBackup(void)
Restores the backup of the hash_table present in backup_conf_hash back to conf_hash.
Definition: conf.c:680
SCStrdup
#define SCStrdup(s)
Definition: util-mem.h:56
FatalError
#define FatalError(...)
Definition: util-debug.h:502
ConfInit
void ConfInit(void)
Initialize the configuration system.
Definition: conf.c:120
SCLogError
#define SCLogError(...)
Macro used to log ERROR messages.
Definition: util-debug.h:261
SCFree
#define SCFree(p)
Definition: util-mem.h:61
ConfNode_
Definition: conf.h:32
ConfNodeGetNodeOrCreate
ConfNode * ConfNodeGetNodeOrCreate(ConfNode *parent, const char *name, int final)
Helper function to get a node, creating it if it does not exist.
Definition: conf.c:66
ConfDeInit
void ConfDeInit(void)
De-initializes the configuration system.
Definition: conf.c:691
ConfSet
int ConfSet(const char *name, const char *val)
Set a configuration value.
Definition: conf.c:224
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