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"
14 #include <wireshark.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
)
34 /* Skip any leading whitespace */
35 while ((source
[offset
] == ' ') || (source
[offset
] == '\t')) {
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];
55 const char *source_proper
= skipWhiteSpace(source
, accumulated_length
);
57 while (source_proper
[offset
] != '\0' && source_proper
[offset
] != delimeter
) {
62 *accumulated_length
+= offset
;
64 /* Copy into new string */
65 char *new_string
= g_strndup(source_proper
, offset
+1);
66 new_string
[offset
] = '\0';
70 /* Return in static buffer */
71 memcpy(&static_buffer
, source_proper
, offset
);
72 static_buffer
[offset
] = '\0';
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
;
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
;
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
;
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
);
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
;
231 /* Make sure field+1 not NULL. */
232 if (strlen(field
) < 2) {
236 /* Make sure there is room for another entry */
237 if (rule
->relevant_vars
.num_ip_vars
>= MAX_RULE_IP_VARS
) {
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
;
258 /* Make sure field+1 not NULL. */
259 if (strlen(field
) < 2) {
263 /* Make sure there is room for another entry */
264 if (rule
->relevant_vars
.num_port_vars
>= MAX_RULE_PORT_VARS
) {
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
)
281 int accumulated_length
= 0;
284 /* No need to do this twice */
285 if (rule
->relevant_vars
.relevant_vars_set
) {
289 /* Walk tokens up to the options, and look up ones that are addresses or ports. */
292 read_token(rule
->rule_string
+accumulated_length
, ' ', &length
, &accumulated_length
, false);
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
);
306 read_token(rule
->rule_string
+accumulated_length
, ' ', &length
, &accumulated_length
, false);
309 field
= read_token(rule
->rule_string
+accumulated_length
, ' ', &length
, &accumulated_length
, false);
310 rule_check_ip_vars(snort_config
, rule
, field
);
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
;
333 int accumulated_length
= 0;
335 /* Get variable type */
336 variable_type
= read_token(line
, ' ', &length
, &accumulated_length
, false);
337 if (variable_type
== NULL
) {
341 if (strncmp(variable_type
, "var", 3) == 0) {
344 else if (strncmp(variable_type
, "ipvar", 5) == 0) {
347 else if (strncmp(variable_type
, "portvar", 7) == 0) {
354 /* Get variable name */
355 variable_name
= read_token(line
+ accumulated_length
, ' ', &length
, &accumulated_length
, true);
356 if (variable_name
== NULL
) {
361 value
= read_token(line
+ accumulated_length
, ' ', &length
, &accumulated_length
, true);
366 /* Add (name->value) to table according to variable type. */
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
);
379 g_hash_table_insert(snort_config
->ipvars
, variable_name
, value
);
382 g_hash_table_insert(snort_config
->portvars
, variable_name
, value
);
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
];
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;
422 if (strncmp(line
, "config reference: ", 18) != 0) {
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
);
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 */
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
;
470 /* Just return the original 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
;
486 *rule_match_number
= ++rule
->matches_seen
;
492 /* Delete an individual entry from a string table. */
493 static gboolean
delete_string_entry(void *key
,
497 char *key_string
= (char*)key
;
498 char *value_string
= (char*)value
;
501 g_free(value_string
);
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
)
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) {
519 if (strncmp(include_token
, "include", 7) != 0) {
523 /* Read the filename */
524 include_filename
= read_token(line
+accumulated_length
, ' ', &length
, &accumulated_length
, false);
525 if (*include_filename
!= '\0') {
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,
542 /* Rule path is relative to config directory, so it goes first */
543 substituted_filename
= g_build_path(G_DIR_SEPARATOR_S
,
545 snort_config
->rule_path
,
546 include_filename
+ 11,
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
);
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
);
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
);
580 fclose(new_config_fd
);
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];
593 int value_length
= 0;
594 uint32_t value32
= 0;
595 int spaces_after_colon
= 0;
597 if (colon_offset
!= 0) {
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
);
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') {
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) {
627 else if (strcmp(name
, "rev") == 0) {
630 else if (strcmp(name
, "content") == 0) {
633 if (value_length
< 3) {
637 /* Need to trim off " ", but first check for ! */
638 if (value
[0] == '!') {
640 if (value_length
< 4) {
641 /* i.e. also need quotes + at least one character */
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) {
652 if (value_length
< 3) {
656 /* Need to trim off " ", but first check for ! */
657 if (value
[0] == '!') {
659 if (value_length
< 4) {
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) {
673 /* Need at least opening and closing / */
674 if (value_length
< 3) {
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
);
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
;
732 bool in_quotes
= false;
733 int options_start_index
= 0, options_index
= 0, colon_offset
= 0;
735 int length
= 0; /* CID 1398227 (bogus - read_token() always sets it) */
738 /* Rule will begin with alert */
739 if (strncmp(line
, "alert ", 6) != 0) {
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");
763 options_index
= (int)(options_start
-line
) + 1;
765 /* To make parsing simpler, replace final ')' with ';' */
766 if (line
[line_length
-1] != ')') {
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
];
781 while ((c
= options
[options_index
++])) {
782 /* Keep track of whether inside quotes */
784 in_quotes
= !in_quotes
;
786 /* Ignore ';' while inside quotes */
789 colon_offset
= options_index
;
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
;
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
);
813 /* Delete an individual rule */
814 static gboolean
delete_rule(void * key _U_
,
816 void * user_data _U_
)
818 Rule_t
*rule
= (Rule_t
*)value
;
821 /* Delete strings on heap. */
822 g_free(rule
->rule_string
);
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
);
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
];
854 ws_debug("parse_config_file(filename=%s, recursion_level=%d)", filename
, recursion_level
);
856 if (recursion_level
> MAX_CONFIG_FILE_RECURSE_DEPTH
) {
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
)) {
866 /* Nothing interesting to parse */
867 if ((line
[0] == '\0') || (line
[0] == '#')) {
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'))) {
876 line
[line_length
] = '\0';
877 if (line_length
== 0) {
881 /* Offer line to the various parsing functions. Could optimise order.. */
882 if (parse_variables_line(snort_config
, line
)) {
885 if (parse_references_prefix_file_line(snort_config
, line
)) {
888 if (parse_include_file(snort_config
, line
, dirname
, recursion_level
)) {
891 if (parse_rule(snort_config
, line
, filename
, line_number
, line_length
)) {
892 snort_config
->stat_rules
++;
900 /* Create the global ConfigParser */
901 void create_config(SnortConfig_t
**snort_config
, const char *snort_config_file
)
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
);
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
);
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
);
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
)) {
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
;
991 /* Look up rule and get current/total matches */
992 rule
= get_rule(snort_config
, sid
);
994 *this_rule_alerts_detected
= rule
->matches_seen
;
997 *this_rule_alerts_detected
= 0;
1001 /* Reset stats on individual rule */
1002 static void reset_rule_stats(void * key _U_
,
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
) {
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;
1034 /* Set table once and for all */
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');
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
)
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 */
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
];
1072 /* Flip binary mode */
1073 in_binary_mode
= !in_binary_mode
;
1077 if (!in_binary_mode
) {
1078 /* Not binary mode. Copying characters into output buffer, but watching out for escaped chars. */
1079 if (!have_backslash
) {
1081 /* Just note that we have a backslash */
1082 have_backslash
= true;
1086 /* Just copy the character straight into output. */
1087 binary_str
[output_idx
++] = (unsigned char)c
;
1091 /* Currently have a backslash. Reset flag. */
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
;
1099 /* Binary mode. Handle pairs of hex digits and translate into uint8_t */
1101 /* Ignoring inside binary mode */
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;
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
;
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
) {
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] */
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 */
1155 /* Verify that string starts with / */
1156 if (content
->str
[0] != '/') {
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
;
1167 switch (content
->str
[i
]) {
1169 content
->pcre_case_insensitive
= true;
1172 content
->pcre_dot_includes_newline
= true;
1175 content
->pcre_raw
= true;
1178 content
->pcre_multiline
= true;
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
]);
1190 if (end_delimiter_offset
== 0) {
1191 /* Didn't find it */
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;
1206 * Editor modelines - https://www.wireshark.org/tools/modelines.html
1211 * indent-tabs-mode: nil
1214 * vi: set shiftwidth=4 tabstop=8 expandtab:
1215 * :indentSize=4:tabSize=8:noTabs=true: