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