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