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
19 #include <wsutil/file_util.h>
20 #include <wsutil/strtoi.h>
22 #include "snort-config.h"
26 const char* g_file_separator = "/";
28 const char* g_file_separator = "\\";
31 /* Forward declaration */
32 static void parse_config_file(SnortConfig_t *snort_config, FILE *config_file_fd, const char *filename, const char *dirname, int recursion_level);
34 /* Skip white space from 'source', return pointer to first non-whitespace char */
35 static char *skipWhiteSpace(char *source, int *accumulated_offset)
39 /* Skip any leading whitespace */
40 while (source[offset] != '\0' && source[offset] == ' ') {
44 *accumulated_offset += offset;
45 return source + offset;
48 /* Read a token from source, stop when get to end of string or delimiter. */
49 /* - source: input string
50 * - delimiter: char to stop at
51 * - length: out param set to delimiter or end-of-string offset
52 * - accumulated_Length: out param that gets length added to it
53 * - copy: whether or an allocated string should be returned
54 * - returns: requested string. Returns from static buffer when copy is FALSE */
55 static char* read_token(char* source, char delimeter, int *length, int *accumulated_length, gboolean copy)
57 static char static_buffer[1024];
60 char *source_proper = skipWhiteSpace(source, accumulated_length);
62 while (source_proper[offset] != '\0' && source_proper[offset] != delimeter) {
67 *accumulated_length += offset;
69 /* Copy into new string */
70 char *new_string = g_strndup(source_proper, offset+1);
71 new_string[offset] = '\0';
75 /* Return in static buffer */
76 memcpy(&static_buffer, source_proper, offset);
77 static_buffer[offset] = '\0';
82 /* Add a new content field to the rule */
83 static gboolean rule_add_content(Rule_t *rule, const char *content_string, gboolean negated)
85 if (rule->number_contents < MAX_CONTENT_ENTRIES) {
86 content_t *new_content = &(rule->contents[rule->number_contents++]);
87 new_content->str = g_strdup(content_string);
88 new_content->negation = negated;
89 rule->last_added_content = new_content;
95 /* Set the nocase property for a rule */
96 static void rule_set_content_nocase(Rule_t *rule)
98 if (rule->last_added_content) {
99 rule->last_added_content->nocase = TRUE;
103 /* Set the offset property of a content field */
104 static void rule_set_content_offset(Rule_t *rule, gint value)
106 if (rule->last_added_content) {
107 rule->last_added_content->offset = value;
108 rule->last_added_content->offset_set = TRUE;
112 /* Set the depth property of a content field */
113 static void rule_set_content_depth(Rule_t *rule, guint value)
115 if (rule->last_added_content) {
116 rule->last_added_content->depth = value;
120 /* Set the distance property of a content field */
121 static void rule_set_content_distance(Rule_t *rule, gint value)
123 if (rule->last_added_content) {
124 rule->last_added_content->distance = value;
125 rule->last_added_content->distance_set = TRUE;
129 /* Set the distance property of a content field */
130 static void rule_set_content_within(Rule_t *rule, guint value)
132 if (rule->last_added_content) {
133 /* Assuming won't be 0... */
134 rule->last_added_content->within = value;
138 /* Set the fastpattern property of a content field */
139 static void rule_set_content_fast_pattern(Rule_t *rule)
141 if (rule->last_added_content) {
142 rule->last_added_content->fastpattern = TRUE;
146 /* Set the rawbytes property of a content field */
147 static void rule_set_content_rawbytes(Rule_t *rule)
149 if (rule->last_added_content) {
150 rule->last_added_content->rawbytes = TRUE;
154 /* Set the http_method property of a content field */
155 static void rule_set_content_http_method(Rule_t *rule)
157 if (rule->last_added_content) {
158 rule->last_added_content->http_method = TRUE;
162 /* Set the http_client property of a content field */
163 static void rule_set_content_http_client_body(Rule_t *rule)
165 if (rule->last_added_content) {
166 rule->last_added_content->http_client_body = TRUE;
170 /* Set the http_cookie property of a content field */
171 static void rule_set_content_http_cookie(Rule_t *rule)
173 if (rule->last_added_content) {
174 rule->last_added_content->http_cookie = TRUE;
178 /* Set the http_UserAgent property of a content field */
179 static void rule_set_content_http_user_agent(Rule_t *rule)
181 if (rule->last_added_content) {
182 rule->last_added_content->http_user_agent = TRUE;
186 /* Add a uricontent field to the rule */
187 static gboolean rule_add_uricontent(Rule_t *rule, const char *uricontent_string, gboolean negated)
189 if (rule_add_content(rule, uricontent_string, negated)) {
190 rule->last_added_content->content_type = UriContent;
196 /* This content field now becomes a uricontent after seeing modifier */
197 static void rule_set_http_uri(Rule_t *rule)
199 if (rule->last_added_content != NULL) {
200 rule->last_added_content->content_type = UriContent;
204 /* Add a pcre field to the rule */
205 static gboolean rule_add_pcre(Rule_t *rule, const char *pcre_string)
207 if (rule_add_content(rule, pcre_string, FALSE)) {
208 rule->last_added_content->content_type = Pcre;
214 /* Set the rule's classtype field */
215 static gboolean rule_set_classtype(Rule_t *rule, const char *classtype)
217 rule->classtype = g_strdup(classtype);
221 /* Add a reference string to the rule */
222 static void rule_add_reference(Rule_t *rule, const char *reference_string)
224 if (rule->number_references < MAX_REFERENCE_ENTRIES) {
225 rule->references[rule->number_references++] = g_strdup(reference_string);
229 /* Check to see if the ip 'field' corresponds to an entry in the ipvar dictionary.
230 * If it is add entry to rule */
231 static void rule_check_ip_vars(SnortConfig_t *snort_config, Rule_t *rule, char *field)
233 gpointer original_key = NULL;
234 gpointer value = NULL;
236 /* Make sure field+1 not NULL. */
237 if (strlen(field) < 2) {
241 /* Make sure there is room for another entry */
242 if (rule->relevant_vars.num_ip_vars >= MAX_RULE_IP_VARS) {
246 /* TODO: a loop re-looking up the answer until its not just another ipvar! */
247 if (g_hash_table_lookup_extended(snort_config->ipvars, field+1, &original_key, &value)) {
249 rule->relevant_vars.ip_vars[rule->relevant_vars.num_ip_vars].name = (char*)original_key;
250 rule->relevant_vars.ip_vars[rule->relevant_vars.num_ip_vars].value = (char*)value;
252 rule->relevant_vars.num_ip_vars++;
256 /* Check to see if the port 'field' corresponds to an entry in the portvar dictionary.
257 * If it is add entry to rule */
258 static void rule_check_port_vars(SnortConfig_t *snort_config, Rule_t *rule, char *field)
260 gpointer original_key = NULL;
261 gpointer value = NULL;
263 /* Make sure field+1 not NULL. */
264 if (strlen(field) < 2) {
268 /* Make sure there is room for another entry */
269 if (rule->relevant_vars.num_port_vars >= MAX_RULE_PORT_VARS) {
273 /* TODO: a loop re-looking up the answer until its not just another portvar! */
274 if (g_hash_table_lookup_extended(snort_config->portvars, field+1, &original_key, &value)) {
275 rule->relevant_vars.port_vars[rule->relevant_vars.num_port_vars].name = (char*)original_key;
276 rule->relevant_vars.port_vars[rule->relevant_vars.num_port_vars].value = (char*)value;
278 rule->relevant_vars.num_port_vars++;
282 /* Look over the IP addresses and ports, and work out which variables/values are being used */
283 void rule_set_relevant_vars(SnortConfig_t *snort_config, Rule_t *rule)
286 int accumulated_length = 0;
289 /* No need to do this twice */
290 if (rule->relevant_vars.relevant_vars_set) {
294 /* Walk tokens up to the options, and look up ones that are addresses or ports. */
297 read_token(rule->rule_string+accumulated_length, ' ', &length, &accumulated_length, FALSE);
300 read_token(rule->rule_string+accumulated_length, ' ', &length, &accumulated_length, FALSE);
302 /* Read source address */
303 field = read_token(rule->rule_string+accumulated_length, ' ', &length, &accumulated_length, FALSE);
304 rule_check_ip_vars(snort_config, rule, field);
306 /* Read source port */
307 field = read_token(rule->rule_string+accumulated_length, ' ', &length, &accumulated_length, FALSE);
308 rule_check_port_vars(snort_config, rule, field);
311 read_token(rule->rule_string+accumulated_length, ' ', &length, &accumulated_length, FALSE);
314 field = read_token(rule->rule_string+accumulated_length, ' ', &length, &accumulated_length, FALSE);
315 rule_check_ip_vars(snort_config, rule, field);
318 field = read_token(rule->rule_string+accumulated_length, ' ', &length, &accumulated_length, FALSE);
319 rule_check_port_vars(snort_config, rule, field);
321 /* Set flag so won't do again for this rule */
322 rule->relevant_vars.relevant_vars_set = TRUE;
326 typedef enum vartype_e { var, ipvar, portvar, unknownvar } vartype_e;
328 /* Look for a "var", "ipvar" or "portvar" entry in this line */
329 static gboolean parse_variables_line(SnortConfig_t *snort_config, char *line)
331 vartype_e var_type = unknownvar;
333 char * variable_type;
334 char * variable_name;
338 int accumulated_length = 0;
340 /* Get variable type */
341 variable_type = read_token(line, ' ', &length, &accumulated_length, FALSE);
342 if (variable_type == NULL) {
346 if (strncmp(variable_type, "var", 3) == 0) {
349 else if (strncmp(variable_type, "ipvar", 5) == 0) {
352 else if (strncmp(variable_type, "portvar", 7) == 0) {
359 /* Get variable name */
360 variable_name = read_token(line+ accumulated_length, ' ', &length, &accumulated_length, TRUE);
361 if (variable_name == NULL) {
366 value = read_token(line + accumulated_length, ' ', &length, &accumulated_length, TRUE);
371 /* Add (name->value) to table according to variable type. */
374 if (strcmp(variable_name, "RULE_PATH") == 0) {
375 /* This can be relative or absolute. */
376 snort_config->rule_path = value;
377 snort_config->rule_path_is_absolute = g_path_is_absolute(value);
378 snort_debug_printf("rule_path set to %s (is_absolute=%d)\n",
379 snort_config->rule_path, snort_config->rule_path_is_absolute);
381 g_hash_table_insert(snort_config->vars, variable_name, value);
384 g_hash_table_insert(snort_config->ipvars, variable_name, value);
387 g_hash_table_insert(snort_config->portvars, variable_name, value);
397 /* Hash function for where key is a string. Just add up the value of each character and return that.. */
398 static guint string_hash(gconstpointer key)
401 const char *key_string = (const char *)key;
402 char c = key_string[n];
411 /* Comparison function for where key is a string. Simple comparison using strcmp() */
412 static gboolean string_equal(gconstpointer a, gconstpointer b)
414 const char *stringa = (const char*)a;
415 const char *stringb = (const char*)b;
417 return (strcmp(stringa, stringb) == 0);
420 /* Process a line that configures a reference line (invariably from 'reference.config') */
421 static gboolean parse_references_prefix_file_line(SnortConfig_t *snort_config, char *line)
424 char *prefix_name, *prefix_value;
425 int length=0, accumulated_length=0;
428 if (strncmp(line, "config reference: ", 18) != 0) {
432 /* Read the prefix and value */
434 prefix_name = read_token(source, ' ', &length, &accumulated_length, TRUE);
435 /* Store all name chars in lower case. */
436 for (n=0; prefix_name[n] != '\0'; n++) {
437 prefix_name[n] = g_ascii_tolower(prefix_name[n]);
440 prefix_value = read_token(source+accumulated_length, ' ', &length, &accumulated_length, TRUE);
442 /* Add entry into table */
443 g_hash_table_insert(snort_config->references_prefixes, prefix_name, prefix_value);
448 /* Try to expand the reference using the prefixes stored in the config */
449 char *expand_reference(SnortConfig_t *snort_config, char *reference)
451 static char expanded_reference[512];
452 int length = (int)strlen(reference);
453 int accumulated_length = 0;
455 /* Extract up to ',', then substitute prefix! */
456 snort_debug_printf("expand_reference(%s)\n", reference);
457 char *prefix = read_token(reference, ',', &length, &accumulated_length, FALSE);
459 if (*prefix != '\0') {
460 /* Convert to lowercase before lookup */
462 for (n=0; prefix[n] != '\0'; n++) {
463 prefix[n] = g_ascii_tolower(prefix[n]);
466 /* Look up prefix in table. */
467 char *prefix_replacement;
468 prefix_replacement = (char*)g_hash_table_lookup(snort_config->references_prefixes, prefix);
470 /* Append prefix and remainder, and return!!!! */
471 g_snprintf(expanded_reference, 512, "%s%s", prefix_replacement, reference+length+1);
472 return expanded_reference;
474 return "ERROR: Reference didn't contain prefix and ','!";
477 /* The rule has been matched with an alert, so update global config stats */
478 void rule_set_alert(SnortConfig_t *snort_config, Rule_t *rule,
479 guint *global_match_number,
480 guint *rule_match_number)
482 snort_config->stat_alerts_detected++;
483 *global_match_number = snort_config->stat_alerts_detected;
485 *rule_match_number = ++rule->matches_seen;
491 /* Delete an individual entry from a string table. */
492 static gboolean delete_string_entry(gpointer key,
494 gpointer user_data _U_)
496 char *key_string = (char*)key;
497 char *value_string = (char*)value;
500 g_free(value_string);
505 /* See if this is an include line, if it is open the file and call parse_config_file() */
506 static gboolean parse_include_file(SnortConfig_t *snort_config, char *line, const char *config_directory, int recursion_level)
509 int accumulated_length = 0;
510 char *include_filename;
512 /* Look for "include " */
513 char *include_token = read_token(line, ' ', &length, &accumulated_length, FALSE);
514 if (strlen(include_token) == 0) {
517 if (strncmp(include_token, "include", 7) != 0) {
521 /* Read the filename */
522 include_filename = read_token(line+accumulated_length, ' ', &length, &accumulated_length, FALSE);
523 if (*include_filename != '\0') {
525 char substituted_filename[512];
526 gboolean is_rule_file = FALSE;
528 /* May need to substitute variables into include path. */
529 if (strncmp(include_filename, "$RULE_PATH", 10) == 0) {
530 /* Write rule path variable value */
531 /* Don't assume $RULE_PATH will end in a file separator */
532 if (snort_config->rule_path_is_absolute) {
533 /* Rule path is absolute, so it can go at start */
534 g_snprintf(substituted_filename, 512, "%s%s%s",
535 snort_config->rule_path,
537 include_filename + 11);
540 /* Rule path is relative to config directory, so it goes first */
541 g_snprintf(substituted_filename, 512, "%s%s%s%s%s",
544 snort_config->rule_path,
546 include_filename + 11);
551 /* No $RULE_PATH, just use directory and filename */
552 /* But may not even need directory if included_folder is absolute! */
553 if (!g_path_is_absolute(include_filename)) {
554 g_snprintf(substituted_filename, 512, "%s/%s", config_directory, include_filename);
557 g_strlcpy(substituted_filename, include_filename, 512);
561 /* Try to open the file. */
562 new_config_fd = ws_fopen(substituted_filename, "r");
563 if (new_config_fd == NULL) {
564 snort_debug_printf("Failed to open config file %s\n", substituted_filename);
570 snort_config->stat_rules_files++;
572 parse_config_file(snort_config, new_config_fd, substituted_filename, config_directory, recursion_level + 1);
575 fclose(new_config_fd);
582 /* Process an individual option - i.e. the elements found between '(' and ')' */
583 static void process_rule_option(Rule_t *rule, char *options, int option_start_offset, int options_end_offset, int colon_offset)
585 static char name[1024], value[1024];
588 gint value_length = 0;
590 gint spaces_after_colon = 0;
592 if (colon_offset != 0) {
594 g_strlcpy(name, options+option_start_offset, colon_offset-option_start_offset);
595 if (options[colon_offset] == ' ') {
596 spaces_after_colon = 1;
598 g_strlcpy(value, options+colon_offset+spaces_after_colon, options_end_offset-spaces_after_colon-colon_offset);
599 value_length = (gint)strlen(value);
603 g_strlcpy(name, options+option_start_offset, options_end_offset-option_start_offset);
606 /* Do this extraction in one place (may not be number but should be OK) */
607 ws_strtoi32(value, (const gchar**)&value[value_length], &value32);
609 /* Think this is space at end of all options - don't compare with option names */
610 if (name[0] == '\0') {
614 /* Process the rule options that we are interested in */
615 if (strcmp(name, "msg") == 0) {
616 rule->msg = g_strdup(value);
618 else if (strcmp(name, "sid") == 0) {
621 else if (strcmp(name, "rev") == 0) {
624 else if (strcmp(name, "content") == 0) {
627 if (value_length < 3) {
631 /* Need to trim off " ", but first check for ! */
632 if (value[0] == '!') {
634 if (value_length < 4) {
635 /* i.e. also need quotes + at least one character */
640 value[options_end_offset-colon_offset-spaces_after_colon-2] = '\0';
641 rule_add_content(rule, value+value_start+1, value_start == 1);
643 else if (strcmp(name, "uricontent") == 0) {
646 if (value_length < 3) {
650 /* Need to trim off " ", but first check for ! */
651 if (value[0] == '!') {
653 if (value_length < 4) {
658 value[options_end_offset-colon_offset-spaces_after_colon-2] = '\0';
659 rule_add_uricontent(rule, value+value_start+1, value_start == 1);
661 else if (strcmp(name, "http_uri") == 0) {
662 rule_set_http_uri(rule);
664 else if (strcmp(name, "pcre") == 0) {
667 /* Need at least opening and closing / */
668 if (value_length < 3) {
672 /* Not expecting negation (!)... */
674 value[options_end_offset-colon_offset-spaces_after_colon-2] = '\0';
675 rule_add_pcre(rule, value+value_start+1);
677 else if (strcmp(name, "nocase") == 0) {
678 rule_set_content_nocase(rule);
680 else if (strcmp(name, "offset") == 0) {
681 rule_set_content_offset(rule, value32);
683 else if (strcmp(name, "depth") == 0) {
684 rule_set_content_depth(rule, value32);
686 else if (strcmp(name, "within") == 0) {
687 rule_set_content_within(rule, value32);
689 else if (strcmp(name, "distance") == 0) {
690 rule_set_content_distance(rule, value32);
692 else if (strcmp(name, "fast_pattern") == 0) {
693 rule_set_content_fast_pattern(rule);
695 else if (strcmp(name, "http_method") == 0) {
696 rule_set_content_http_method(rule);
698 else if (strcmp(name, "http_client_body") == 0) {
699 rule_set_content_http_client_body(rule);
701 else if (strcmp(name, "http_cookie") == 0) {
702 rule_set_content_http_cookie(rule);
704 else if (strcmp(name, "http_user_agent") == 0) {
705 rule_set_content_http_user_agent(rule);
707 else if (strcmp(name, "rawbytes") == 0) {
708 rule_set_content_rawbytes(rule);
710 else if (strcmp(name, "classtype") == 0) {
711 rule_set_classtype(rule, value);
713 else if (strcmp(name, "reference") == 0) {
714 rule_add_reference(rule, value);
717 /* Ignore an option we don't currently handle */
721 /* Parse a Snort alert, return TRUE if successful */
722 static gboolean parse_rule(SnortConfig_t *snort_config, char *line, const char *filename, int line_number, int line_length)
726 gboolean in_quotes = FALSE;
727 int options_start_index = 0, options_index = 0, colon_offset = 0;
729 int length = 0; /* CID 1398227 (bogus - read_token() always sets it) */
732 /* Rule will begin with alert */
733 if (strncmp(line, "alert ", 6) != 0) {
737 /* Allocate the rule itself */
738 rule = (Rule_t*)g_malloc(sizeof(Rule_t));
740 snort_debug_printf("looks like a rule: %s\n", line);
741 memset(rule, 0, sizeof(Rule_t));
743 rule->rule_string = g_strdup(line);
744 rule->file = g_strdup(filename);
745 rule->line_number = line_number;
747 /* Next token is the protocol */
748 rule->protocol = read_token(line+6, ' ', &length, &length, TRUE);
750 /* Find start of options. */
751 options_start = strstr(line, "(");
752 if (options_start == NULL) {
753 snort_debug_printf("start of options not found\n");
757 options_index = (int)(options_start-line) + 1;
759 /* To make parsing simpler, replace final ')' with ';' */
760 if (line[line_length-1] != ')') {
765 line[line_length-1] = ';';
768 /* Skip any spaces before next option */
769 while (line[options_index] == ' ') options_index++;
771 /* Now look for next ';', process one option at a time */
772 options = &line[options_index];
775 while ((c = options[options_index++])) {
776 /* Keep track of whether inside quotes */
778 in_quotes = !in_quotes;
780 /* Ignore ';' while inside quotes */
783 colon_offset = options_index;
786 /* End of current option - add to rule. */
787 process_rule_option(rule, options, options_start_index, options_index, colon_offset);
789 /* Skip any spaces before next option */
790 while (options[options_index] == ' ') options_index++;
792 /* Next rule will start here */
793 options_start_index = options_index;
800 /* Add rule to map of rules. */
801 g_hash_table_insert(snort_config->rules, GUINT_TO_POINTER((guint)rule->sid), rule);
802 snort_debug_printf("Snort rule with SID=%u added to table\n", rule->sid);
807 /* Delete an individual rule */
808 static gboolean delete_rule(gpointer key _U_,
810 gpointer user_data _U_)
812 Rule_t *rule = (Rule_t*)value;
815 /* Delete strings on heap. */
816 g_free(rule->rule_string);
819 g_free(rule->classtype);
820 g_free(rule->protocol);
822 for (n=0; n < rule->number_contents; n++) {
823 g_free(rule->contents[n].str);
824 g_free(rule->contents[n].translated_str);
827 for (n=0; n < rule->number_references; n++) {
828 g_free(rule->references[n]);
831 snort_debug_printf("Freeing rule at :%p\n", rule);
837 /* Parse this file, adding details to snort_config. */
838 /* N.B. using recursion_level to limit stack depth. */
839 #define MAX_CONFIG_FILE_RECURSE_DEPTH 8
840 static void parse_config_file(SnortConfig_t *snort_config, FILE *config_file_fd,
841 const char *filename, const char *dirname, int recursion_level)
843 #define MAX_LINE_LENGTH 4096
844 char line[MAX_LINE_LENGTH];
847 snort_debug_printf("parse_config_file(filename=%s, recursion_level=%d)\n", filename, recursion_level);
849 if (recursion_level > MAX_CONFIG_FILE_RECURSE_DEPTH) {
853 /* Read each line of the file in turn, and see if we want any info from it. */
854 while (fgets(line, MAX_LINE_LENGTH, config_file_fd)) {
859 /* Nothing interesting to parse */
860 if ((line[0] == '\0') || (line[0] == '#')) {
864 /* Trim newline from end */
865 line_length = (int)strlen(line);
866 while (line_length && ((line[line_length - 1] == '\n') || (line[line_length - 1] == '\r'))) {
869 line[line_length] = '\0';
870 if (line_length == 0) {
874 /* Offer line to the various parsing functions. Could optimise order.. */
875 if (parse_variables_line(snort_config, line)) {
878 if (parse_references_prefix_file_line(snort_config, line)) {
881 if (parse_include_file(snort_config, line, dirname, recursion_level)) {
884 if (parse_rule(snort_config, line, filename, line_number, line_length)) {
885 snort_config->stat_rules++;
893 /* Create the global ConfigParser */
894 void create_config(SnortConfig_t **snort_config, const char *snort_config_file)
898 FILE *config_file_fd;
900 snort_debug_printf("create_config (%s)\n", snort_config_file);
902 *snort_config = (SnortConfig_t*)g_malloc(sizeof(SnortConfig_t));
903 memset(*snort_config, 0, sizeof(SnortConfig_t));
905 /* Create rule table */
906 (*snort_config)->rules = g_hash_table_new(g_direct_hash, g_direct_equal);
908 /* Create reference prefix table */
909 (*snort_config)->references_prefixes = g_hash_table_new(string_hash, string_equal);
912 (*snort_config)->vars = g_hash_table_new(string_hash, string_equal);
913 (*snort_config)->ipvars = g_hash_table_new(string_hash, string_equal);
914 (*snort_config)->portvars = g_hash_table_new(string_hash, string_equal);
916 /* Extract separate directory and filename. */
917 dirname = g_path_get_dirname(snort_config_file);
918 basename = g_path_get_basename(snort_config_file);
920 /* Attempt to open the config file */
921 config_file_fd = ws_fopen(snort_config_file, "r");
922 if (config_file_fd == NULL) {
923 snort_debug_printf("Failed to open config file %s\n", snort_config_file);
927 /* Start parsing from the top-level config file. */
928 parse_config_file(*snort_config, config_file_fd, snort_config_file, dirname, 1 /* recursion level */);
933 fclose(config_file_fd);
937 /* Delete the entire config */
938 void delete_config(SnortConfig_t **snort_config)
940 snort_debug_printf("delete_config()\n");
942 /* Iterate over all rules, freeing each one! */
943 g_hash_table_foreach_remove((*snort_config)->rules, delete_rule, NULL);
944 g_hash_table_destroy((*snort_config)->rules);
946 /* References table */
947 g_hash_table_foreach_remove((*snort_config)->references_prefixes, delete_string_entry, NULL);
948 g_hash_table_destroy((*snort_config)->references_prefixes);
950 /* Free up variable tables */
951 g_hash_table_foreach_remove((*snort_config)->vars, delete_string_entry, NULL);
952 g_hash_table_destroy((*snort_config)->vars);
953 g_hash_table_foreach_remove((*snort_config)->ipvars, delete_string_entry, NULL);
954 g_hash_table_destroy((*snort_config)->ipvars);
955 g_hash_table_foreach_remove((*snort_config)->portvars, delete_string_entry, NULL);
956 g_hash_table_destroy((*snort_config)->portvars);
958 g_free(*snort_config);
960 *snort_config = NULL;
963 /* Look for a rule corresponding to the given SID */
964 Rule_t *get_rule(SnortConfig_t *snort_config, guint32 sid)
966 if ((snort_config == NULL) || (snort_config->rules == NULL)) {
970 return (Rule_t*)g_hash_table_lookup(snort_config->rules, GUINT_TO_POINTER(sid));
974 /* Fetch some statistics. */
975 void get_global_rule_stats(SnortConfig_t *snort_config, unsigned int sid,
976 unsigned int *number_rules_files, unsigned int *number_rules,
977 unsigned int *alerts_detected, unsigned int *this_rule_alerts_detected)
979 *number_rules_files = snort_config->stat_rules_files;
980 *number_rules = snort_config->stat_rules;
981 *alerts_detected = snort_config->stat_alerts_detected;
984 /* Look up rule and get current/total matches */
985 rule = get_rule(snort_config, sid);
987 *this_rule_alerts_detected = rule->matches_seen;
990 *this_rule_alerts_detected = 0;
994 /* Reset stats on individual rule */
995 static void reset_rule_stats(gpointer key _U_,
997 gpointer user_data _U_)
999 Rule_t *rule = (Rule_t*)value;
1000 rule->matches_seen = 0;
1003 /* Reset stats on all rules */
1004 void reset_global_rule_stats(SnortConfig_t *snort_config)
1006 /* Reset global stats */
1007 if (snort_config == NULL) {
1010 snort_config->stat_alerts_detected = 0;
1012 /* Iterate over all rules, resetting the stats of each */
1013 g_hash_table_foreach(snort_config->rules, reset_rule_stats, NULL);
1017 /*************************************************************************************/
1018 /* Dealing with content fields and trying to find where it matches within the packet */
1019 /* Parse content strings to interpret binary and escaped characters. Do this */
1020 /* so we can look for in frame using memcmp(). */
1021 static unsigned char content_get_nibble_value(char c)
1023 static unsigned char values[256];
1024 static gboolean values_set = FALSE;
1027 /* Set table once and for all */
1029 for (ch='a'; ch <= 'f'; ch++) {
1030 values[ch] = 0xa + (ch-'a');
1032 for (ch='A'; ch <= 'F'; ch++) {
1033 values[ch] = 0xa + (ch-'A');
1035 for (ch='0'; ch <= '9'; ch++) {
1036 values[ch] = (ch-'0');
1041 return values[(unsigned char)c];
1044 /* Go through string, converting hex digits into guint8, and removing escape characters. */
1045 guint content_convert_to_binary(content_t *content)
1048 gboolean in_binary_mode = FALSE; /* Are we in a binary region of the string? */
1049 gboolean have_one_nibble = FALSE; /* Do we have the first nibble of the pair needed to make a byte? */
1050 unsigned char one_nibble = 0; /* Value of first nibble if we have it */
1053 gboolean have_backslash = FALSE;
1054 static gchar binary_str[1024];
1056 /* Just return length if have previously translated in binary string. */
1057 if (content->translated) {
1058 return content->translated_length;
1061 /* Walk over each character, work out what needs to be written into output */
1062 for (n=0; content->str[n] != '\0'; n++) {
1063 c = content->str[n];
1065 /* Flip binary mode */
1066 in_binary_mode = !in_binary_mode;
1070 if (!in_binary_mode) {
1071 /* Not binary mode. Copying characters into output buffer, but watching out for escaped chars. */
1072 if (!have_backslash) {
1074 /* Just note that we have a backslash */
1075 have_backslash = TRUE;
1079 /* Just copy the character straight into output. */
1080 binary_str[output_idx++] = (unsigned char)c;
1084 /* Currently have a backslash. Reset flag. */
1086 /* Just copy the character into output. Really, the only characters that should be escaped
1087 are ';' and '\' and '"' */
1088 binary_str[output_idx++] = (unsigned char)c;
1092 /* Binary mode. Handle pairs of hex digits and translate into guint8 */
1094 /* Ignoring inside binary mode */
1098 unsigned char nibble = content_get_nibble_value(c);
1099 if (!have_one_nibble) {
1100 /* Store first nibble of a pair */
1101 one_nibble = nibble;
1102 have_one_nibble = TRUE;
1105 /* Combine both nibbles into a byte */
1106 binary_str[output_idx++] = (one_nibble << 4) + nibble;
1107 /* Reset flag - looking for new pair of nibbles */
1108 have_one_nibble = FALSE;
1114 /* Store result for next time. */
1115 content->translated_str = (guchar*)g_malloc(output_idx+1);
1116 memcpy(content->translated_str, binary_str, output_idx+1);
1117 content->translated = TRUE;
1118 content->translated_length = output_idx;
1123 /* In order to use glib's regex library, need to trim
1124 '/' delimiters and any modifiers from the end of the string */
1125 gboolean content_convert_pcre_for_regex(content_t *content)
1127 guint pcre_length, i, end_delimiter_offset = 0;
1129 /* Return if already converted */
1130 if (content->translated_str) {
1134 pcre_length = (guint)strlen(content->str);
1136 /* Start with content->str */
1137 if (pcre_length < 3) {
1138 /* Can't be valid. Expect /regex/[modifiers] */
1142 if (pcre_length >= 512) {
1143 /* Have seen regex library crash on very long expressions
1144 * (830 bytes) as seen in SID=2019326, REV=6 */
1148 /* Verify that string starts with / */
1149 if (content->str[0] != '/') {
1153 /* Next, look for closing / near end of string */
1154 for (i=pcre_length-1; i > 2; i--) {
1155 if (content->str[i] == '/') {
1156 end_delimiter_offset = i;
1160 switch (content->str[i]) {
1162 content->pcre_case_insensitive = TRUE;
1165 content->pcre_dot_includes_newline = TRUE;
1168 content->pcre_raw = TRUE;
1171 content->pcre_multiline = TRUE;
1175 /* TODO: handle other modifiers that will get seen? */
1176 /* N.B. 'U' (match in decoded URI buffers) can't be handled, so don't store in flag. */
1177 /* N.B. not sure if/how to handle 'R' (effectively distance:0) */
1178 snort_debug_printf("Unhandled pcre modifier '%c'\n", content->str[i]);
1183 if (end_delimiter_offset == 0) {
1184 /* Didn't find it */
1188 /* Store result for next time. */
1189 content->translated_str = (guchar*)g_malloc(end_delimiter_offset);
1190 memcpy(content->translated_str, content->str+1, end_delimiter_offset - 1);
1191 content->translated_str[end_delimiter_offset-1] = '\0';
1192 content->translated = TRUE;
1193 content->translated_length = end_delimiter_offset - 1;
1199 * Editor modelines - http://www.wireshark.org/tools/modelines.html
1204 * indent-tabs-mode: nil
1207 * vi: set shiftwidth=4 tabstop=8 expandtab:
1208 * :indentSize=4:tabSize=8:noTabs=true: