epan/dissectors/pidl/samr/samr.cnf cnf_dissect_lsa_BinaryString => lsarpc_dissect_str...
[wireshark-sm.git] / epan / dissectors / packet-snort-config.c
blob4ef2514e25b40251ac77e4835c61ee1a9be99b6b
1 /* packet-snort-config.c
3 * Copyright 2016, Martin Mathieson
5 * Wireshark - Network traffic analyzer
6 * By Gerald Combs <gerald@wireshark.org>
7 * Copyright 1998 Gerald Combs
9 * SPDX-License-Identifier: GPL-2.0-or-later
12 #define WS_LOG_DOMAIN "packet-snort-config"
13 #include "config.h"
14 #include <wireshark.h>
16 #include <stdlib.h>
17 #include <string.h>
19 #include <wsutil/file_util.h>
20 #include <wsutil/strtoi.h>
21 #include <wsutil/report_message.h>
23 #include "packet-snort-config.h"
24 #include "ws_attributes.h"
26 /* Forward declaration */
27 static void parse_config_file(SnortConfig_t *snort_config, FILE *config_file_fd, const char *filename, const char *dirname, int recursion_level);
29 /* Skip white space from 'source', return pointer to first non-whitespace char */
30 static const char *skipWhiteSpace(const char *source, int *accumulated_offset)
32 int offset = 0;
34 /* Skip any leading whitespace */
35 while ((source[offset] == ' ') || (source[offset] == '\t')) {
36 offset++;
39 *accumulated_offset += offset;
40 return source + offset;
43 /* Read a token from source, stop when get to end of string or delimiter. */
44 /* - source: input string
45 * - delimiter: char to stop at
46 * - length: out param set to delimiter or end-of-string offset
47 * - accumulated_Length: out param that gets length added to it
48 * - copy: whether or an allocated string should be returned
49 * - returns: requested string. Returns from static buffer when copy is false */
50 static char* read_token(const char* source, char delimeter, int *length, int *accumulated_length, bool copy)
52 static char static_buffer[1024];
53 int offset = 0;
55 const char *source_proper = skipWhiteSpace(source, accumulated_length);
57 while (source_proper[offset] != '\0' && source_proper[offset] != delimeter) {
58 offset++;
61 *length = offset;
62 *accumulated_length += offset;
63 if (copy) {
64 /* Copy into new string */
65 char *new_string = g_strndup(source_proper, offset+1);
66 new_string[offset] = '\0';
67 return new_string;
69 else {
70 /* Return in static buffer */
71 memcpy(&static_buffer, source_proper, offset);
72 static_buffer[offset] = '\0';
73 return static_buffer;
77 /* Add a new content field to the rule */
78 static bool rule_add_content(Rule_t *rule, const char *content_string, bool negated)
80 if (rule->number_contents < MAX_CONTENT_ENTRIES) {
81 content_t *new_content = &(rule->contents[rule->number_contents++]);
82 new_content->str = g_strdup(content_string);
83 new_content->negation = negated;
84 rule->last_added_content = new_content;
85 return true;
87 return false;
90 /* Set the nocase property for a rule */
91 static void rule_set_content_nocase(Rule_t *rule)
93 if (rule->last_added_content) {
94 rule->last_added_content->nocase = true;
98 /* Set the offset property of a content field */
99 static void rule_set_content_offset(Rule_t *rule, int value)
101 if (rule->last_added_content) {
102 rule->last_added_content->offset = value;
103 rule->last_added_content->offset_set = true;
107 /* Set the depth property of a content field */
108 static void rule_set_content_depth(Rule_t *rule, unsigned value)
110 if (rule->last_added_content) {
111 rule->last_added_content->depth = value;
115 /* Set the distance property of a content field */
116 static void rule_set_content_distance(Rule_t *rule, int value)
118 if (rule->last_added_content) {
119 rule->last_added_content->distance = value;
120 rule->last_added_content->distance_set = true;
124 /* Set the distance property of a content field */
125 static void rule_set_content_within(Rule_t *rule, unsigned value)
127 if (rule->last_added_content) {
128 /* Assuming won't be 0... */
129 rule->last_added_content->within = value;
133 /* Set the fastpattern property of a content field */
134 static void rule_set_content_fast_pattern(Rule_t *rule)
136 if (rule->last_added_content) {
137 rule->last_added_content->fastpattern = true;
141 /* Set the rawbytes property of a content field */
142 static void rule_set_content_rawbytes(Rule_t *rule)
144 if (rule->last_added_content) {
145 rule->last_added_content->rawbytes = true;
149 /* Set the http_method property of a content field */
150 static void rule_set_content_http_method(Rule_t *rule)
152 if (rule->last_added_content) {
153 rule->last_added_content->http_method = true;
157 /* Set the http_client property of a content field */
158 static void rule_set_content_http_client_body(Rule_t *rule)
160 if (rule->last_added_content) {
161 rule->last_added_content->http_client_body = true;
165 /* Set the http_cookie property of a content field */
166 static void rule_set_content_http_cookie(Rule_t *rule)
168 if (rule->last_added_content) {
169 rule->last_added_content->http_cookie = true;
173 /* Set the http_UserAgent property of a content field */
174 static void rule_set_content_http_user_agent(Rule_t *rule)
176 if (rule->last_added_content) {
177 rule->last_added_content->http_user_agent = true;
181 /* Add a uricontent field to the rule */
182 static bool rule_add_uricontent(Rule_t *rule, const char *uricontent_string, bool negated)
184 if (rule_add_content(rule, uricontent_string, negated)) {
185 rule->last_added_content->content_type = UriContent;
186 return true;
188 return false;
191 /* This content field now becomes a uricontent after seeing modifier */
192 static void rule_set_http_uri(Rule_t *rule)
194 if (rule->last_added_content != NULL) {
195 rule->last_added_content->content_type = UriContent;
199 /* Add a pcre field to the rule */
200 static bool rule_add_pcre(Rule_t *rule, const char *pcre_string)
202 if (rule_add_content(rule, pcre_string, false)) {
203 rule->last_added_content->content_type = Pcre;
204 return true;
206 return false;
209 /* Set the rule's classtype field */
210 static bool rule_set_classtype(Rule_t *rule, const char *classtype)
212 rule->classtype = g_strdup(classtype);
213 return true;
216 /* Add a reference string to the rule */
217 static void rule_add_reference(Rule_t *rule, const char *reference_string)
219 if (rule->number_references < MAX_REFERENCE_ENTRIES) {
220 rule->references[rule->number_references++] = g_strdup(reference_string);
224 /* Check to see if the ip 'field' corresponds to an entry in the ipvar dictionary.
225 * If it is add entry to rule */
226 static void rule_check_ip_vars(SnortConfig_t *snort_config, Rule_t *rule, char *field)
228 void *original_key = NULL;
229 void *value = NULL;
231 /* Make sure field+1 not NULL. */
232 if (strlen(field) < 2) {
233 return;
236 /* Make sure there is room for another entry */
237 if (rule->relevant_vars.num_ip_vars >= MAX_RULE_IP_VARS) {
238 return;
241 /* TODO: a loop re-looking up the answer until its not just another ipvar! */
242 if (g_hash_table_lookup_extended(snort_config->ipvars, field+1, &original_key, &value)) {
244 rule->relevant_vars.ip_vars[rule->relevant_vars.num_ip_vars].name = (char*)original_key;
245 rule->relevant_vars.ip_vars[rule->relevant_vars.num_ip_vars].value = (char*)value;
247 rule->relevant_vars.num_ip_vars++;
251 /* Check to see if the port 'field' corresponds to an entry in the portvar dictionary.
252 * If it is add entry to rule */
253 static void rule_check_port_vars(SnortConfig_t *snort_config, Rule_t *rule, char *field)
255 void *original_key = NULL;
256 void *value = NULL;
258 /* Make sure field+1 not NULL. */
259 if (strlen(field) < 2) {
260 return;
263 /* Make sure there is room for another entry */
264 if (rule->relevant_vars.num_port_vars >= MAX_RULE_PORT_VARS) {
265 return;
268 /* TODO: a loop re-looking up the answer until its not just another portvar! */
269 if (g_hash_table_lookup_extended(snort_config->portvars, field+1, &original_key, &value)) {
270 rule->relevant_vars.port_vars[rule->relevant_vars.num_port_vars].name = (char*)original_key;
271 rule->relevant_vars.port_vars[rule->relevant_vars.num_port_vars].value = (char*)value;
273 rule->relevant_vars.num_port_vars++;
277 /* Look over the IP addresses and ports, and work out which variables/values are being used */
278 void rule_set_relevant_vars(SnortConfig_t *snort_config, Rule_t *rule)
280 int length;
281 int accumulated_length = 0;
282 char *field;
284 /* No need to do this twice */
285 if (rule->relevant_vars.relevant_vars_set) {
286 return;
289 /* Walk tokens up to the options, and look up ones that are addresses or ports. */
291 /* Skip "alert" */
292 read_token(rule->rule_string+accumulated_length, ' ', &length, &accumulated_length, false);
294 /* Skip protocol. */
295 read_token(rule->rule_string+accumulated_length, ' ', &length, &accumulated_length, false);
297 /* Read source address */
298 field = read_token(rule->rule_string+accumulated_length, ' ', &length, &accumulated_length, false);
299 rule_check_ip_vars(snort_config, rule, field);
301 /* Read source port */
302 field = read_token(rule->rule_string+accumulated_length, ' ', &length, &accumulated_length, false);
303 rule_check_port_vars(snort_config, rule, field);
305 /* Read direction */
306 read_token(rule->rule_string+accumulated_length, ' ', &length, &accumulated_length, false);
308 /* Dest address */
309 field = read_token(rule->rule_string+accumulated_length, ' ', &length, &accumulated_length, false);
310 rule_check_ip_vars(snort_config, rule, field);
312 /* Dest port */
313 field = read_token(rule->rule_string+accumulated_length, ' ', &length, &accumulated_length, false);
314 rule_check_port_vars(snort_config, rule, field);
316 /* Set flag so won't do again for this rule */
317 rule->relevant_vars.relevant_vars_set = true;
321 typedef enum vartype_e { var, ipvar, portvar, unknownvar } vartype_e;
323 /* Look for a "var", "ipvar" or "portvar" entry in this line */
324 static bool parse_variables_line(SnortConfig_t *snort_config, const char *line)
326 vartype_e var_type = unknownvar;
328 const char* variable_type;
329 char * variable_name;
330 char * value;
332 int length;
333 int accumulated_length = 0;
335 /* Get variable type */
336 variable_type = read_token(line, ' ', &length, &accumulated_length, false);
337 if (variable_type == NULL) {
338 return false;
341 if (strncmp(variable_type, "var", 3) == 0) {
342 var_type = var;
344 else if (strncmp(variable_type, "ipvar", 5) == 0) {
345 var_type = ipvar;
347 else if (strncmp(variable_type, "portvar", 7) == 0) {
348 var_type = portvar;
350 else {
351 return false;
354 /* Get variable name */
355 variable_name = read_token(line+ accumulated_length, ' ', &length, &accumulated_length, true);
356 if (variable_name == NULL) {
357 return false;
360 /* Now value */
361 value = read_token(line + accumulated_length, ' ', &length, &accumulated_length, true);
362 if (value == NULL) {
363 return false;
366 /* Add (name->value) to table according to variable type. */
367 switch (var_type) {
368 case var:
369 if (strcmp(variable_name, "RULE_PATH") == 0) {
370 /* This can be relative or absolute. */
371 snort_config->rule_path = value;
372 snort_config->rule_path_is_absolute = g_path_is_absolute(value);
373 ws_debug("rule_path set to %s (is_absolute=%d)",
374 snort_config->rule_path, snort_config->rule_path_is_absolute);
376 g_hash_table_insert(snort_config->vars, variable_name, value);
377 break;
378 case ipvar:
379 g_hash_table_insert(snort_config->ipvars, variable_name, value);
380 break;
381 case portvar:
382 g_hash_table_insert(snort_config->portvars, variable_name, value);
383 break;
385 default:
386 return false;
389 return false;
392 /* Hash function for where key is a string. Just add up the value of each character and return that.. */
393 static unsigned string_hash(const void *key)
395 unsigned total=0, n=0;
396 const char *key_string = (const char *)key;
397 char c = key_string[n];
399 while (c != '\0') {
400 total += (int)c;
401 c = key_string[++n];
403 return total;
406 /* Comparison function for where key is a string. Simple comparison using strcmp() */
407 static gboolean string_equal(const void *a, const void *b)
409 const char *stringa = (const char*)a;
410 const char *stringb = (const char*)b;
412 return (strcmp(stringa, stringb) == 0);
415 /* Process a line that configures a reference line (invariably from 'reference.config') */
416 static bool parse_references_prefix_file_line(SnortConfig_t *snort_config, const char *line)
418 char *prefix_name, *prefix_value;
419 int length=0, accumulated_length=0;
420 int n;
422 if (strncmp(line, "config reference: ", 18) != 0) {
423 return false;
426 /* Read the prefix and value */
427 const char *source = line+18;
428 prefix_name = read_token(source, ' ', &length, &accumulated_length, true);
429 /* Store all name chars in lower case. */
430 for (n=0; prefix_name[n] != '\0'; n++) {
431 prefix_name[n] = g_ascii_tolower(prefix_name[n]);
434 prefix_value = read_token(source+accumulated_length, ' ', &length, &accumulated_length, true);
436 /* Add entry into table */
437 g_hash_table_insert(snort_config->references_prefixes, prefix_name, prefix_value);
439 return false;
442 /* Try to expand the reference using the prefixes stored in the config */
443 char *expand_reference(SnortConfig_t *snort_config, char *reference)
445 static char expanded_reference[512];
446 int length = (int)strlen(reference);
447 int accumulated_length = 0;
449 /* Extract up to ',', then substitute prefix! */
450 ws_debug("expand_reference(%s)", reference);
451 char *prefix = read_token(reference, ',', &length, &accumulated_length, false);
453 if (*prefix != '\0') {
454 /* Convert to lowercase before lookup */
455 unsigned n;
456 for (n=0; prefix[n] != '\0'; n++) {
457 prefix[n] = g_ascii_tolower(prefix[n]);
460 /* Look up prefix in table. */
461 const char* prefix_replacement;
462 prefix_replacement = (char*)g_hash_table_lookup(snort_config->references_prefixes, prefix);
464 /* Append prefix and remainder, and return!!!! */
465 if (prefix_replacement) {
466 snprintf(expanded_reference, 512, "%s%s", prefix_replacement, reference+length+1);
467 return expanded_reference;
469 else {
470 /* Just return the original reference */
471 return reference;
475 return "ERROR: Reference didn't contain prefix and ','!";
478 /* The rule has been matched with an alert, so update global config stats */
479 void rule_set_alert(SnortConfig_t *snort_config, Rule_t *rule,
480 unsigned *global_match_number,
481 unsigned *rule_match_number)
483 snort_config->stat_alerts_detected++;
484 *global_match_number = snort_config->stat_alerts_detected;
485 if (rule != NULL) {
486 *rule_match_number = ++rule->matches_seen;
492 /* Delete an individual entry from a string table. */
493 static gboolean delete_string_entry(void *key,
494 void *value,
495 void *user_data _U_)
497 char *key_string = (char*)key;
498 char *value_string = (char*)value;
500 g_free(key_string);
501 g_free(value_string);
503 return TRUE;
506 /* See if this is an include line, if it is open the file and call parse_config_file() */
507 // NOLINTNEXTLINE(misc-no-recursion)
508 static bool parse_include_file(SnortConfig_t *snort_config, const char *line, const char *config_directory, int recursion_level)
510 int length;
511 int accumulated_length = 0;
512 char *include_filename;
514 /* Look for "include " */
515 const char *include_token = read_token(line, ' ', &length, &accumulated_length, false);
516 if (strlen(include_token) == 0) {
517 return false;
519 if (strncmp(include_token, "include", 7) != 0) {
520 return false;
523 /* Read the filename */
524 include_filename = read_token(line+accumulated_length, ' ', &length, &accumulated_length, false);
525 if (*include_filename != '\0') {
526 FILE *new_config_fd;
527 char *substituted_filename;
528 bool is_rule_file = false;
530 /* May need to substitute variables into include path. */
531 if (strncmp(include_filename, "$RULE_PATH", 10) == 0) {
532 /* Write rule path variable value */
533 /* Don't assume $RULE_PATH will end in a file separator */
534 if (snort_config->rule_path_is_absolute) {
535 /* Rule path is absolute, so it can go at start */
536 substituted_filename = g_build_path(G_DIR_SEPARATOR_S,
537 snort_config->rule_path,
538 include_filename + 11,
539 NULL);
541 else {
542 /* Rule path is relative to config directory, so it goes first */
543 substituted_filename = g_build_path(G_DIR_SEPARATOR_S,
544 config_directory,
545 snort_config->rule_path,
546 include_filename + 11,
547 NULL);
549 is_rule_file = true;
551 else {
552 /* No $RULE_PATH, just use directory and filename */
553 /* But may not even need directory if included_folder is absolute! */
554 if (!g_path_is_absolute(include_filename)) {
555 substituted_filename = g_build_path(G_DIR_SEPARATOR_S,
556 config_directory, include_filename, NULL);
558 else {
559 substituted_filename = g_strdup(include_filename);
563 /* Try to open the file. */
564 new_config_fd = ws_fopen(substituted_filename, "r");
565 if (new_config_fd == NULL) {
566 ws_debug("Failed to open config file %s", substituted_filename);
567 report_failure("Snort dissector: Failed to open config file %s\n", substituted_filename);
568 g_free(substituted_filename);
569 return false;
572 /* Parse the file */
573 if (is_rule_file) {
574 snort_config->stat_rules_files++;
576 parse_config_file(snort_config, new_config_fd, substituted_filename, config_directory, recursion_level + 1);
577 g_free(substituted_filename);
579 /* Close the file */
580 fclose(new_config_fd);
582 return true;
584 return false;
587 /* Process an individual option - i.e. the elements found between '(' and ')' */
588 static void process_rule_option(Rule_t *rule, char *options, int option_start_offset, int options_end_offset, int colon_offset)
590 static char name[1024], value[1024];
591 name[0] = '\0';
592 value[0] = '\0';
593 int value_length = 0;
594 uint32_t value32 = 0;
595 int spaces_after_colon = 0;
597 if (colon_offset != 0) {
598 /* Name and value */
599 (void) g_strlcpy(name, options+option_start_offset, colon_offset-option_start_offset);
600 if (options[colon_offset] == ' ') {
601 spaces_after_colon = 1;
603 (void) g_strlcpy(value, options+colon_offset+spaces_after_colon, options_end_offset-spaces_after_colon-colon_offset);
604 value_length = (int)strlen(value);
606 else {
607 /* Just name */
608 (void) g_strlcpy(name, options+option_start_offset, options_end_offset-option_start_offset);
611 /* Some rule options expect a number, parse it now. Note that any space
612 * after the value will currently result in the number being ignored. */
613 ws_strtoi32(value, NULL, &value32);
615 /* Think this is space at end of all options - don't compare with option names */
616 if (name[0] == '\0') {
617 return;
620 /* Process the rule options that we are interested in */
621 if (strcmp(name, "msg") == 0) {
622 rule->msg = g_strdup(value);
624 else if (strcmp(name, "sid") == 0) {
625 rule->sid = value32;
627 else if (strcmp(name, "rev") == 0) {
628 rule->rev = value32;
630 else if (strcmp(name, "content") == 0) {
631 int value_start = 0;
633 if (value_length < 3) {
634 return;
637 /* Need to trim off " ", but first check for ! */
638 if (value[0] == '!') {
639 value_start = 1;
640 if (value_length < 4) {
641 /* i.e. also need quotes + at least one character */
642 return;
646 value[options_end_offset-colon_offset-spaces_after_colon-2] = '\0';
647 rule_add_content(rule, value+value_start+1, value_start == 1);
649 else if (strcmp(name, "uricontent") == 0) {
650 int value_start = 0;
652 if (value_length < 3) {
653 return;
656 /* Need to trim off " ", but first check for ! */
657 if (value[0] == '!') {
658 value_start = 1;
659 if (value_length < 4) {
660 return;
664 value[options_end_offset-colon_offset-spaces_after_colon-2] = '\0';
665 rule_add_uricontent(rule, value+value_start+1, value_start == 1);
667 else if (strcmp(name, "http_uri") == 0) {
668 rule_set_http_uri(rule);
670 else if (strcmp(name, "pcre") == 0) {
671 int value_start = 0;
673 /* Need at least opening and closing / */
674 if (value_length < 3) {
675 return;
678 /* Not expecting negation (!)... */
680 value[options_end_offset-colon_offset-spaces_after_colon-2] = '\0';
681 rule_add_pcre(rule, value+value_start+1);
683 else if (strcmp(name, "nocase") == 0) {
684 rule_set_content_nocase(rule);
686 else if (strcmp(name, "offset") == 0) {
687 rule_set_content_offset(rule, value32);
689 else if (strcmp(name, "depth") == 0) {
690 rule_set_content_depth(rule, value32);
692 else if (strcmp(name, "within") == 0) {
693 rule_set_content_within(rule, value32);
695 else if (strcmp(name, "distance") == 0) {
696 rule_set_content_distance(rule, value32);
698 else if (strcmp(name, "fast_pattern") == 0) {
699 rule_set_content_fast_pattern(rule);
701 else if (strcmp(name, "http_method") == 0) {
702 rule_set_content_http_method(rule);
704 else if (strcmp(name, "http_client_body") == 0) {
705 rule_set_content_http_client_body(rule);
707 else if (strcmp(name, "http_cookie") == 0) {
708 rule_set_content_http_cookie(rule);
710 else if (strcmp(name, "http_user_agent") == 0) {
711 rule_set_content_http_user_agent(rule);
713 else if (strcmp(name, "rawbytes") == 0) {
714 rule_set_content_rawbytes(rule);
716 else if (strcmp(name, "classtype") == 0) {
717 rule_set_classtype(rule, value);
719 else if (strcmp(name, "reference") == 0) {
720 rule_add_reference(rule, value);
722 else {
723 /* Ignore an option we don't currently handle */
727 /* Parse a Snort alert, return true if successful */
728 static bool parse_rule(SnortConfig_t *snort_config, char *line, const char *filename, int line_number, int line_length)
730 const char* options_start;
731 char *options;
732 bool in_quotes = false;
733 int options_start_index = 0, options_index = 0, colon_offset = 0;
734 char c;
735 int length = 0; /* CID 1398227 (bogus - read_token() always sets it) */
736 Rule_t *rule = NULL;
738 /* Rule will begin with alert */
739 if (strncmp(line, "alert ", 6) != 0) {
740 return false;
743 /* Allocate the rule itself */
744 rule = g_new(Rule_t, 1);
746 ws_debug("looks like a rule: %s", line);
747 memset(rule, 0, sizeof(Rule_t));
749 rule->rule_string = g_strdup(line);
750 rule->file = g_strdup(filename);
751 rule->line_number = line_number;
753 /* Next token is the protocol */
754 rule->protocol = read_token(line+6, ' ', &length, &length, true);
756 /* Find start of options. */
757 options_start = strstr(line, "(");
758 if (options_start == NULL) {
759 ws_debug("start of options not found");
760 g_free(rule);
761 return false;
763 options_index = (int)(options_start-line) + 1;
765 /* To make parsing simpler, replace final ')' with ';' */
766 if (line[line_length-1] != ')') {
767 g_free(rule);
768 return false;
770 else {
771 line[line_length-1] = ';';
774 /* Skip any spaces before next option */
775 while (line[options_index] == ' ') options_index++;
777 /* Now look for next ';', process one option at a time */
778 options = &line[options_index];
779 options_index = 0;
781 while ((c = options[options_index++])) {
782 /* Keep track of whether inside quotes */
783 if (c == '"') {
784 in_quotes = !in_quotes;
786 /* Ignore ';' while inside quotes */
787 if (!in_quotes) {
788 if (c == ':') {
789 colon_offset = options_index;
791 if (c == ';') {
792 /* End of current option - add to rule. */
793 process_rule_option(rule, options, options_start_index, options_index, colon_offset);
795 /* Skip any spaces before next option */
796 while (options[options_index] == ' ') options_index++;
798 /* Next rule will start here */
799 options_start_index = options_index;
800 colon_offset = 0;
801 in_quotes = false;
806 /* Add rule to map of rules. */
807 g_hash_table_insert(snort_config->rules, GUINT_TO_POINTER((unsigned)rule->sid), rule);
808 ws_debug("Snort rule with SID=%u added to table", rule->sid);
810 return true;
813 /* Delete an individual rule */
814 static gboolean delete_rule(void * key _U_,
815 void * value,
816 void * user_data _U_)
818 Rule_t *rule = (Rule_t*)value;
819 unsigned int n;
821 /* Delete strings on heap. */
822 g_free(rule->rule_string);
823 g_free(rule->file);
824 g_free(rule->msg);
825 g_free(rule->classtype);
826 g_free(rule->protocol);
828 for (n=0; n < rule->number_contents; n++) {
829 g_free(rule->contents[n].str);
830 g_free(rule->contents[n].translated_str);
833 for (n=0; n < rule->number_references; n++) {
834 g_free(rule->references[n]);
837 ws_debug("Freeing rule at :%p", rule);
838 g_free(rule);
839 return TRUE;
843 /* Parse this file, adding details to snort_config. */
844 /* N.B. using recursion_level to limit stack depth. */
845 #define MAX_CONFIG_FILE_RECURSE_DEPTH 8
846 // NOLINTNEXTLINE(misc-no-recursion)
847 static void parse_config_file(SnortConfig_t *snort_config, FILE *config_file_fd,
848 const char *filename, const char *dirname, int recursion_level)
850 #define MAX_LINE_LENGTH 4096
851 char line[MAX_LINE_LENGTH];
852 int line_number = 0;
854 ws_debug("parse_config_file(filename=%s, recursion_level=%d)", filename, recursion_level);
856 if (recursion_level > MAX_CONFIG_FILE_RECURSE_DEPTH) {
857 return;
860 /* Read each line of the file in turn, and see if we want any info from it. */
861 while (fgets(line, MAX_LINE_LENGTH, config_file_fd)) {
863 int line_length;
864 ++line_number;
866 /* Nothing interesting to parse */
867 if ((line[0] == '\0') || (line[0] == '#')) {
868 continue;
871 /* Trim newline from end */
872 line_length = (int)strlen(line);
873 while (line_length && ((line[line_length - 1] == '\n') || (line[line_length - 1] == '\r'))) {
874 --line_length;
876 line[line_length] = '\0';
877 if (line_length == 0) {
878 continue;
881 /* Offer line to the various parsing functions. Could optimise order.. */
882 if (parse_variables_line(snort_config, line)) {
883 continue;
885 if (parse_references_prefix_file_line(snort_config, line)) {
886 continue;
888 if (parse_include_file(snort_config, line, dirname, recursion_level)) {
889 continue;
891 if (parse_rule(snort_config, line, filename, line_number, line_length)) {
892 snort_config->stat_rules++;
893 continue;
900 /* Create the global ConfigParser */
901 void create_config(SnortConfig_t **snort_config, const char *snort_config_file)
903 char* dirname;
904 char* basename;
905 FILE *config_file_fd;
907 ws_debug("create_config (%s)", snort_config_file);
909 *snort_config = g_new(SnortConfig_t, 1);
910 memset(*snort_config, 0, sizeof(SnortConfig_t));
912 /* Create rule table */
913 (*snort_config)->rules = g_hash_table_new(g_direct_hash, g_direct_equal);
915 /* Create reference prefix table */
916 (*snort_config)->references_prefixes = g_hash_table_new(string_hash, string_equal);
918 /* Vars tables */
919 (*snort_config)->vars = g_hash_table_new(string_hash, string_equal);
920 (*snort_config)->ipvars = g_hash_table_new(string_hash, string_equal);
921 (*snort_config)->portvars = g_hash_table_new(string_hash, string_equal);
923 /* Extract separate directory and filename. */
924 dirname = g_path_get_dirname(snort_config_file);
925 basename = g_path_get_basename(snort_config_file);
927 /* Attempt to open the config file */
928 config_file_fd = ws_fopen(snort_config_file, "r");
929 if (config_file_fd == NULL) {
930 ws_debug("Failed to open config file %s", snort_config_file);
931 report_failure("Snort dissector: Failed to open config file %s\n", snort_config_file);
933 else {
934 /* Start parsing from the top-level config file. */
935 parse_config_file(*snort_config, config_file_fd, snort_config_file, dirname, 1 /* recursion level */);
936 fclose(config_file_fd);
939 g_free(dirname);
940 g_free(basename);
944 /* Delete the entire config */
945 void delete_config(SnortConfig_t **snort_config)
947 ws_debug("delete_config()");
949 /* Iterate over all rules, freeing each one! */
950 g_hash_table_foreach_remove((*snort_config)->rules, delete_rule, NULL);
951 g_hash_table_destroy((*snort_config)->rules);
953 /* References table */
954 g_hash_table_foreach_remove((*snort_config)->references_prefixes, delete_string_entry, NULL);
955 g_hash_table_destroy((*snort_config)->references_prefixes);
957 /* Free up variable tables */
958 g_hash_table_foreach_remove((*snort_config)->vars, delete_string_entry, NULL);
959 g_hash_table_destroy((*snort_config)->vars);
960 g_hash_table_foreach_remove((*snort_config)->ipvars, delete_string_entry, NULL);
961 g_hash_table_destroy((*snort_config)->ipvars);
962 g_hash_table_foreach_remove((*snort_config)->portvars, delete_string_entry, NULL);
963 g_hash_table_destroy((*snort_config)->portvars);
965 g_free(*snort_config);
967 *snort_config = NULL;
970 /* Look for a rule corresponding to the given SID */
971 Rule_t *get_rule(SnortConfig_t *snort_config, uint32_t sid)
973 if ((snort_config == NULL) || (snort_config->rules == NULL)) {
974 return NULL;
976 else {
977 return (Rule_t*)g_hash_table_lookup(snort_config->rules, GUINT_TO_POINTER(sid));
981 /* Fetch some statistics. */
982 void get_global_rule_stats(SnortConfig_t *snort_config, unsigned int sid,
983 unsigned int *number_rules_files, unsigned int *number_rules,
984 unsigned int *alerts_detected, unsigned int *this_rule_alerts_detected)
986 *number_rules_files = snort_config->stat_rules_files;
987 *number_rules = snort_config->stat_rules;
988 *alerts_detected = snort_config->stat_alerts_detected;
989 const Rule_t *rule;
991 /* Look up rule and get current/total matches */
992 rule = get_rule(snort_config, sid);
993 if (rule) {
994 *this_rule_alerts_detected = rule->matches_seen;
996 else {
997 *this_rule_alerts_detected = 0;
1001 /* Reset stats on individual rule */
1002 static void reset_rule_stats(void * key _U_,
1003 void * value,
1004 void * user_data _U_)
1006 Rule_t *rule = (Rule_t*)value;
1007 rule->matches_seen = 0;
1010 /* Reset stats on all rules */
1011 void reset_global_rule_stats(SnortConfig_t *snort_config)
1013 /* Reset global stats */
1014 if (snort_config == NULL) {
1015 return;
1017 snort_config->stat_alerts_detected = 0;
1019 /* Iterate over all rules, resetting the stats of each */
1020 g_hash_table_foreach(snort_config->rules, reset_rule_stats, NULL);
1024 /*************************************************************************************/
1025 /* Dealing with content fields and trying to find where it matches within the packet */
1026 /* Parse content strings to interpret binary and escaped characters. Do this */
1027 /* so we can look for in frame using memcmp(). */
1028 static unsigned char content_get_nibble_value(char c)
1030 static unsigned char values[256];
1031 static bool values_set = false;
1033 if (!values_set) {
1034 /* Set table once and for all */
1035 unsigned char ch;
1036 for (ch='a'; ch <= 'f'; ch++) {
1037 values[ch] = 0xa + (ch-'a');
1039 for (ch='A'; ch <= 'F'; ch++) {
1040 values[ch] = 0xa + (ch-'A');
1042 for (ch='0'; ch <= '9'; ch++) {
1043 values[ch] = (ch-'0');
1045 values_set = true;
1048 return values[(unsigned char)c];
1051 /* Go through string, converting hex digits into uint8_t, and removing escape characters. */
1052 unsigned content_convert_to_binary(content_t *content)
1054 int output_idx = 0;
1055 bool in_binary_mode = false; /* Are we in a binary region of the string? */
1056 bool have_one_nibble = false; /* Do we have the first nibble of the pair needed to make a byte? */
1057 unsigned char one_nibble = 0; /* Value of first nibble if we have it */
1058 char c;
1059 int n;
1060 bool have_backslash = false;
1061 static char binary_str[1024];
1063 /* Just return length if have previously translated in binary string. */
1064 if (content->translated) {
1065 return content->translated_length;
1068 /* Walk over each character, work out what needs to be written into output */
1069 for (n=0; content->str[n] != '\0'; n++) {
1070 c = content->str[n];
1071 if (c == '|') {
1072 /* Flip binary mode */
1073 in_binary_mode = !in_binary_mode;
1074 continue;
1077 if (!in_binary_mode) {
1078 /* Not binary mode. Copying characters into output buffer, but watching out for escaped chars. */
1079 if (!have_backslash) {
1080 if (c == '\\') {
1081 /* Just note that we have a backslash */
1082 have_backslash = true;
1083 continue;
1085 else {
1086 /* Just copy the character straight into output. */
1087 binary_str[output_idx++] = (unsigned char)c;
1090 else {
1091 /* Currently have a backslash. Reset flag. */
1092 have_backslash = 0;
1093 /* Just copy the character into output. Really, the only characters that should be escaped
1094 are ';' and '\' and '"' */
1095 binary_str[output_idx++] = (unsigned char)c;
1098 else {
1099 /* Binary mode. Handle pairs of hex digits and translate into uint8_t */
1100 if (c == ' ') {
1101 /* Ignoring inside binary mode */
1102 continue;
1104 else {
1105 unsigned char nibble = content_get_nibble_value(c);
1106 if (!have_one_nibble) {
1107 /* Store first nibble of a pair */
1108 one_nibble = nibble;
1109 have_one_nibble = true;
1111 else {
1112 /* Combine both nibbles into a byte */
1113 binary_str[output_idx++] = (one_nibble << 4) + nibble;
1114 /* Reset flag - looking for new pair of nibbles */
1115 have_one_nibble = false;
1121 /* Store result for next time. */
1122 content->translated_str = (unsigned char*)g_malloc(output_idx+1);
1123 memcpy(content->translated_str, binary_str, output_idx+1);
1124 content->translated = true;
1125 content->translated_length = output_idx;
1127 return output_idx;
1130 /* In order to use glib's regex library, need to trim
1131 '/' delimiters and any modifiers from the end of the string */
1132 bool content_convert_pcre_for_regex(content_t *content)
1134 unsigned pcre_length, i, end_delimiter_offset = 0;
1136 /* Return if already converted */
1137 if (content->translated_str) {
1138 return true;
1141 pcre_length = (unsigned)strlen(content->str);
1143 /* Start with content->str */
1144 if (pcre_length < 3) {
1145 /* Can't be valid. Expect /regex/[modifiers] */
1146 return false;
1149 if (pcre_length >= 512) {
1150 /* Have seen regex library crash on very long expressions
1151 * (830 bytes) as seen in SID=2019326, REV=6 */
1152 return false;
1155 /* Verify that string starts with / */
1156 if (content->str[0] != '/') {
1157 return false;
1160 /* Next, look for closing / near end of string */
1161 for (i=pcre_length-1; i > 2; i--) {
1162 if (content->str[i] == '/') {
1163 end_delimiter_offset = i;
1164 break;
1166 else {
1167 switch (content->str[i]) {
1168 case 'i':
1169 content->pcre_case_insensitive = true;
1170 break;
1171 case 's':
1172 content->pcre_dot_includes_newline = true;
1173 break;
1174 case 'B':
1175 content->pcre_raw = true;
1176 break;
1177 case 'm':
1178 content->pcre_multiline = true;
1179 break;
1181 default:
1182 /* TODO: handle other modifiers that will get seen? */
1183 /* N.B. 'U' (match in decoded URI buffers) can't be handled, so don't store in flag. */
1184 /* N.B. not sure if/how to handle 'R' (effectively distance:0) */
1185 ws_debug("Unhandled pcre modifier '%c'", content->str[i]);
1186 break;
1190 if (end_delimiter_offset == 0) {
1191 /* Didn't find it */
1192 return false;
1195 /* Store result for next time. */
1196 content->translated_str = (unsigned char*)g_malloc(end_delimiter_offset);
1197 memcpy(content->translated_str, content->str+1, end_delimiter_offset - 1);
1198 content->translated_str[end_delimiter_offset-1] = '\0';
1199 content->translated = true;
1200 content->translated_length = end_delimiter_offset - 1;
1202 return true;
1206 * Editor modelines - https://www.wireshark.org/tools/modelines.html
1208 * Local variables:
1209 * c-basic-offset: 4
1210 * tab-width: 8
1211 * indent-tabs-mode: nil
1212 * End:
1214 * vi: set shiftwidth=4 tabstop=8 expandtab:
1215 * :indentSize=4:tabSize=8:noTabs=true: