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