ieee1609dot2: fix no previous prototype for function 'proto_register_ieee1609dot2...
[metze/wireshark/wip.git] / epan / dissectors / snort-config.c
1 /* snort-config.c
2  *
3  * Copyright 2016, Martin Mathieson
4  *
5  * Wireshark - Network traffic analyzer
6  * By Gerald Combs <gerald@wireshark.org>
7  * Copyright 1998 Gerald Combs
8  *
9  * SPDX-License-Identifier: GPL-2.0-or-later
10  */
11
12 #include "config.h"
13
14 #include <stdio.h>
15 #include <stdlib.h>
16 #include <string.h>
17 #include <ctype.h>
18
19 #include <wsutil/file_util.h>
20 #include <wsutil/strtoi.h>
21
22 #include "snort-config.h"
23
24
25 #ifndef _WIN32
26 const char* g_file_separator = "/";
27 #else
28 const char* g_file_separator = "\\";
29 #endif
30
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);
33
34 /* Skip white space from 'source', return pointer to first non-whitespace char */
35 static char *skipWhiteSpace(char *source, int *accumulated_offset)
36 {
37     int offset = 0;
38
39     /* Skip any leading whitespace */
40     while (source[offset] != '\0' && source[offset] == ' ') {
41         offset++;
42     }
43
44     *accumulated_offset += offset;
45     return source + offset;
46 }
47
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)
56 {
57     static char static_buffer[1024];
58     int offset = 0;
59
60     char *source_proper = skipWhiteSpace(source, accumulated_length);
61
62     while (source_proper[offset] != '\0' && source_proper[offset] != delimeter) {
63         offset++;
64     }
65
66     *length = offset;
67     *accumulated_length += offset;
68     if (copy) {
69         /* Copy into new string */
70         char *new_string = g_strndup(source_proper, offset+1);
71         new_string[offset] = '\0';
72         return new_string;
73     }
74     else {
75         /* Return in static buffer */
76         memcpy(&static_buffer, source_proper, offset);
77         static_buffer[offset] = '\0';
78         return static_buffer;
79     }
80 }
81
82 /* Add a new content field to the rule */
83 static gboolean rule_add_content(Rule_t *rule, const char *content_string, gboolean negated)
84 {
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;
90         return TRUE;
91     }
92     return FALSE;
93 }
94
95 /* Set the nocase property for a rule */
96 static void rule_set_content_nocase(Rule_t *rule)
97 {
98     if (rule->last_added_content) {
99         rule->last_added_content->nocase = TRUE;
100     }
101 }
102
103 /* Set the offset property of a content field */
104 static void rule_set_content_offset(Rule_t *rule, gint value)
105 {
106     if (rule->last_added_content) {
107         rule->last_added_content->offset = value;
108         rule->last_added_content->offset_set = TRUE;
109     }
110 }
111
112 /* Set the depth property of a content field */
113 static void rule_set_content_depth(Rule_t *rule, guint value)
114 {
115     if (rule->last_added_content) {
116         rule->last_added_content->depth = value;
117     }
118 }
119
120 /* Set the distance property of a content field */
121 static void rule_set_content_distance(Rule_t *rule, gint value)
122 {
123     if (rule->last_added_content) {
124         rule->last_added_content->distance = value;
125         rule->last_added_content->distance_set = TRUE;
126     }
127 }
128
129 /* Set the distance property of a content field */
130 static void rule_set_content_within(Rule_t *rule, guint value)
131 {
132     if (rule->last_added_content) {
133         /* Assuming won't be 0... */
134         rule->last_added_content->within = value;
135     }
136 }
137
138 /* Set the fastpattern property of a content field */
139 static void rule_set_content_fast_pattern(Rule_t *rule)
140 {
141     if (rule->last_added_content) {
142         rule->last_added_content->fastpattern = TRUE;
143     }
144 }
145
146 /* Set the rawbytes property of a content field */
147 static void rule_set_content_rawbytes(Rule_t *rule)
148 {
149     if (rule->last_added_content) {
150         rule->last_added_content->rawbytes = TRUE;
151     }
152 }
153
154 /* Set the http_method property of a content field */
155 static void rule_set_content_http_method(Rule_t *rule)
156 {
157     if (rule->last_added_content) {
158         rule->last_added_content->http_method = TRUE;
159     }
160 }
161
162 /* Set the http_client property of a content field */
163 static void rule_set_content_http_client_body(Rule_t *rule)
164 {
165     if (rule->last_added_content) {
166         rule->last_added_content->http_client_body = TRUE;
167     }
168 }
169
170 /* Set the http_cookie property of a content field */
171 static void rule_set_content_http_cookie(Rule_t *rule)
172 {
173     if (rule->last_added_content) {
174         rule->last_added_content->http_cookie = TRUE;
175     }
176 }
177
178 /* Set the http_UserAgent property of a content field */
179 static void rule_set_content_http_user_agent(Rule_t *rule)
180 {
181     if (rule->last_added_content) {
182         rule->last_added_content->http_user_agent = TRUE;
183     }
184 }
185
186 /* Add a uricontent field to the rule */
187 static gboolean rule_add_uricontent(Rule_t *rule, const char *uricontent_string, gboolean negated)
188 {
189     if (rule_add_content(rule, uricontent_string, negated)) {
190         rule->last_added_content->content_type = UriContent;
191         return TRUE;
192     }
193     return FALSE;
194 }
195
196 /* This content field now becomes a uricontent after seeing modifier  */
197 static void rule_set_http_uri(Rule_t *rule)
198 {
199     if (rule->last_added_content != NULL) {
200         rule->last_added_content->content_type = UriContent;
201     }
202 }
203
204 /* Add a pcre field to the rule */
205 static gboolean rule_add_pcre(Rule_t *rule, const char *pcre_string)
206 {
207     if (rule_add_content(rule, pcre_string, FALSE)) {
208         rule->last_added_content->content_type = Pcre;
209         return TRUE;
210     }
211     return FALSE;
212 }
213
214 /* Set the rule's classtype field */
215 static gboolean rule_set_classtype(Rule_t *rule, const char *classtype)
216 {
217     rule->classtype = g_strdup(classtype);
218     return TRUE;
219 }
220
221 /* Add a reference string to the rule */
222 static void rule_add_reference(Rule_t *rule, const char *reference_string)
223 {
224     if (rule->number_references < MAX_REFERENCE_ENTRIES) {
225         rule->references[rule->number_references++] = g_strdup(reference_string);
226     }
227 }
228
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)
232 {
233     gpointer original_key = NULL;
234     gpointer value = NULL;
235
236     /* Make sure field+1 not NULL. */
237     if (strlen(field) < 2) {
238         return;
239     }
240
241     /* Make sure there is room for another entry */
242     if (rule->relevant_vars.num_ip_vars >= MAX_RULE_IP_VARS) {
243         return;
244     }
245
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)) {
248
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;
251
252         rule->relevant_vars.num_ip_vars++;
253     }
254 }
255
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)
259 {
260     gpointer original_key = NULL;
261     gpointer value = NULL;
262
263     /* Make sure field+1 not NULL. */
264     if (strlen(field) < 2) {
265         return;
266     }
267
268     /* Make sure there is room for another entry */
269     if (rule->relevant_vars.num_port_vars >= MAX_RULE_PORT_VARS) {
270         return;
271     }
272
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;
277
278         rule->relevant_vars.num_port_vars++;
279     }
280 }
281
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)
284 {
285     int length;
286     int accumulated_length = 0;
287     char *field;
288
289     /* No need to do this twice */
290     if (rule->relevant_vars.relevant_vars_set) {
291         return;
292     }
293
294     /* Walk tokens up to the options, and look up ones that are addresses or ports. */
295
296     /* Skip "alert" */
297     read_token(rule->rule_string+accumulated_length, ' ', &length, &accumulated_length, FALSE);
298
299     /* Skip protocol. */
300     read_token(rule->rule_string+accumulated_length, ' ', &length, &accumulated_length, FALSE);
301
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);
305
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);
309
310     /* Read direction */
311     read_token(rule->rule_string+accumulated_length, ' ', &length, &accumulated_length, FALSE);
312
313     /* Dest address */
314     field = read_token(rule->rule_string+accumulated_length, ' ', &length, &accumulated_length, FALSE);
315     rule_check_ip_vars(snort_config, rule, field);
316
317     /* Dest port */
318     field = read_token(rule->rule_string+accumulated_length, ' ', &length, &accumulated_length, FALSE);
319     rule_check_port_vars(snort_config, rule, field);
320
321     /* Set flag so won't do again for this rule */
322     rule->relevant_vars.relevant_vars_set = TRUE;
323 }
324
325
326 typedef enum vartype_e { var, ipvar, portvar, unknownvar } vartype_e;
327
328 /* Look for a "var", "ipvar" or "portvar" entry in this line */
329 static gboolean parse_variables_line(SnortConfig_t *snort_config, char *line)
330 {
331     vartype_e var_type = unknownvar;
332
333     char *  variable_type;
334     char *  variable_name;
335     char *  value;
336
337     int length;
338     int accumulated_length = 0;
339
340     /* Get variable type */
341     variable_type = read_token(line, ' ', &length, &accumulated_length, FALSE);
342     if (variable_type == NULL) {
343         return FALSE;
344     }
345
346     if (strncmp(variable_type, "var", 3) == 0) {
347         var_type = var;
348     }
349     else if (strncmp(variable_type, "ipvar", 5) == 0) {
350         var_type = ipvar;
351     }
352     else if (strncmp(variable_type, "portvar", 7) == 0) {
353         var_type = portvar;
354     }
355     else {
356         return FALSE;
357     }
358
359     /* Get variable name */
360     variable_name = read_token(line+ accumulated_length, ' ', &length, &accumulated_length, TRUE);
361     if (variable_name == NULL) {
362         return FALSE;
363     }
364
365     /* Now value */
366     value = read_token(line + accumulated_length, ' ', &length, &accumulated_length, TRUE);
367     if (value == NULL) {
368         return FALSE;
369     }
370
371     /* Add (name->value) to table according to variable type. */
372     switch (var_type) {
373         case var:
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);
380             }
381             g_hash_table_insert(snort_config->vars, variable_name, value);
382             break;
383         case ipvar:
384             g_hash_table_insert(snort_config->ipvars, variable_name, value);
385             break;
386         case portvar:
387             g_hash_table_insert(snort_config->portvars, variable_name, value);
388             break;
389
390         default:
391             return FALSE;
392     }
393
394     return FALSE;
395 }
396
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)
399 {
400     guint total=0, n=0;
401     const char *key_string = (const char *)key;
402     char c = key_string[n];
403
404     while (c != '\0') {
405         total += (int)c;
406         c = key_string[++n];
407     }
408     return total;
409 }
410
411 /* Comparison function for where key is a string. Simple comparison using strcmp() */
412 static gboolean string_equal(gconstpointer a, gconstpointer b)
413 {
414     const char *stringa = (const char*)a;
415     const char *stringb = (const char*)b;
416
417     return (strcmp(stringa, stringb) == 0);
418 }
419
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)
422 {
423     char *source;
424     char *prefix_name, *prefix_value;
425     int length=0, accumulated_length=0;
426     int n;
427
428     if (strncmp(line, "config reference: ", 18) != 0) {
429         return FALSE;
430     }
431
432     /* Read the prefix and value */
433     source = line+18;
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]);
438     }
439
440     prefix_value = read_token(source+accumulated_length, ' ', &length, &accumulated_length, TRUE);
441
442     /* Add entry into table */
443     g_hash_table_insert(snort_config->references_prefixes, prefix_name, prefix_value);
444
445     return FALSE;
446 }
447
448 /* Try to expand the reference using the prefixes stored in the config */
449 char *expand_reference(SnortConfig_t *snort_config, char *reference)
450 {
451     static char expanded_reference[512];
452     int length = (int)strlen(reference);
453     int accumulated_length = 0;
454
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);
458
459     if (*prefix != '\0') {
460         /* Convert to lowercase before lookup */
461         guint n;
462         for (n=0; prefix[n] != '\0'; n++) {
463             prefix[n] = g_ascii_tolower(prefix[n]);
464         }
465
466         /* Look up prefix in table. */
467         char *prefix_replacement;
468         prefix_replacement = (char*)g_hash_table_lookup(snort_config->references_prefixes, prefix);
469
470         /* Append prefix and remainder, and return!!!! */
471         g_snprintf(expanded_reference, 512, "%s%s", prefix_replacement, reference+length+1);
472         return expanded_reference;
473     }
474     return "ERROR: Reference didn't contain prefix and ','!";
475 }
476
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)
481 {
482     snort_config->stat_alerts_detected++;
483     *global_match_number = snort_config->stat_alerts_detected;
484     if (rule != NULL) {
485         *rule_match_number = ++rule->matches_seen;
486     }
487 }
488
489
490
491 /* Delete an individual entry from a string table. */
492 static gboolean delete_string_entry(gpointer key,
493                                     gpointer value,
494                                     gpointer user_data _U_)
495 {
496     char *key_string = (char*)key;
497     char *value_string = (char*)value;
498
499     g_free(key_string);
500     g_free(value_string);
501
502     return TRUE;
503 }
504
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)
507 {
508     int length;
509     int accumulated_length = 0;
510     char *include_filename;
511
512     /* Look for "include " */
513     char *include_token = read_token(line, ' ', &length, &accumulated_length, FALSE);
514     if (strlen(include_token) == 0) {
515         return FALSE;
516     }
517     if (strncmp(include_token, "include", 7) != 0) {
518         return FALSE;
519     }
520
521     /* Read the filename */
522     include_filename = read_token(line+accumulated_length, ' ', &length, &accumulated_length, FALSE);
523     if (*include_filename != '\0') {
524         FILE *new_config_fd;
525         char substituted_filename[512];
526         gboolean is_rule_file = FALSE;
527
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,
536                            g_file_separator,
537                            include_filename + 11);
538             }
539             else {
540                 /* Rule path is relative to config directory, so it goes first */
541                 g_snprintf(substituted_filename, 512, "%s%s%s%s%s",
542                            config_directory,
543                            g_file_separator,
544                            snort_config->rule_path,
545                            g_file_separator,
546                            include_filename + 11);
547             }
548             is_rule_file = TRUE;
549         }
550         else {
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);
555             }
556             else {
557                 g_strlcpy(substituted_filename, include_filename, 512);
558             }
559         }
560
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);
565             return FALSE;
566         }
567
568         /* Parse the file */
569         if (is_rule_file) {
570             snort_config->stat_rules_files++;
571         }
572         parse_config_file(snort_config, new_config_fd, substituted_filename, config_directory, recursion_level + 1);
573
574         /* Close the file */
575         fclose(new_config_fd);
576
577         return TRUE;
578     }
579     return FALSE;
580 }
581
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)
584 {
585     static char name[1024], value[1024];
586     name[0] = '\0';
587     value[0] = '\0';
588     gint value_length = 0;
589     guint32 value32 = 0;
590     gint spaces_after_colon = 0;
591
592     if (colon_offset != 0) {
593         /* Name and value */
594         g_strlcpy(name, options+option_start_offset, colon_offset-option_start_offset);
595         if (options[colon_offset] == ' ') {
596             spaces_after_colon = 1;
597         }
598         g_strlcpy(value, options+colon_offset+spaces_after_colon, options_end_offset-spaces_after_colon-colon_offset);
599         value_length = (gint)strlen(value);
600     }
601     else {
602         /* Just name */
603         g_strlcpy(name, options+option_start_offset, options_end_offset-option_start_offset);
604     }
605
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);
608
609     /* Think this is space at end of all options - don't compare with option names */
610     if (name[0] == '\0') {
611         return;
612     }
613
614     /* Process the rule options that we are interested in */
615     if (strcmp(name, "msg") == 0) {
616         rule->msg = g_strdup(value);
617     }
618     else if (strcmp(name, "sid") == 0) {
619         rule->sid = value32;
620     }
621     else if (strcmp(name, "rev") == 0) {
622         value32 = rule->rev;
623     }
624     else if (strcmp(name, "content") == 0) {
625         int value_start = 0;
626
627         if (value_length < 3) {
628             return;
629         }
630
631         /* Need to trim off " ", but first check for ! */
632         if (value[0] == '!') {
633             value_start = 1;
634             if (value_length < 4) {
635                 /* i.e. also need quotes + at least one character */
636                 return;
637             }
638         }
639
640         value[options_end_offset-colon_offset-spaces_after_colon-2] = '\0';
641         rule_add_content(rule, value+value_start+1, value_start == 1);
642     }
643     else if (strcmp(name, "uricontent") == 0) {
644         int value_start = 0;
645
646         if (value_length < 3) {
647             return;
648         }
649
650         /* Need to trim off " ", but first check for ! */
651         if (value[0] == '!') {
652             value_start = 1;
653             if (value_length < 4) {
654                 return;
655             }
656         }
657
658         value[options_end_offset-colon_offset-spaces_after_colon-2] = '\0';
659         rule_add_uricontent(rule, value+value_start+1, value_start == 1);
660     }
661     else if (strcmp(name, "http_uri") == 0) {
662         rule_set_http_uri(rule);
663     }
664     else if (strcmp(name, "pcre") == 0) {
665         int value_start = 0;
666
667         /* Need at least opening and closing / */
668         if (value_length < 3) {
669             return;
670         }
671
672         /* Not expecting negation (!)... */
673
674         value[options_end_offset-colon_offset-spaces_after_colon-2] = '\0';
675         rule_add_pcre(rule, value+value_start+1);
676     }
677     else if (strcmp(name, "nocase") == 0) {
678         rule_set_content_nocase(rule);
679     }
680     else if (strcmp(name, "offset") == 0) {
681         rule_set_content_offset(rule, value32);
682     }
683     else if (strcmp(name, "depth") == 0) {
684         rule_set_content_depth(rule, value32);
685     }
686     else if (strcmp(name, "within") == 0) {
687         rule_set_content_within(rule, value32);
688     }
689     else if (strcmp(name, "distance") == 0) {
690         rule_set_content_distance(rule, value32);
691     }
692     else if (strcmp(name, "fast_pattern") == 0) {
693         rule_set_content_fast_pattern(rule);
694     }
695     else if (strcmp(name, "http_method") == 0) {
696         rule_set_content_http_method(rule);
697     }
698     else if (strcmp(name, "http_client_body") == 0) {
699         rule_set_content_http_client_body(rule);
700     }
701     else if (strcmp(name, "http_cookie") == 0) {
702         rule_set_content_http_cookie(rule);
703     }
704     else if (strcmp(name, "http_user_agent") == 0) {
705         rule_set_content_http_user_agent(rule);
706     }
707     else if (strcmp(name, "rawbytes") == 0) {
708         rule_set_content_rawbytes(rule);
709     }
710     else if (strcmp(name, "classtype") == 0) {
711         rule_set_classtype(rule, value);
712     }
713     else if (strcmp(name, "reference") == 0) {
714         rule_add_reference(rule, value);
715     }
716     else {
717         /* Ignore an option we don't currently handle */
718     }
719 }
720
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)
723 {
724     char *options_start;
725     char *options;
726     gboolean in_quotes = FALSE;
727     int options_start_index = 0, options_index = 0, colon_offset = 0;
728     char c;
729     int length = 0; /*  CID 1398227 (bogus - read_token() always sets it) */
730     Rule_t *rule = NULL;
731
732     /* Rule will begin with alert */
733     if (strncmp(line, "alert ", 6) != 0) {
734         return FALSE;
735     }
736
737     /* Allocate the rule itself */
738     rule = (Rule_t*)g_malloc(sizeof(Rule_t));
739
740     snort_debug_printf("looks like a rule: %s\n", line);
741     memset(rule, 0, sizeof(Rule_t));
742
743     rule->rule_string = g_strdup(line);
744     rule->file = g_strdup(filename);
745     rule->line_number = line_number;
746
747     /* Next token is the protocol */
748     rule->protocol = read_token(line+6, ' ', &length, &length, TRUE);
749
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");
754         g_free(rule);
755         return FALSE;
756     }
757     options_index = (int)(options_start-line) + 1;
758
759     /* To make parsing simpler, replace final ')' with ';' */
760     if (line[line_length-1] != ')') {
761         g_free(rule);
762         return FALSE;
763     }
764     else {
765         line[line_length-1] = ';';
766     }
767
768     /* Skip any spaces before next option */
769     while (line[options_index] == ' ') options_index++;
770
771     /* Now look for next ';', process one option at a time */
772     options = &line[options_index];
773     options_index = 0;
774
775     while ((c = options[options_index++])) {
776         /* Keep track of whether inside quotes */
777         if (c == '"') {
778             in_quotes = !in_quotes;
779         }
780         /* Ignore ';' while inside quotes */
781         if (!in_quotes) {
782             if (c == ':') {
783                 colon_offset = options_index;
784             }
785             if (c == ';') {
786                 /* End of current option - add to rule. */
787                 process_rule_option(rule, options, options_start_index, options_index, colon_offset);
788
789                 /* Skip any spaces before next option */
790                 while (options[options_index] == ' ') options_index++;
791
792                 /* Next rule will start here */
793                 options_start_index = options_index;
794                 colon_offset = 0;
795                 in_quotes = FALSE;
796             }
797         }
798     }
799
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);
803
804     return TRUE;
805 }
806
807 /* Delete an individual rule */
808 static gboolean delete_rule(gpointer  key _U_,
809                             gpointer  value,
810                             gpointer  user_data _U_)
811 {
812     Rule_t *rule = (Rule_t*)value;
813     unsigned int n;
814
815     /* Delete strings on heap. */
816     g_free(rule->rule_string);
817     g_free(rule->file);
818     g_free(rule->msg);
819     g_free(rule->classtype);
820     g_free(rule->protocol);
821
822     for (n=0; n < rule->number_contents; n++) {
823         g_free(rule->contents[n].str);
824         g_free(rule->contents[n].translated_str);
825     }
826
827     for (n=0; n < rule->number_references; n++) {
828         g_free(rule->references[n]);
829     }
830
831     snort_debug_printf("Freeing rule at :%p\n", rule);
832     g_free(rule);
833     return TRUE;
834 }
835
836
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)
842 {
843     #define MAX_LINE_LENGTH 4096
844     char line[MAX_LINE_LENGTH];
845     int  line_number = 0;
846
847     snort_debug_printf("parse_config_file(filename=%s, recursion_level=%d)\n", filename, recursion_level);
848
849     if (recursion_level > MAX_CONFIG_FILE_RECURSE_DEPTH) {
850         return;
851     }
852
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)) {
855
856         int line_length;
857         ++line_number;
858
859         /* Nothing interesting to parse */
860         if ((line[0] == '\0') || (line[0] == '#')) {
861             continue;
862         }
863
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'))) {
867             --line_length;
868         }
869         line[line_length] = '\0';
870         if (line_length == 0) {
871             continue;
872         }
873
874         /* Offer line to the various parsing functions.  Could optimise order.. */
875         if (parse_variables_line(snort_config, line)) {
876             continue;
877         }
878         if (parse_references_prefix_file_line(snort_config, line)) {
879             continue;
880         }
881         if (parse_include_file(snort_config, line, dirname, recursion_level)) {
882             continue;
883         }
884         if (parse_rule(snort_config, line, filename, line_number, line_length)) {
885             snort_config->stat_rules++;
886             continue;
887         }
888     }
889 }
890
891
892
893 /* Create the global ConfigParser */
894 void create_config(SnortConfig_t **snort_config, const char *snort_config_file)
895 {
896     gchar* dirname;
897     gchar* basename;
898     FILE *config_file_fd;
899
900     snort_debug_printf("create_config (%s)\n", snort_config_file);
901
902     *snort_config = (SnortConfig_t*)g_malloc(sizeof(SnortConfig_t));
903     memset(*snort_config, 0, sizeof(SnortConfig_t));
904
905     /* Create rule table */
906     (*snort_config)->rules = g_hash_table_new(g_direct_hash, g_direct_equal);
907
908     /* Create reference prefix table */
909     (*snort_config)->references_prefixes = g_hash_table_new(string_hash, string_equal);
910
911     /* Vars tables */
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);
915
916     /* Extract separate directory and filename. */
917     dirname =  g_path_get_dirname(snort_config_file);
918     basename =  g_path_get_basename(snort_config_file);
919
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);
924         return;
925     }
926
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 */);
929
930     g_free(dirname);
931     g_free(basename);
932
933     fclose(config_file_fd);
934 }
935
936
937 /* Delete the entire config */
938 void delete_config(SnortConfig_t **snort_config)
939 {
940     snort_debug_printf("delete_config()\n");
941
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);
945
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);
949
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);
957
958     g_free(*snort_config);
959
960     *snort_config = NULL;
961 }
962
963 /* Look for a rule corresponding to the given SID */
964 Rule_t *get_rule(SnortConfig_t *snort_config, guint32 sid)
965 {
966     if ((snort_config == NULL) || (snort_config->rules == NULL)) {
967         return NULL;
968     }
969     else {
970         return (Rule_t*)g_hash_table_lookup(snort_config->rules, GUINT_TO_POINTER(sid));
971     }
972 }
973
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)
978 {
979     *number_rules_files = snort_config->stat_rules_files;
980     *number_rules = snort_config->stat_rules;
981     *alerts_detected = snort_config->stat_alerts_detected;
982     Rule_t *rule;
983
984     /* Look up rule and get current/total matches */
985     rule = get_rule(snort_config, sid);
986     if (rule) {
987         *this_rule_alerts_detected = rule->matches_seen;
988     }
989     else {
990         *this_rule_alerts_detected = 0;
991     }
992 }
993
994 /* Reset stats on individual rule */
995 static void reset_rule_stats(gpointer  key _U_,
996                              gpointer  value,
997                              gpointer  user_data _U_)
998 {
999     Rule_t *rule = (Rule_t*)value;
1000     rule->matches_seen = 0;
1001 }
1002
1003 /* Reset stats on all rules */
1004 void reset_global_rule_stats(SnortConfig_t *snort_config)
1005 {
1006     /* Reset global stats */
1007     if (snort_config == NULL) {
1008         return;
1009     }
1010     snort_config->stat_alerts_detected = 0;
1011
1012     /* Iterate over all rules, resetting the stats of each */
1013     g_hash_table_foreach(snort_config->rules, reset_rule_stats, NULL);
1014 }
1015
1016
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)
1022 {
1023     static unsigned char values[256];
1024     static gboolean values_set = FALSE;
1025
1026     if (!values_set) {
1027         /* Set table once and for all */
1028         unsigned char ch;
1029         for (ch='a'; ch <= 'f'; ch++) {
1030             values[ch] = 0xa + (ch-'a');
1031         }
1032         for (ch='A'; ch <= 'F'; ch++) {
1033             values[ch] = 0xa + (ch-'A');
1034         }
1035         for (ch='0'; ch <= '9'; ch++) {
1036             values[ch] = (ch-'0');
1037         }
1038         values_set = TRUE;
1039     }
1040
1041     return values[(unsigned char)c];
1042 }
1043
1044 /* Go through string, converting hex digits into guint8, and removing escape characters. */
1045 guint content_convert_to_binary(content_t *content)
1046 {
1047     int output_idx = 0;
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 */
1051     char c;
1052     int n;
1053     gboolean have_backslash = FALSE;
1054     static gchar binary_str[1024];
1055
1056     /* Just return length if have previously translated in binary string. */
1057     if (content->translated) {
1058         return content->translated_length;
1059     }
1060
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];
1064         if (c == '|') {
1065             /* Flip binary mode */
1066             in_binary_mode = !in_binary_mode;
1067             continue;
1068         }
1069
1070         if (!in_binary_mode) {
1071             /* Not binary mode. Copying characters into output buffer, but watching out for escaped chars. */
1072             if (!have_backslash) {
1073                 if (c == '\\') {
1074                     /* Just note that we have a backslash */
1075                     have_backslash = TRUE;
1076                     continue;
1077                 }
1078                 else {
1079                     /* Just copy the character straight into output. */
1080                     binary_str[output_idx++] = (unsigned char)c;
1081                 }
1082             }
1083             else {
1084                 /* Currently have a backslash. Reset flag. */
1085                 have_backslash = 0;
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;
1089             }
1090         }
1091         else {
1092             /* Binary mode. Handle pairs of hex digits and translate into guint8 */
1093             if (c == ' ') {
1094                 /* Ignoring inside binary mode */
1095                 continue;
1096             }
1097             else {
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;
1103                 }
1104                 else {
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;
1109                 }
1110             }
1111         }
1112     }
1113
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;
1119
1120     return output_idx;
1121 }
1122
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)
1126 {
1127     guint pcre_length, i, end_delimiter_offset = 0;
1128
1129     /* Return if already converted */
1130     if (content->translated_str) {
1131         return TRUE;
1132     }
1133
1134     pcre_length = (guint)strlen(content->str);
1135
1136     /* Start with content->str */
1137     if (pcre_length < 3) {
1138         /* Can't be valid.  Expect /regex/[modifiers] */
1139         return FALSE;
1140     }
1141
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 */
1145         return FALSE;
1146     }
1147
1148     /* Verify that string starts with / */
1149     if (content->str[0] != '/') {
1150         return FALSE;
1151     }
1152
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;
1157             break;
1158         }
1159         else {
1160             switch (content->str[i]) {
1161                 case 'i':
1162                     content->pcre_case_insensitive = TRUE;
1163                     break;
1164                 case 's':
1165                     content->pcre_dot_includes_newline = TRUE;
1166                     break;
1167                 case 'B':
1168                     content->pcre_raw = TRUE;
1169                     break;
1170                 case 'm':
1171                     content->pcre_multiline = TRUE;
1172                     break;
1173
1174                 default:
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]);
1179                     break;
1180             }
1181         }
1182     }
1183     if (end_delimiter_offset == 0) {
1184         /* Didn't find it */
1185         return FALSE;
1186     }
1187
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;
1194
1195     return TRUE;
1196 }
1197
1198 /*
1199  * Editor modelines  -  http://www.wireshark.org/tools/modelines.html
1200  *
1201  * Local variables:
1202  * c-basic-offset: 4
1203  * tab-width: 8
1204  * indent-tabs-mode: nil
1205  * End:
1206  *
1207  * vi: set shiftwidth=4 tabstop=8 expandtab:
1208  * :indentSize=4:tabSize=8:noTabs=true:
1209  */