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