/src/wireshark/epan/dissectors/packet-snort-config.c
Line | Count | Source |
1 | | /* packet-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 | 0 | #define WS_LOG_DOMAIN "packet-snort-config" |
13 | | #include "config.h" |
14 | | #include <wireshark.h> |
15 | | |
16 | | #include <stdlib.h> |
17 | | #include <string.h> |
18 | | |
19 | | #include <wsutil/file_util.h> |
20 | | #include <wsutil/strtoi.h> |
21 | | #include <wsutil/report_message.h> |
22 | | |
23 | | #include "packet-snort-config.h" |
24 | | #include "ws_attributes.h" |
25 | | |
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); |
28 | | |
29 | | /* Skip white space from 'source', return pointer to first non-whitespace char */ |
30 | | static const char *skipWhiteSpace(const char *source, int *accumulated_offset) |
31 | 0 | { |
32 | 0 | int offset = 0; |
33 | | |
34 | | /* Skip any leading whitespace */ |
35 | 0 | while ((source[offset] == ' ') || (source[offset] == '\t')) { |
36 | 0 | offset++; |
37 | 0 | } |
38 | |
|
39 | 0 | *accumulated_offset += offset; |
40 | 0 | return source + offset; |
41 | 0 | } |
42 | | |
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) |
51 | 0 | { |
52 | 0 | static char static_buffer[1024]; |
53 | 0 | int offset = 0; |
54 | |
|
55 | 0 | const char *source_proper = skipWhiteSpace(source, accumulated_length); |
56 | |
|
57 | 0 | while (source_proper[offset] != '\0' && source_proper[offset] != delimeter) { |
58 | 0 | offset++; |
59 | 0 | } |
60 | |
|
61 | 0 | *length = offset; |
62 | 0 | *accumulated_length += offset; |
63 | 0 | if (copy) { |
64 | | /* Copy into new string */ |
65 | 0 | char *new_string = g_strndup(source_proper, offset+1); |
66 | 0 | new_string[offset] = '\0'; |
67 | 0 | return new_string; |
68 | 0 | } |
69 | 0 | else { |
70 | | /* Return in static buffer */ |
71 | 0 | memcpy(&static_buffer, source_proper, offset); |
72 | 0 | static_buffer[offset] = '\0'; |
73 | 0 | return static_buffer; |
74 | 0 | } |
75 | 0 | } |
76 | | |
77 | | /* Add a new content field to the rule */ |
78 | | static bool rule_add_content(Rule_t *rule, const char *content_string, bool negated) |
79 | 0 | { |
80 | 0 | if (rule->number_contents < MAX_CONTENT_ENTRIES) { |
81 | 0 | content_t *new_content = &(rule->contents[rule->number_contents++]); |
82 | 0 | new_content->str = g_strdup(content_string); |
83 | 0 | new_content->negation = negated; |
84 | 0 | rule->last_added_content = new_content; |
85 | 0 | return true; |
86 | 0 | } |
87 | 0 | return false; |
88 | 0 | } |
89 | | |
90 | | /* Set the nocase property for a rule */ |
91 | | static void rule_set_content_nocase(Rule_t *rule) |
92 | 0 | { |
93 | 0 | if (rule->last_added_content) { |
94 | 0 | rule->last_added_content->nocase = true; |
95 | 0 | } |
96 | 0 | } |
97 | | |
98 | | /* Set the offset property of a content field */ |
99 | | static void rule_set_content_offset(Rule_t *rule, int value) |
100 | 0 | { |
101 | 0 | if (rule->last_added_content) { |
102 | 0 | rule->last_added_content->offset = value; |
103 | 0 | rule->last_added_content->offset_set = true; |
104 | 0 | } |
105 | 0 | } |
106 | | |
107 | | /* Set the depth property of a content field */ |
108 | | static void rule_set_content_depth(Rule_t *rule, unsigned value) |
109 | 0 | { |
110 | 0 | if (rule->last_added_content) { |
111 | 0 | rule->last_added_content->depth = value; |
112 | 0 | } |
113 | 0 | } |
114 | | |
115 | | /* Set the distance property of a content field */ |
116 | | static void rule_set_content_distance(Rule_t *rule, int value) |
117 | 0 | { |
118 | 0 | if (rule->last_added_content) { |
119 | 0 | rule->last_added_content->distance = value; |
120 | 0 | rule->last_added_content->distance_set = true; |
121 | 0 | } |
122 | 0 | } |
123 | | |
124 | | /* Set the distance property of a content field */ |
125 | | static void rule_set_content_within(Rule_t *rule, unsigned value) |
126 | 0 | { |
127 | 0 | if (rule->last_added_content) { |
128 | | /* Assuming won't be 0... */ |
129 | 0 | rule->last_added_content->within = value; |
130 | 0 | } |
131 | 0 | } |
132 | | |
133 | | /* Set the fastpattern property of a content field */ |
134 | | static void rule_set_content_fast_pattern(Rule_t *rule) |
135 | 0 | { |
136 | 0 | if (rule->last_added_content) { |
137 | 0 | rule->last_added_content->fastpattern = true; |
138 | 0 | } |
139 | 0 | } |
140 | | |
141 | | /* Set the rawbytes property of a content field */ |
142 | | static void rule_set_content_rawbytes(Rule_t *rule) |
143 | 0 | { |
144 | 0 | if (rule->last_added_content) { |
145 | 0 | rule->last_added_content->rawbytes = true; |
146 | 0 | } |
147 | 0 | } |
148 | | |
149 | | /* Set the http_method property of a content field */ |
150 | | static void rule_set_content_http_method(Rule_t *rule) |
151 | 0 | { |
152 | 0 | if (rule->last_added_content) { |
153 | 0 | rule->last_added_content->http_method = true; |
154 | 0 | } |
155 | 0 | } |
156 | | |
157 | | /* Set the http_client property of a content field */ |
158 | | static void rule_set_content_http_client_body(Rule_t *rule) |
159 | 0 | { |
160 | 0 | if (rule->last_added_content) { |
161 | 0 | rule->last_added_content->http_client_body = true; |
162 | 0 | } |
163 | 0 | } |
164 | | |
165 | | /* Set the http_cookie property of a content field */ |
166 | | static void rule_set_content_http_cookie(Rule_t *rule) |
167 | 0 | { |
168 | 0 | if (rule->last_added_content) { |
169 | 0 | rule->last_added_content->http_cookie = true; |
170 | 0 | } |
171 | 0 | } |
172 | | |
173 | | /* Set the http_UserAgent property of a content field */ |
174 | | static void rule_set_content_http_user_agent(Rule_t *rule) |
175 | 0 | { |
176 | 0 | if (rule->last_added_content) { |
177 | 0 | rule->last_added_content->http_user_agent = true; |
178 | 0 | } |
179 | 0 | } |
180 | | |
181 | | /* Add a uricontent field to the rule */ |
182 | | static bool rule_add_uricontent(Rule_t *rule, const char *uricontent_string, bool negated) |
183 | 0 | { |
184 | 0 | if (rule_add_content(rule, uricontent_string, negated)) { |
185 | 0 | rule->last_added_content->content_type = UriContent; |
186 | 0 | return true; |
187 | 0 | } |
188 | 0 | return false; |
189 | 0 | } |
190 | | |
191 | | /* This content field now becomes a uricontent after seeing modifier */ |
192 | | static void rule_set_http_uri(Rule_t *rule) |
193 | 0 | { |
194 | 0 | if (rule->last_added_content != NULL) { |
195 | 0 | rule->last_added_content->content_type = UriContent; |
196 | 0 | } |
197 | 0 | } |
198 | | |
199 | | /* Add a pcre field to the rule */ |
200 | | static bool rule_add_pcre(Rule_t *rule, const char *pcre_string) |
201 | 0 | { |
202 | 0 | if (rule_add_content(rule, pcre_string, false)) { |
203 | 0 | rule->last_added_content->content_type = Pcre; |
204 | 0 | return true; |
205 | 0 | } |
206 | 0 | return false; |
207 | 0 | } |
208 | | |
209 | | /* Set the rule's classtype field */ |
210 | | static bool rule_set_classtype(Rule_t *rule, const char *classtype) |
211 | 0 | { |
212 | 0 | rule->classtype = g_strdup(classtype); |
213 | 0 | return true; |
214 | 0 | } |
215 | | |
216 | | /* Add a reference string to the rule */ |
217 | | static void rule_add_reference(Rule_t *rule, const char *reference_string) |
218 | 0 | { |
219 | 0 | if (rule->number_references < MAX_REFERENCE_ENTRIES) { |
220 | 0 | rule->references[rule->number_references++] = g_strdup(reference_string); |
221 | 0 | } |
222 | 0 | } |
223 | | |
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) |
227 | 0 | { |
228 | 0 | void *original_key = NULL; |
229 | 0 | void *value = NULL; |
230 | | |
231 | | /* Make sure field+1 not NULL. */ |
232 | 0 | if (strlen(field) < 2) { |
233 | 0 | return; |
234 | 0 | } |
235 | | |
236 | | /* Make sure there is room for another entry */ |
237 | 0 | if (rule->relevant_vars.num_ip_vars >= MAX_RULE_IP_VARS) { |
238 | 0 | return; |
239 | 0 | } |
240 | | |
241 | | /* TODO: a loop re-looking up the answer until its not just another ipvar! */ |
242 | 0 | if (g_hash_table_lookup_extended(snort_config->ipvars, field+1, &original_key, &value)) { |
243 | |
|
244 | 0 | rule->relevant_vars.ip_vars[rule->relevant_vars.num_ip_vars].name = (char*)original_key; |
245 | 0 | rule->relevant_vars.ip_vars[rule->relevant_vars.num_ip_vars].value = (char*)value; |
246 | |
|
247 | 0 | rule->relevant_vars.num_ip_vars++; |
248 | 0 | } |
249 | 0 | } |
250 | | |
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) |
254 | 0 | { |
255 | 0 | void *original_key = NULL; |
256 | 0 | void *value = NULL; |
257 | | |
258 | | /* Make sure field+1 not NULL. */ |
259 | 0 | if (strlen(field) < 2) { |
260 | 0 | return; |
261 | 0 | } |
262 | | |
263 | | /* Make sure there is room for another entry */ |
264 | 0 | if (rule->relevant_vars.num_port_vars >= MAX_RULE_PORT_VARS) { |
265 | 0 | return; |
266 | 0 | } |
267 | | |
268 | | /* TODO: a loop re-looking up the answer until its not just another portvar! */ |
269 | 0 | if (g_hash_table_lookup_extended(snort_config->portvars, field+1, &original_key, &value)) { |
270 | 0 | rule->relevant_vars.port_vars[rule->relevant_vars.num_port_vars].name = (char*)original_key; |
271 | 0 | rule->relevant_vars.port_vars[rule->relevant_vars.num_port_vars].value = (char*)value; |
272 | |
|
273 | 0 | rule->relevant_vars.num_port_vars++; |
274 | 0 | } |
275 | 0 | } |
276 | | |
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) |
279 | 0 | { |
280 | 0 | int length; |
281 | 0 | int accumulated_length = 0; |
282 | 0 | char *field; |
283 | | |
284 | | /* No need to do this twice */ |
285 | 0 | if (rule->relevant_vars.relevant_vars_set) { |
286 | 0 | return; |
287 | 0 | } |
288 | | |
289 | | /* Walk tokens up to the options, and look up ones that are addresses or ports. */ |
290 | | |
291 | | /* Skip "alert" */ |
292 | 0 | read_token(rule->rule_string+accumulated_length, ' ', &length, &accumulated_length, false); |
293 | | |
294 | | /* Skip protocol. */ |
295 | 0 | read_token(rule->rule_string+accumulated_length, ' ', &length, &accumulated_length, false); |
296 | | |
297 | | /* Read source address */ |
298 | 0 | field = read_token(rule->rule_string+accumulated_length, ' ', &length, &accumulated_length, false); |
299 | 0 | rule_check_ip_vars(snort_config, rule, field); |
300 | | |
301 | | /* Read source port */ |
302 | 0 | field = read_token(rule->rule_string+accumulated_length, ' ', &length, &accumulated_length, false); |
303 | 0 | rule_check_port_vars(snort_config, rule, field); |
304 | | |
305 | | /* Read direction */ |
306 | 0 | read_token(rule->rule_string+accumulated_length, ' ', &length, &accumulated_length, false); |
307 | | |
308 | | /* Dest address */ |
309 | 0 | field = read_token(rule->rule_string+accumulated_length, ' ', &length, &accumulated_length, false); |
310 | 0 | rule_check_ip_vars(snort_config, rule, field); |
311 | | |
312 | | /* Dest port */ |
313 | 0 | field = read_token(rule->rule_string+accumulated_length, ' ', &length, &accumulated_length, false); |
314 | 0 | rule_check_port_vars(snort_config, rule, field); |
315 | | |
316 | | /* Set flag so won't do again for this rule */ |
317 | 0 | rule->relevant_vars.relevant_vars_set = true; |
318 | 0 | } |
319 | | |
320 | | |
321 | | typedef enum vartype_e { var, ipvar, portvar, unknownvar } vartype_e; |
322 | | |
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) |
325 | 0 | { |
326 | 0 | vartype_e var_type = unknownvar; |
327 | |
|
328 | 0 | const char* variable_type; |
329 | 0 | char * variable_name; |
330 | 0 | char * value; |
331 | |
|
332 | 0 | int length; |
333 | 0 | int accumulated_length = 0; |
334 | | |
335 | | /* Get variable type */ |
336 | 0 | variable_type = read_token(line, ' ', &length, &accumulated_length, false); |
337 | 0 | if (variable_type == NULL) { |
338 | 0 | return false; |
339 | 0 | } |
340 | | |
341 | 0 | if (strncmp(variable_type, "var", 3) == 0) { |
342 | 0 | var_type = var; |
343 | 0 | } |
344 | 0 | else if (strncmp(variable_type, "ipvar", 5) == 0) { |
345 | 0 | var_type = ipvar; |
346 | 0 | } |
347 | 0 | else if (strncmp(variable_type, "portvar", 7) == 0) { |
348 | 0 | var_type = portvar; |
349 | 0 | } |
350 | 0 | else { |
351 | 0 | return false; |
352 | 0 | } |
353 | | |
354 | | /* Get variable name */ |
355 | 0 | variable_name = read_token(line+ accumulated_length, ' ', &length, &accumulated_length, true); |
356 | 0 | if (variable_name == NULL) { |
357 | 0 | return false; |
358 | 0 | } |
359 | | |
360 | | /* Now value */ |
361 | 0 | value = read_token(line + accumulated_length, ' ', &length, &accumulated_length, true); |
362 | 0 | if (value == NULL) { |
363 | 0 | return false; |
364 | 0 | } |
365 | | |
366 | | /* Add (name->value) to table according to variable type. */ |
367 | 0 | switch (var_type) { |
368 | 0 | case var: |
369 | 0 | if (strcmp(variable_name, "RULE_PATH") == 0) { |
370 | | /* This can be relative or absolute. */ |
371 | 0 | snort_config->rule_path = value; |
372 | 0 | snort_config->rule_path_is_absolute = g_path_is_absolute(value); |
373 | 0 | ws_debug("rule_path set to %s (is_absolute=%d)", |
374 | 0 | snort_config->rule_path, snort_config->rule_path_is_absolute); |
375 | 0 | } |
376 | 0 | g_hash_table_insert(snort_config->vars, variable_name, value); |
377 | 0 | break; |
378 | 0 | case ipvar: |
379 | 0 | g_hash_table_insert(snort_config->ipvars, variable_name, value); |
380 | 0 | break; |
381 | 0 | case portvar: |
382 | 0 | g_hash_table_insert(snort_config->portvars, variable_name, value); |
383 | 0 | break; |
384 | | |
385 | 0 | default: |
386 | 0 | return false; |
387 | 0 | } |
388 | | |
389 | 0 | return false; |
390 | 0 | } |
391 | | |
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) |
394 | 0 | { |
395 | 0 | unsigned total=0, n=0; |
396 | 0 | const char *key_string = (const char *)key; |
397 | 0 | char c = key_string[n]; |
398 | |
|
399 | 0 | while (c != '\0') { |
400 | 0 | total += (int)c; |
401 | 0 | c = key_string[++n]; |
402 | 0 | } |
403 | 0 | return total; |
404 | 0 | } |
405 | | |
406 | | /* Comparison function for where key is a string. Simple comparison using strcmp() */ |
407 | | static gboolean string_equal(const void *a, const void *b) |
408 | 0 | { |
409 | 0 | const char *stringa = (const char*)a; |
410 | 0 | const char *stringb = (const char*)b; |
411 | |
|
412 | 0 | return (strcmp(stringa, stringb) == 0); |
413 | 0 | } |
414 | | |
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) |
417 | 0 | { |
418 | 0 | char *prefix_name, *prefix_value; |
419 | 0 | int length=0, accumulated_length=0; |
420 | 0 | int n; |
421 | |
|
422 | 0 | if (strncmp(line, "config reference: ", 18) != 0) { |
423 | 0 | return false; |
424 | 0 | } |
425 | | |
426 | | /* Read the prefix and value */ |
427 | 0 | const char *source = line+18; |
428 | 0 | prefix_name = read_token(source, ' ', &length, &accumulated_length, true); |
429 | | /* Store all name chars in lower case. */ |
430 | 0 | for (n=0; prefix_name[n] != '\0'; n++) { |
431 | 0 | prefix_name[n] = g_ascii_tolower(prefix_name[n]); |
432 | 0 | } |
433 | |
|
434 | 0 | prefix_value = read_token(source+accumulated_length, ' ', &length, &accumulated_length, true); |
435 | | |
436 | | /* Add entry into table */ |
437 | 0 | g_hash_table_insert(snort_config->references_prefixes, prefix_name, prefix_value); |
438 | |
|
439 | 0 | return false; |
440 | 0 | } |
441 | | |
442 | | /* Try to expand the reference using the prefixes stored in the config */ |
443 | | char *expand_reference(SnortConfig_t *snort_config, char *reference) |
444 | 0 | { |
445 | 0 | static char expanded_reference[512]; |
446 | 0 | int length = (int)strlen(reference); |
447 | 0 | int accumulated_length = 0; |
448 | | |
449 | | /* Extract up to ',', then substitute prefix! */ |
450 | 0 | ws_debug("expand_reference(%s)", reference); |
451 | 0 | char *prefix = read_token(reference, ',', &length, &accumulated_length, false); |
452 | |
|
453 | 0 | if (*prefix != '\0') { |
454 | | /* Convert to lowercase before lookup */ |
455 | 0 | unsigned n; |
456 | 0 | for (n=0; prefix[n] != '\0'; n++) { |
457 | 0 | prefix[n] = g_ascii_tolower(prefix[n]); |
458 | 0 | } |
459 | | |
460 | | /* Look up prefix in table. */ |
461 | 0 | const char* prefix_replacement; |
462 | 0 | prefix_replacement = (char*)g_hash_table_lookup(snort_config->references_prefixes, prefix); |
463 | | |
464 | | /* Append prefix and remainder, and return!!!! */ |
465 | 0 | if (prefix_replacement) { |
466 | 0 | snprintf(expanded_reference, 512, "%s%s", prefix_replacement, reference+length+1); |
467 | 0 | return expanded_reference; |
468 | 0 | } |
469 | 0 | else { |
470 | | /* Just return the original reference */ |
471 | 0 | return reference; |
472 | 0 | } |
473 | |
|
474 | 0 | } |
475 | 0 | return "ERROR: Reference didn't contain prefix and ','!"; |
476 | 0 | } |
477 | | |
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) |
482 | 0 | { |
483 | 0 | snort_config->stat_alerts_detected++; |
484 | 0 | *global_match_number = snort_config->stat_alerts_detected; |
485 | 0 | if (rule != NULL) { |
486 | 0 | *rule_match_number = ++rule->matches_seen; |
487 | 0 | } |
488 | 0 | } |
489 | | |
490 | | |
491 | | |
492 | | /* Delete an individual entry from a string table. */ |
493 | | static gboolean delete_string_entry(void *key, |
494 | | void *value, |
495 | | void *user_data _U_) |
496 | 0 | { |
497 | 0 | char *key_string = (char*)key; |
498 | 0 | char *value_string = (char*)value; |
499 | |
|
500 | 0 | g_free(key_string); |
501 | 0 | g_free(value_string); |
502 | |
|
503 | 0 | return TRUE; |
504 | 0 | } |
505 | | |
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) |
509 | 0 | { |
510 | 0 | int length; |
511 | 0 | int accumulated_length = 0; |
512 | 0 | char *include_filename; |
513 | | |
514 | | /* Look for "include " */ |
515 | 0 | const char *include_token = read_token(line, ' ', &length, &accumulated_length, false); |
516 | 0 | if (strlen(include_token) == 0) { |
517 | 0 | return false; |
518 | 0 | } |
519 | 0 | if (strncmp(include_token, "include", 7) != 0) { |
520 | 0 | return false; |
521 | 0 | } |
522 | | |
523 | | /* Read the filename */ |
524 | 0 | include_filename = read_token(line+accumulated_length, ' ', &length, &accumulated_length, false); |
525 | 0 | if (*include_filename != '\0') { |
526 | 0 | FILE *new_config_fd; |
527 | 0 | char *substituted_filename; |
528 | 0 | bool is_rule_file = false; |
529 | | |
530 | | /* May need to substitute variables into include path. */ |
531 | 0 | 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 | 0 | if (snort_config->rule_path_is_absolute) { |
535 | | /* Rule path is absolute, so it can go at start */ |
536 | 0 | substituted_filename = g_build_path(G_DIR_SEPARATOR_S, |
537 | 0 | snort_config->rule_path, |
538 | 0 | include_filename + 11, |
539 | 0 | NULL); |
540 | 0 | } |
541 | 0 | else { |
542 | | /* Rule path is relative to config directory, so it goes first */ |
543 | 0 | substituted_filename = g_build_path(G_DIR_SEPARATOR_S, |
544 | 0 | config_directory, |
545 | 0 | snort_config->rule_path, |
546 | 0 | include_filename + 11, |
547 | 0 | NULL); |
548 | 0 | } |
549 | 0 | is_rule_file = true; |
550 | 0 | } |
551 | 0 | else { |
552 | | /* No $RULE_PATH, just use directory and filename */ |
553 | | /* But may not even need directory if included_folder is absolute! */ |
554 | 0 | if (!g_path_is_absolute(include_filename)) { |
555 | 0 | substituted_filename = g_build_path(G_DIR_SEPARATOR_S, |
556 | 0 | config_directory, include_filename, NULL); |
557 | 0 | } |
558 | 0 | else { |
559 | 0 | substituted_filename = g_strdup(include_filename); |
560 | 0 | } |
561 | 0 | } |
562 | | |
563 | | /* Try to open the file. */ |
564 | 0 | new_config_fd = ws_fopen(substituted_filename, "r"); |
565 | 0 | if (new_config_fd == NULL) { |
566 | 0 | ws_debug("Failed to open config file %s", substituted_filename); |
567 | 0 | report_failure("Snort dissector: Failed to open config file %s\n", substituted_filename); |
568 | 0 | g_free(substituted_filename); |
569 | 0 | return false; |
570 | 0 | } |
571 | | |
572 | | /* Parse the file */ |
573 | 0 | if (is_rule_file) { |
574 | 0 | snort_config->stat_rules_files++; |
575 | 0 | } |
576 | 0 | parse_config_file(snort_config, new_config_fd, substituted_filename, config_directory, recursion_level + 1); |
577 | 0 | g_free(substituted_filename); |
578 | | |
579 | | /* Close the file */ |
580 | 0 | fclose(new_config_fd); |
581 | |
|
582 | 0 | return true; |
583 | 0 | } |
584 | 0 | return false; |
585 | 0 | } |
586 | | |
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) |
589 | 0 | { |
590 | 0 | static char name[1024], value[1024]; |
591 | 0 | name[0] = '\0'; |
592 | 0 | value[0] = '\0'; |
593 | 0 | int value_length = 0; |
594 | 0 | int32_t value_i32 = 0; |
595 | 0 | uint32_t value_u32 = 0; |
596 | 0 | uint16_t value_u16 = 0; |
597 | 0 | const char *endptr; // Just to ignore trailing whitespace |
598 | 0 | int spaces_after_colon = 0; |
599 | |
|
600 | 0 | if (colon_offset != 0) { |
601 | | /* Name and value */ |
602 | 0 | (void) g_strlcpy(name, options+option_start_offset, colon_offset-option_start_offset); |
603 | 0 | if (options[colon_offset] == ' ') { |
604 | 0 | spaces_after_colon = 1; |
605 | 0 | } |
606 | 0 | (void) g_strlcpy(value, options+colon_offset+spaces_after_colon, options_end_offset-spaces_after_colon-colon_offset); |
607 | 0 | value_length = (int)strlen(value); |
608 | 0 | } |
609 | 0 | else { |
610 | | /* Just name */ |
611 | 0 | (void) g_strlcpy(name, options+option_start_offset, options_end_offset-option_start_offset); |
612 | 0 | } |
613 | | |
614 | | /* Think this is space at end of all options - don't compare with option names */ |
615 | 0 | if (name[0] == '\0') { |
616 | 0 | return; |
617 | 0 | } |
618 | | |
619 | | /* Process the rule options that we are interested in */ |
620 | 0 | if (strcmp(name, "msg") == 0) { |
621 | 0 | rule->msg = g_strdup(value); |
622 | 0 | } |
623 | 0 | else if (strcmp(name, "sid") == 0) { |
624 | 0 | if (!ws_strtou32(value, &endptr, &value_u32)) { |
625 | 0 | ws_info("failed to parse %s argument", name); |
626 | 0 | return; |
627 | 0 | } |
628 | 0 | rule->sid = value_u32; |
629 | 0 | } |
630 | 0 | else if (strcmp(name, "rev") == 0) { |
631 | 0 | if (!ws_strtou32(value, &endptr, &value_u32)) { |
632 | 0 | ws_info("failed to parse %s argument", name); |
633 | 0 | return; |
634 | 0 | } |
635 | 0 | rule->rev = value_u32; |
636 | 0 | } |
637 | 0 | else if (strcmp(name, "content") == 0) { |
638 | 0 | int value_start = 0; |
639 | |
|
640 | 0 | if (value_length < 3) { |
641 | 0 | return; |
642 | 0 | } |
643 | | |
644 | | /* Need to trim off " ", but first check for ! */ |
645 | 0 | if (value[0] == '!') { |
646 | 0 | value_start = 1; |
647 | 0 | if (value_length < 4) { |
648 | | /* i.e. also need quotes + at least one character */ |
649 | 0 | return; |
650 | 0 | } |
651 | 0 | } |
652 | | |
653 | 0 | value[options_end_offset-colon_offset-spaces_after_colon-2] = '\0'; |
654 | 0 | rule_add_content(rule, value+value_start+1, value_start == 1); |
655 | 0 | } |
656 | 0 | else if (strcmp(name, "uricontent") == 0) { |
657 | 0 | int value_start = 0; |
658 | |
|
659 | 0 | if (value_length < 3) { |
660 | 0 | return; |
661 | 0 | } |
662 | | |
663 | | /* Need to trim off " ", but first check for ! */ |
664 | 0 | if (value[0] == '!') { |
665 | 0 | value_start = 1; |
666 | 0 | if (value_length < 4) { |
667 | 0 | return; |
668 | 0 | } |
669 | 0 | } |
670 | | |
671 | 0 | value[options_end_offset-colon_offset-spaces_after_colon-2] = '\0'; |
672 | 0 | rule_add_uricontent(rule, value+value_start+1, value_start == 1); |
673 | 0 | } |
674 | 0 | else if (strcmp(name, "http_uri") == 0) { |
675 | 0 | rule_set_http_uri(rule); |
676 | 0 | } |
677 | 0 | else if (strcmp(name, "pcre") == 0) { |
678 | 0 | int value_start = 0; |
679 | | |
680 | | /* Need at least opening and closing / */ |
681 | 0 | if (value_length < 3) { |
682 | 0 | return; |
683 | 0 | } |
684 | | |
685 | | /* Not expecting negation (!)... */ |
686 | | |
687 | 0 | value[options_end_offset-colon_offset-spaces_after_colon-2] = '\0'; |
688 | 0 | rule_add_pcre(rule, value+value_start+1); |
689 | 0 | } |
690 | 0 | else if (strcmp(name, "nocase") == 0) { |
691 | 0 | rule_set_content_nocase(rule); |
692 | 0 | } |
693 | 0 | else if (strcmp(name, "offset") == 0) { |
694 | | // Allows values from -65535 to 65535 |
695 | 0 | if (!ws_strtoi32(value, &endptr, &value_i32)) { |
696 | 0 | ws_info("failed to parse %s argument", name); |
697 | 0 | return; |
698 | 0 | } |
699 | 0 | rule_set_content_offset(rule, value_i32); |
700 | 0 | } |
701 | 0 | else if (strcmp(name, "depth") == 0) { |
702 | | // Max value is 65535 |
703 | 0 | if (!ws_strtou16(value, &endptr, &value_u16)) { |
704 | 0 | ws_info("failed to parse %s argument", name); |
705 | 0 | return; |
706 | 0 | } |
707 | 0 | rule_set_content_depth(rule, value_u16); |
708 | 0 | } |
709 | 0 | else if (strcmp(name, "within") == 0) { |
710 | | // Max value is 65535 |
711 | 0 | if (!ws_strtou16(value, &endptr, &value_u16)) { |
712 | 0 | ws_info("failed to parse %s argument", name); |
713 | 0 | return; |
714 | 0 | } |
715 | 0 | rule_set_content_within(rule, value_u16); |
716 | 0 | } |
717 | 0 | else if (strcmp(name, "distance") == 0) { |
718 | | // Allows values from -65535 to 65535 |
719 | 0 | if (!ws_strtoi32(value, &endptr, &value_i32)) { |
720 | 0 | ws_info("failed to parse %s argument", name); |
721 | 0 | return; |
722 | 0 | } |
723 | 0 | rule_set_content_distance(rule, value_i32); |
724 | 0 | } |
725 | 0 | else if (strcmp(name, "fast_pattern") == 0) { |
726 | 0 | rule_set_content_fast_pattern(rule); |
727 | 0 | } |
728 | 0 | else if (strcmp(name, "http_method") == 0) { |
729 | 0 | rule_set_content_http_method(rule); |
730 | 0 | } |
731 | 0 | else if (strcmp(name, "http_client_body") == 0) { |
732 | 0 | rule_set_content_http_client_body(rule); |
733 | 0 | } |
734 | 0 | else if (strcmp(name, "http_cookie") == 0) { |
735 | 0 | rule_set_content_http_cookie(rule); |
736 | 0 | } |
737 | 0 | else if (strcmp(name, "http_user_agent") == 0) { |
738 | 0 | rule_set_content_http_user_agent(rule); |
739 | 0 | } |
740 | 0 | else if (strcmp(name, "rawbytes") == 0) { |
741 | 0 | rule_set_content_rawbytes(rule); |
742 | 0 | } |
743 | 0 | else if (strcmp(name, "classtype") == 0) { |
744 | 0 | rule_set_classtype(rule, value); |
745 | 0 | } |
746 | 0 | else if (strcmp(name, "reference") == 0) { |
747 | 0 | rule_add_reference(rule, value); |
748 | 0 | } |
749 | 0 | else { |
750 | | /* Ignore an option we don't currently handle */ |
751 | 0 | } |
752 | 0 | } |
753 | | |
754 | | /* Parse a Snort alert, return true if successful */ |
755 | | static bool parse_rule(SnortConfig_t *snort_config, char *line, const char *filename, int line_number, int line_length) |
756 | 0 | { |
757 | 0 | const char* options_start; |
758 | 0 | char *options; |
759 | 0 | bool in_quotes = false; |
760 | 0 | int options_start_index = 0, options_index = 0, colon_offset = 0; |
761 | 0 | char c; |
762 | 0 | int length = 0; /* CID 1398227 (bogus - read_token() always sets it) */ |
763 | 0 | Rule_t *rule = NULL; |
764 | | |
765 | | /* Rule will begin with alert */ |
766 | 0 | if (strncmp(line, "alert ", 6) != 0) { |
767 | 0 | return false; |
768 | 0 | } |
769 | | |
770 | | /* Allocate the rule itself */ |
771 | 0 | rule = g_new(Rule_t, 1); |
772 | |
|
773 | 0 | ws_debug("looks like a rule: %s", line); |
774 | 0 | memset(rule, 0, sizeof(Rule_t)); |
775 | |
|
776 | 0 | rule->rule_string = g_strdup(line); |
777 | 0 | rule->file = g_strdup(filename); |
778 | 0 | rule->line_number = line_number; |
779 | | |
780 | | /* Next token is the protocol */ |
781 | 0 | rule->protocol = read_token(line+6, ' ', &length, &length, true); |
782 | | |
783 | | /* Find start of options. */ |
784 | 0 | options_start = strstr(line, "("); |
785 | 0 | if (options_start == NULL) { |
786 | 0 | ws_debug("start of options not found"); |
787 | 0 | g_free(rule); |
788 | 0 | return false; |
789 | 0 | } |
790 | 0 | options_index = (int)(options_start-line) + 1; |
791 | | |
792 | | /* To make parsing simpler, replace final ')' with ';' */ |
793 | 0 | if (line[line_length-1] != ')') { |
794 | 0 | g_free(rule); |
795 | 0 | return false; |
796 | 0 | } |
797 | 0 | else { |
798 | 0 | line[line_length-1] = ';'; |
799 | 0 | } |
800 | | |
801 | | /* Skip any spaces before next option */ |
802 | 0 | while (line[options_index] == ' ') options_index++; |
803 | | |
804 | | /* Now look for next ';', process one option at a time */ |
805 | 0 | options = &line[options_index]; |
806 | 0 | options_index = 0; |
807 | |
|
808 | 0 | while ((c = options[options_index++])) { |
809 | | /* Keep track of whether inside quotes */ |
810 | 0 | if (c == '"') { |
811 | 0 | in_quotes = !in_quotes; |
812 | 0 | } |
813 | | /* Ignore ';' while inside quotes */ |
814 | 0 | if (!in_quotes) { |
815 | 0 | if (c == ':') { |
816 | 0 | colon_offset = options_index; |
817 | 0 | } |
818 | 0 | if (c == ';') { |
819 | | /* End of current option - add to rule. */ |
820 | 0 | process_rule_option(rule, options, options_start_index, options_index, colon_offset); |
821 | | |
822 | | /* Skip any spaces before next option */ |
823 | 0 | while (options[options_index] == ' ') options_index++; |
824 | | |
825 | | /* Next rule will start here */ |
826 | 0 | options_start_index = options_index; |
827 | 0 | colon_offset = 0; |
828 | 0 | in_quotes = false; |
829 | 0 | } |
830 | 0 | } |
831 | 0 | } |
832 | | |
833 | | /* Add rule to map of rules. */ |
834 | 0 | g_hash_table_insert(snort_config->rules, GUINT_TO_POINTER((unsigned)rule->sid), rule); |
835 | 0 | ws_debug("Snort rule with SID=%u added to table", rule->sid); |
836 | |
|
837 | 0 | return true; |
838 | 0 | } |
839 | | |
840 | | /* Delete an individual rule */ |
841 | | static gboolean delete_rule(void * key _U_, |
842 | | void * value, |
843 | | void * user_data _U_) |
844 | 0 | { |
845 | 0 | Rule_t *rule = (Rule_t*)value; |
846 | 0 | unsigned int n; |
847 | | |
848 | | /* Delete strings on heap. */ |
849 | 0 | g_free(rule->rule_string); |
850 | 0 | g_free(rule->file); |
851 | 0 | g_free(rule->msg); |
852 | 0 | g_free(rule->classtype); |
853 | 0 | g_free(rule->protocol); |
854 | |
|
855 | 0 | for (n=0; n < rule->number_contents; n++) { |
856 | 0 | g_free(rule->contents[n].str); |
857 | 0 | g_free(rule->contents[n].translated_str); |
858 | 0 | } |
859 | |
|
860 | 0 | for (n=0; n < rule->number_references; n++) { |
861 | 0 | g_free(rule->references[n]); |
862 | 0 | } |
863 | |
|
864 | 0 | ws_debug("Freeing rule at :%p", rule); |
865 | 0 | g_free(rule); |
866 | 0 | return TRUE; |
867 | 0 | } |
868 | | |
869 | | |
870 | | /* Parse this file, adding details to snort_config. */ |
871 | | /* N.B. using recursion_level to limit stack depth. */ |
872 | 0 | #define MAX_CONFIG_FILE_RECURSE_DEPTH 8 |
873 | | // NOLINTNEXTLINE(misc-no-recursion) |
874 | | static void parse_config_file(SnortConfig_t *snort_config, FILE *config_file_fd, |
875 | | const char *filename, const char *dirname, int recursion_level) |
876 | 0 | { |
877 | 0 | #define MAX_LINE_LENGTH 4096 |
878 | 0 | char line[MAX_LINE_LENGTH]; |
879 | 0 | int line_number = 0; |
880 | |
|
881 | 0 | ws_debug("parse_config_file(filename=%s, recursion_level=%d)", filename, recursion_level); |
882 | |
|
883 | 0 | if (recursion_level > MAX_CONFIG_FILE_RECURSE_DEPTH) { |
884 | 0 | return; |
885 | 0 | } |
886 | | |
887 | | /* Read each line of the file in turn, and see if we want any info from it. */ |
888 | 0 | while (fgets(line, MAX_LINE_LENGTH, config_file_fd)) { |
889 | |
|
890 | 0 | int line_length; |
891 | 0 | ++line_number; |
892 | | |
893 | | /* Nothing interesting to parse */ |
894 | 0 | if ((line[0] == '\0') || (line[0] == '#')) { |
895 | 0 | continue; |
896 | 0 | } |
897 | | |
898 | | /* Trim newline from end */ |
899 | 0 | line_length = (int)strlen(line); |
900 | 0 | while (line_length && ((line[line_length - 1] == '\n') || (line[line_length - 1] == '\r'))) { |
901 | 0 | --line_length; |
902 | 0 | } |
903 | 0 | line[line_length] = '\0'; |
904 | 0 | if (line_length == 0) { |
905 | 0 | continue; |
906 | 0 | } |
907 | | |
908 | | /* Offer line to the various parsing functions. Could optimise order.. */ |
909 | 0 | if (parse_variables_line(snort_config, line)) { |
910 | 0 | continue; |
911 | 0 | } |
912 | 0 | if (parse_references_prefix_file_line(snort_config, line)) { |
913 | 0 | continue; |
914 | 0 | } |
915 | 0 | if (parse_include_file(snort_config, line, dirname, recursion_level)) { |
916 | 0 | continue; |
917 | 0 | } |
918 | 0 | if (parse_rule(snort_config, line, filename, line_number, line_length)) { |
919 | 0 | snort_config->stat_rules++; |
920 | 0 | continue; |
921 | 0 | } |
922 | 0 | } |
923 | 0 | } |
924 | | |
925 | | |
926 | | |
927 | | /* Create the global ConfigParser */ |
928 | | void create_config(SnortConfig_t **snort_config, const char *snort_config_file) |
929 | 0 | { |
930 | 0 | char* dirname; |
931 | 0 | char* basename; |
932 | 0 | FILE *config_file_fd; |
933 | |
|
934 | 0 | ws_debug("create_config (%s)", snort_config_file); |
935 | |
|
936 | 0 | *snort_config = g_new(SnortConfig_t, 1); |
937 | 0 | memset(*snort_config, 0, sizeof(SnortConfig_t)); |
938 | | |
939 | | /* Create rule table */ |
940 | 0 | (*snort_config)->rules = g_hash_table_new(g_direct_hash, g_direct_equal); |
941 | | |
942 | | /* Create reference prefix table */ |
943 | 0 | (*snort_config)->references_prefixes = g_hash_table_new(string_hash, string_equal); |
944 | | |
945 | | /* Vars tables */ |
946 | 0 | (*snort_config)->vars = g_hash_table_new(string_hash, string_equal); |
947 | 0 | (*snort_config)->ipvars = g_hash_table_new(string_hash, string_equal); |
948 | 0 | (*snort_config)->portvars = g_hash_table_new(string_hash, string_equal); |
949 | | |
950 | | /* Extract separate directory and filename. */ |
951 | 0 | dirname = g_path_get_dirname(snort_config_file); |
952 | 0 | basename = g_path_get_basename(snort_config_file); |
953 | | |
954 | | /* Attempt to open the config file */ |
955 | 0 | config_file_fd = ws_fopen(snort_config_file, "r"); |
956 | 0 | if (config_file_fd == NULL) { |
957 | 0 | ws_debug("Failed to open config file %s", snort_config_file); |
958 | 0 | report_failure("Snort dissector: Failed to open config file %s\n", snort_config_file); |
959 | 0 | } |
960 | 0 | else { |
961 | | /* Start parsing from the top-level config file. */ |
962 | 0 | parse_config_file(*snort_config, config_file_fd, snort_config_file, dirname, 1 /* recursion level */); |
963 | 0 | fclose(config_file_fd); |
964 | 0 | } |
965 | |
|
966 | 0 | g_free(dirname); |
967 | 0 | g_free(basename); |
968 | 0 | } |
969 | | |
970 | | |
971 | | /* Delete the entire config */ |
972 | | void delete_config(SnortConfig_t **snort_config) |
973 | 0 | { |
974 | 0 | ws_debug("delete_config()"); |
975 | | |
976 | | /* Iterate over all rules, freeing each one! */ |
977 | 0 | g_hash_table_foreach_remove((*snort_config)->rules, delete_rule, NULL); |
978 | 0 | g_hash_table_destroy((*snort_config)->rules); |
979 | | |
980 | | /* References table */ |
981 | 0 | g_hash_table_foreach_remove((*snort_config)->references_prefixes, delete_string_entry, NULL); |
982 | 0 | g_hash_table_destroy((*snort_config)->references_prefixes); |
983 | | |
984 | | /* Free up variable tables */ |
985 | 0 | g_hash_table_foreach_remove((*snort_config)->vars, delete_string_entry, NULL); |
986 | 0 | g_hash_table_destroy((*snort_config)->vars); |
987 | 0 | g_hash_table_foreach_remove((*snort_config)->ipvars, delete_string_entry, NULL); |
988 | 0 | g_hash_table_destroy((*snort_config)->ipvars); |
989 | 0 | g_hash_table_foreach_remove((*snort_config)->portvars, delete_string_entry, NULL); |
990 | 0 | g_hash_table_destroy((*snort_config)->portvars); |
991 | |
|
992 | 0 | g_free(*snort_config); |
993 | |
|
994 | 0 | *snort_config = NULL; |
995 | 0 | } |
996 | | |
997 | | /* Look for a rule corresponding to the given SID */ |
998 | | Rule_t *get_rule(SnortConfig_t *snort_config, uint32_t sid) |
999 | 0 | { |
1000 | 0 | if ((snort_config == NULL) || (snort_config->rules == NULL)) { |
1001 | 0 | return NULL; |
1002 | 0 | } |
1003 | 0 | else { |
1004 | 0 | return (Rule_t*)g_hash_table_lookup(snort_config->rules, GUINT_TO_POINTER(sid)); |
1005 | 0 | } |
1006 | 0 | } |
1007 | | |
1008 | | /* Fetch some statistics. */ |
1009 | | void get_global_rule_stats(SnortConfig_t *snort_config, unsigned int sid, |
1010 | | unsigned int *number_rules_files, unsigned int *number_rules, |
1011 | | unsigned int *alerts_detected, unsigned int *this_rule_alerts_detected) |
1012 | 0 | { |
1013 | 0 | *number_rules_files = snort_config->stat_rules_files; |
1014 | 0 | *number_rules = snort_config->stat_rules; |
1015 | 0 | *alerts_detected = snort_config->stat_alerts_detected; |
1016 | 0 | const Rule_t *rule; |
1017 | | |
1018 | | /* Look up rule and get current/total matches */ |
1019 | 0 | rule = get_rule(snort_config, sid); |
1020 | 0 | if (rule) { |
1021 | 0 | *this_rule_alerts_detected = rule->matches_seen; |
1022 | 0 | } |
1023 | 0 | else { |
1024 | 0 | *this_rule_alerts_detected = 0; |
1025 | 0 | } |
1026 | 0 | } |
1027 | | |
1028 | | /* Reset stats on individual rule */ |
1029 | | static void reset_rule_stats(void * key _U_, |
1030 | | void * value, |
1031 | | void * user_data _U_) |
1032 | 0 | { |
1033 | 0 | Rule_t *rule = (Rule_t*)value; |
1034 | 0 | rule->matches_seen = 0; |
1035 | 0 | } |
1036 | | |
1037 | | /* Reset stats on all rules */ |
1038 | | void reset_global_rule_stats(SnortConfig_t *snort_config) |
1039 | 0 | { |
1040 | | /* Reset global stats */ |
1041 | 0 | if (snort_config == NULL) { |
1042 | 0 | return; |
1043 | 0 | } |
1044 | 0 | snort_config->stat_alerts_detected = 0; |
1045 | | |
1046 | | /* Iterate over all rules, resetting the stats of each */ |
1047 | 0 | g_hash_table_foreach(snort_config->rules, reset_rule_stats, NULL); |
1048 | 0 | } |
1049 | | |
1050 | | |
1051 | | /*************************************************************************************/ |
1052 | | /* Dealing with content fields and trying to find where it matches within the packet */ |
1053 | | /* Parse content strings to interpret binary and escaped characters. Do this */ |
1054 | | /* so we can look for in frame using memcmp(). */ |
1055 | | static unsigned char content_get_nibble_value(char c) |
1056 | 0 | { |
1057 | 0 | static unsigned char values[256]; |
1058 | 0 | static bool values_set = false; |
1059 | |
|
1060 | 0 | if (!values_set) { |
1061 | | /* Set table once and for all */ |
1062 | 0 | unsigned ch; |
1063 | 0 | for (ch='a'; ch <= 'f'; ch++) { |
1064 | 0 | values[ch] = 0xa + (ch-'a'); |
1065 | 0 | } |
1066 | 0 | for (ch='A'; ch <= 'F'; ch++) { |
1067 | 0 | values[ch] = 0xa + (ch-'A'); |
1068 | 0 | } |
1069 | 0 | for (ch='0'; ch <= '9'; ch++) { |
1070 | 0 | values[ch] = (ch-'0'); |
1071 | 0 | } |
1072 | 0 | values_set = true; |
1073 | 0 | } |
1074 | |
|
1075 | 0 | return values[(unsigned char)c]; |
1076 | 0 | } |
1077 | | |
1078 | | /* Go through string, converting hex digits into uint8_t, and removing escape characters. */ |
1079 | | unsigned content_convert_to_binary(content_t *content) |
1080 | 0 | { |
1081 | 0 | int output_idx = 0; |
1082 | 0 | bool in_binary_mode = false; /* Are we in a binary region of the string? */ |
1083 | 0 | bool have_one_nibble = false; /* Do we have the first nibble of the pair needed to make a byte? */ |
1084 | 0 | unsigned char one_nibble = 0; /* Value of first nibble if we have it */ |
1085 | 0 | char c; |
1086 | 0 | int n; |
1087 | 0 | bool have_backslash = false; |
1088 | 0 | static char binary_str[1024]; |
1089 | | |
1090 | | /* Just return length if have previously translated in binary string. */ |
1091 | 0 | if (content->translated) { |
1092 | 0 | return content->translated_length; |
1093 | 0 | } |
1094 | | |
1095 | | /* Walk over each character, work out what needs to be written into output */ |
1096 | 0 | for (n=0; content->str[n] != '\0'; n++) { |
1097 | 0 | c = content->str[n]; |
1098 | 0 | if (c == '|') { |
1099 | | /* Flip binary mode */ |
1100 | 0 | in_binary_mode = !in_binary_mode; |
1101 | 0 | continue; |
1102 | 0 | } |
1103 | | |
1104 | 0 | if (!in_binary_mode) { |
1105 | | /* Not binary mode. Copying characters into output buffer, but watching out for escaped chars. */ |
1106 | 0 | if (!have_backslash) { |
1107 | 0 | if (c == '\\') { |
1108 | | /* Just note that we have a backslash */ |
1109 | 0 | have_backslash = true; |
1110 | 0 | continue; |
1111 | 0 | } |
1112 | 0 | else { |
1113 | | /* Just copy the character straight into output. */ |
1114 | 0 | binary_str[output_idx++] = (unsigned char)c; |
1115 | 0 | } |
1116 | 0 | } |
1117 | 0 | else { |
1118 | | /* Currently have a backslash. Reset flag. */ |
1119 | 0 | have_backslash = 0; |
1120 | | /* Just copy the character into output. Really, the only characters that should be escaped |
1121 | | are ';' and '\' and '"' */ |
1122 | 0 | binary_str[output_idx++] = (unsigned char)c; |
1123 | 0 | } |
1124 | 0 | } |
1125 | 0 | else { |
1126 | | /* Binary mode. Handle pairs of hex digits and translate into uint8_t */ |
1127 | 0 | if (c == ' ') { |
1128 | | /* Ignoring inside binary mode */ |
1129 | 0 | continue; |
1130 | 0 | } |
1131 | 0 | else { |
1132 | 0 | unsigned char nibble = content_get_nibble_value(c); |
1133 | 0 | if (!have_one_nibble) { |
1134 | | /* Store first nibble of a pair */ |
1135 | 0 | one_nibble = nibble; |
1136 | 0 | have_one_nibble = true; |
1137 | 0 | } |
1138 | 0 | else { |
1139 | | /* Combine both nibbles into a byte */ |
1140 | 0 | binary_str[output_idx++] = (one_nibble << 4) + nibble; |
1141 | | /* Reset flag - looking for new pair of nibbles */ |
1142 | 0 | have_one_nibble = false; |
1143 | 0 | } |
1144 | 0 | } |
1145 | 0 | } |
1146 | 0 | } |
1147 | | |
1148 | | /* Store result for next time. */ |
1149 | 0 | content->translated_str = (unsigned char*)g_malloc(output_idx+1); |
1150 | 0 | memcpy(content->translated_str, binary_str, output_idx+1); |
1151 | 0 | content->translated = true; |
1152 | 0 | content->translated_length = output_idx; |
1153 | |
|
1154 | 0 | return output_idx; |
1155 | 0 | } |
1156 | | |
1157 | | /* In order to use glib's regex library, need to trim |
1158 | | '/' delimiters and any modifiers from the end of the string */ |
1159 | | bool content_convert_pcre_for_regex(content_t *content) |
1160 | 0 | { |
1161 | 0 | unsigned pcre_length, i, end_delimiter_offset = 0; |
1162 | | |
1163 | | /* Return if already converted */ |
1164 | 0 | if (content->translated_str) { |
1165 | 0 | return true; |
1166 | 0 | } |
1167 | | |
1168 | 0 | pcre_length = (unsigned)strlen(content->str); |
1169 | | |
1170 | | /* Start with content->str */ |
1171 | 0 | if (pcre_length < 3) { |
1172 | | /* Can't be valid. Expect /regex/[modifiers] */ |
1173 | 0 | return false; |
1174 | 0 | } |
1175 | | |
1176 | 0 | if (pcre_length >= 512) { |
1177 | | /* Have seen regex library crash on very long expressions |
1178 | | * (830 bytes) as seen in SID=2019326, REV=6 */ |
1179 | 0 | return false; |
1180 | 0 | } |
1181 | | |
1182 | | /* Verify that string starts with / */ |
1183 | 0 | if (content->str[0] != '/') { |
1184 | 0 | return false; |
1185 | 0 | } |
1186 | | |
1187 | | /* Next, look for closing / near end of string */ |
1188 | 0 | for (i=pcre_length-1; i > 2; i--) { |
1189 | 0 | if (content->str[i] == '/') { |
1190 | 0 | end_delimiter_offset = i; |
1191 | 0 | break; |
1192 | 0 | } |
1193 | 0 | else { |
1194 | 0 | switch (content->str[i]) { |
1195 | 0 | case 'i': |
1196 | 0 | content->pcre_case_insensitive = true; |
1197 | 0 | break; |
1198 | 0 | case 's': |
1199 | 0 | content->pcre_dot_includes_newline = true; |
1200 | 0 | break; |
1201 | 0 | case 'B': |
1202 | 0 | content->pcre_raw = true; |
1203 | 0 | break; |
1204 | 0 | case 'm': |
1205 | 0 | content->pcre_multiline = true; |
1206 | 0 | break; |
1207 | | |
1208 | 0 | default: |
1209 | | /* TODO: handle other modifiers that will get seen? */ |
1210 | | /* N.B. 'U' (match in decoded URI buffers) can't be handled, so don't store in flag. */ |
1211 | | /* N.B. not sure if/how to handle 'R' (effectively distance:0) */ |
1212 | 0 | ws_debug("Unhandled pcre modifier '%c'", content->str[i]); |
1213 | 0 | break; |
1214 | 0 | } |
1215 | 0 | } |
1216 | 0 | } |
1217 | 0 | if (end_delimiter_offset == 0) { |
1218 | | /* Didn't find it */ |
1219 | 0 | return false; |
1220 | 0 | } |
1221 | | |
1222 | | /* Store result for next time. */ |
1223 | 0 | content->translated_str = (unsigned char*)g_malloc(end_delimiter_offset); |
1224 | 0 | memcpy(content->translated_str, content->str+1, end_delimiter_offset - 1); |
1225 | 0 | content->translated_str[end_delimiter_offset-1] = '\0'; |
1226 | 0 | content->translated = true; |
1227 | 0 | content->translated_length = end_delimiter_offset - 1; |
1228 | |
|
1229 | | return true; |
1230 | 0 | } |
1231 | | |
1232 | | /* |
1233 | | * Editor modelines - https://www.wireshark.org/tools/modelines.html |
1234 | | * |
1235 | | * Local variables: |
1236 | | * c-basic-offset: 4 |
1237 | | * tab-width: 8 |
1238 | | * indent-tabs-mode: nil |
1239 | | * End: |
1240 | | * |
1241 | | * vi: set shiftwidth=4 tabstop=8 expandtab: |
1242 | | * :indentSize=4:tabSize=8:noTabs=true: |
1243 | | */ |