Revert "TODO epan/dissectors/asn1/kerberos/packet-kerberos-template.c new GSS flags"
[wireshark-sm.git] / epan / dissectors / packet-snort.c
blob4f3acf54d7f645d9da65507017fce36ab62ce76c
1 /* packet-snort.c
3 * Copyright 2011, Jakub Zawadzki <darkjames-ws@darkjames.pl>
4 * Copyright 2016, Martin Mathieson
6 * Google Summer of Code 2011 for The Honeynet Project
7 * Mentors:
8 * Guillaume Arcas <guillaume.arcas (at) retiaire.org>
9 * Jeff Nathan <jeffnathan (at) gmail.com>
11 * Wireshark - Network traffic analyzer
12 * By Gerald Combs <gerald@wireshark.org>
13 * Copyright 1998 Gerald Combs
15 * SPDX-License-Identifier: GPL-2.0-or-later
19 /* TODO:
20 * - sort out threading/channel-sync so works reliably in tshark
21 * - postponed for now, as Qt crashes if call g_main_context_iteration()
22 * at an inopportune time
23 * - have looked into writing a tap that could provide an interface for error messages/events and snort stats,
24 * but not easy as taps are not usually listening when alerts are detected
25 * - for a content/pcre match, find all protocol fields that cover same bytes and show in tree
26 * - other use-cases as suggested in https://sharkfesteurope.wireshark.org/assets/presentations16eu/14.pptx
30 #define WS_LOG_DOMAIN "packet-snort"
31 #include "config.h"
32 #include <wireshark.h>
34 #include <epan/packet.h>
35 #include <epan/prefs.h>
36 #include <epan/expert.h>
37 #include <wsutil/file_util.h>
38 #include <wsutil/report_message.h>
39 #include <wiretap/wtap.h>
41 #include "packet-snort-config.h"
43 /* Forward declarations */
44 void proto_register_snort(void);
45 void proto_reg_handoff_snort(void);
48 static int proto_snort;
50 /* These are from parsing snort fast_alert output and/or looking up snort config */
51 static int hf_snort_raw_alert;
52 static int hf_snort_classification;
53 static int hf_snort_rule;
54 static int hf_snort_msg;
55 static int hf_snort_rev;
56 static int hf_snort_sid;
57 static int hf_snort_generator;
58 static int hf_snort_priority;
59 static int hf_snort_rule_string;
60 static int hf_snort_rule_protocol;
61 static int hf_snort_rule_filename;
62 static int hf_snort_rule_line_number;
63 static int hf_snort_rule_ip_var;
64 static int hf_snort_rule_port_var;
66 static int hf_snort_reassembled_in;
67 static int hf_snort_reassembled_from;
69 /* Patterns to match */
70 static int hf_snort_content;
71 static int hf_snort_uricontent;
72 static int hf_snort_pcre;
74 /* Web links */
75 static int hf_snort_reference;
77 /* General stats about the rule set */
78 static int hf_snort_global_stats;
79 static int hf_snort_global_stats_rule_file_count; /* number of rules files */
80 static int hf_snort_global_stats_rule_count; /* number of rules in config */
82 static int hf_snort_global_stats_total_alerts_count;
83 static int hf_snort_global_stats_alert_match_number;
85 static int hf_snort_global_stats_rule_alerts_count;
86 static int hf_snort_global_stats_rule_match_number;
89 /* Subtrees */
90 static int ett_snort;
91 static int ett_snort_rule;
92 static int ett_snort_global_stats;
94 /* Expert info */
95 static expert_field ei_snort_alert;
96 static expert_field ei_snort_content_not_matched;
98 static dissector_handle_t snort_handle;
101 /*****************************************/
102 /* Preferences */
104 /* Where to look for alerts. */
105 enum alerts_source {
106 FromNowhere, /* disabled */
107 FromRunningSnort,
108 FromUserComments /* see https://blog.packet-foo.com/2015/08/verifying-iocs-with-snort-and-tracewrangler/ */
110 /* By default, dissector is effectively disabled */
111 static int pref_snort_alerts_source = (int)FromNowhere;
113 /* Snort binary and config file */
114 #ifndef _WIN32
115 static const char *pref_snort_binary_filename = "/usr/sbin/snort";
116 static const char *pref_snort_config_filename = "/etc/snort/snort.conf";
117 #else
118 /* Default locations from Snort Windows installer */
119 static const char *pref_snort_binary_filename = "C:\\Snort\\bin\\snort.exe";
120 static const char *pref_snort_config_filename = "C:\\Snort\\etc\\snort.conf";
121 #endif
123 /* Should rule stats be shown in protocol tree? */
124 static bool snort_show_rule_stats;
126 /* Should alerts be added as expert info? */
127 static bool snort_show_alert_expert_info;
129 /* Should we try to attach the alert to the tcp.reassembled_in frame instead of current one? */
130 static bool snort_alert_in_reassembled_frame;
132 /* Should Snort ignore checksum errors (as will likely be seen because of check offloading or
133 * possibly if trying to capture live in a container)? */
134 static bool snort_ignore_checksum_errors = true;
137 /********************************************************/
138 /* Global variable with single parsed snort config */
139 static SnortConfig_t *g_snort_config;
142 /******************************************************/
143 /* This is to keep track of the running Snort process */
144 typedef struct {
145 bool running;
146 bool working;
148 GPid pid;
149 int in, out, err; /* fds for talking to snort process */
151 GString *buf; /* Incomplete alert output that has been read */
152 wtap_dumper *pdh; /* wiretap dumper used to deliver packets to 'in' */
154 GIOChannel *channel; /* IO channel used for readimg stdout (alerts) */
156 wmem_tree_t *alerts_tree; /* Lookup from frame-number -> Alerts_t* */
157 } snort_session_t;
159 /* Global instance of the snort session */
160 static snort_session_t current_session;
162 static bool snort_config_ok = true; /* N.B. Not running test at the moment... */
166 /*************************************************/
167 /* An alert.
168 Created by parsing alert from snort, hopefully with more details linked from matched_rule. */
169 typedef struct Alert_t {
170 /* Rule */
171 uint32_t sid; /* Rule identifier */
172 uint32_t rev; /* Revision number of rule */
173 uint32_t gen; /* Which engine generated alert (not often interesting) */
174 int prio; /* Priority as reported in alert (not usually interesting) */
176 char *raw_alert; /* The whole alert string as reported by snort */
177 bool raw_alert_ts_fixed; /* Set when correct timestamp is restored before displaying */
179 char *msg; /* Rule msg/description as it appears in the alert */
180 char *classification; /* Classification type of rule */
182 Rule_t *matched_rule; /* Link to corresponding rule from snort config */
184 uint32_t original_frame;
185 uint32_t reassembled_frame;
187 /* Stats for this alert among the capture file. */
188 unsigned int overall_match_number;
189 unsigned int rule_match_number;
190 } Alert_t;
192 /* Can have multiple alerts fire on same frame, so define this container */
193 typedef struct Alerts_t {
194 /* N.B. Snort limit appears to be 6 (at least with default config..) */
195 #define MAX_ALERTS_PER_FRAME 8
196 Alert_t alerts[MAX_ALERTS_PER_FRAME];
197 unsigned num_alerts;
198 } Alerts_t;
201 /* Add an alert to the map stored in current_session.
202 * N.B. even if preference 'snort_alert_in_reassembled_frame' is set,
203 * need to set to original frame now, and try to update it in the 2nd pass... */
204 static void add_alert_to_session_tree(unsigned frame_number, Alert_t *alert)
206 /* First look up tree to see if there is an existing entry */
207 Alerts_t *alerts = (Alerts_t*)wmem_tree_lookup32(current_session.alerts_tree, frame_number);
208 if (alerts == NULL) {
209 /* Create a new entry for the table */
210 alerts = g_new(Alerts_t, 1);
211 /* Deep copy of alert */
212 alerts->alerts[0] = *alert;
213 alerts->num_alerts = 1;
214 wmem_tree_insert32(current_session.alerts_tree, frame_number, alerts);
216 else {
217 /* See if there is room in the existing Alerts_t struct for this frame */
218 if (alerts->num_alerts < MAX_ALERTS_PER_FRAME) {
219 /* Deep copy of alert */
220 alerts->alerts[alerts->num_alerts++] = *alert;
226 /******************************************************************/
228 /* Given an alert struct, look up by Snort ID (sid) and try to fill in other details to display. */
229 static void fill_alert_config(SnortConfig_t *snort_config, Alert_t *alert)
231 unsigned global_match_number=0, rule_match_number=0;
233 /* Look up rule by sid */
234 alert->matched_rule = get_rule(snort_config, alert->sid);
236 /* Classtype usually filled in from alert rather than rule, but missing for supsported
237 comment format. */
238 if (pref_snort_alerts_source == FromUserComments) {
239 alert->classification = g_strdup(alert->matched_rule->classtype);
242 /* Inform the config/rule about the alert */
243 rule_set_alert(snort_config, alert->matched_rule,
244 &global_match_number, &rule_match_number);
246 /* Copy updated counts into the alert */
247 alert->overall_match_number = global_match_number;
248 alert->rule_match_number = rule_match_number;
252 /* Helper functions for matching expected bytes against the packet buffer.
253 Case-sensitive comparison - can just memcmp().
254 Case-insensitive comparison - need to look at each byte and compare uppercase version */
255 static bool content_compare_case_sensitive(const uint8_t* memory, const char* target, unsigned length)
257 return (memcmp(memory, target, length) == 0);
260 static bool content_compare_case_insensitive(const uint8_t* memory, const char* target, unsigned length)
262 for (unsigned n=0; n < length; n++) {
263 if (g_ascii_isalpha(target[n])) {
264 if (g_ascii_toupper(memory[n]) != g_ascii_toupper(target[n])) {
265 return false;
268 else {
269 if ((uint8_t)memory[n] != (uint8_t)target[n]) {
270 return false;
275 return true;
278 /* Move through the bytes of the tvbuff, looking for a match against the
279 * regexp from the given content.
281 static bool look_for_pcre(content_t *content, tvbuff_t *tvb, unsigned start_offset, unsigned *match_offset, unsigned *match_length)
283 /* Create a regex object for the pcre in the content. */
284 GRegex *regex;
285 GMatchInfo *match_info;
286 bool match_found = false;
287 GRegexCompileFlags regex_compile_flags = (GRegexCompileFlags)0;
289 /* Make sure pcre string is ready for regex library. */
290 if (!content_convert_pcre_for_regex(content)) {
291 return false;
294 /* Copy remaining bytes into NULL-terminated string. Unfortunately, this interface does't allow
295 us to find patterns that involve bytes with value 0.. */
296 int length_remaining = tvb_captured_length_remaining(tvb, start_offset);
297 char *string = (char*)g_malloc(length_remaining + 1);
298 tvb_memcpy(tvb, (void*)string, start_offset, length_remaining);
299 string[length_remaining] = '\0';
301 /* For pcre, translated_str already has / /[modifiers] removed.. */
303 /* Apply any set modifier flags */
304 if (content->pcre_case_insensitive) {
305 regex_compile_flags = (GRegexCompileFlags)(regex_compile_flags | G_REGEX_CASELESS);
307 if (content->pcre_dot_includes_newline) {
308 regex_compile_flags = (GRegexCompileFlags)(regex_compile_flags | G_REGEX_DOTALL);
310 if (content->pcre_raw) {
311 regex_compile_flags = (GRegexCompileFlags)(regex_compile_flags | G_REGEX_RAW);
313 if (content->pcre_multiline) {
314 regex_compile_flags = (GRegexCompileFlags)(regex_compile_flags | G_REGEX_MULTILINE);
317 /* Create regex */
318 regex = g_regex_new(content->translated_str,
319 regex_compile_flags,
320 (GRegexMatchFlags)0, NULL);
322 /* Lookup PCRE match */
323 g_regex_match(regex, string, (GRegexMatchFlags)0, &match_info);
324 /* Only first match needed */
325 /* TODO: need to restart at any NULL before the final end? */
326 if (g_match_info_matches(match_info)) {
327 int start_pos, end_pos;
329 /* Find out where the match is */
330 g_match_info_fetch_pos(match_info,
331 0, /* match_num */
332 &start_pos, &end_pos);
334 *match_offset = start_offset + start_pos;
335 *match_length = end_pos - start_pos;
336 match_found = true;
339 g_match_info_free(match_info);
340 g_regex_unref(regex);
341 g_free(string);
343 return match_found;
346 /* Move through the bytes of the tvbuff, looking for a match against the expanded
347 binary contents of this content object.
349 static bool look_for_content(content_t *content, tvbuff_t *tvb, unsigned start_offset, unsigned *match_offset, unsigned *match_length)
351 int tvb_len = tvb_captured_length(tvb);
353 /* Make sure content has been translated into binary string. */
354 unsigned converted_content_length = content_convert_to_binary(content);
356 /* Look for a match at each position. */
357 for (unsigned m=start_offset; m <= (tvb_len-converted_content_length); m++) {
358 const uint8_t *ptr = tvb_get_ptr(tvb, m, converted_content_length);
359 if (content->nocase) {
360 if (content_compare_case_insensitive(ptr, content->translated_str, content->translated_length)) {
361 *match_offset = m;
362 *match_length = content->translated_length;
363 return true;
366 else {
367 if (content_compare_case_sensitive(ptr, content->translated_str, content->translated_length)) {
368 *match_offset = m;
369 *match_length = content->translated_length;
370 return true;
375 return false;
381 /* Look for where the content match happens within the tvb.
382 * Set out parameters match_offset and match_length */
383 static bool get_content_match(Alert_t *alert, unsigned content_idx,
384 tvbuff_t *tvb, unsigned content_start_match,
385 unsigned *match_offset, unsigned *match_length)
387 content_t *content;
388 Rule_t *rule = alert->matched_rule;
390 /* Can't match if don't know rule */
391 if (rule == NULL) {
392 return false;
395 /* Get content object. */
396 content = &(rule->contents[content_idx]);
398 /* Look for content match in the packet */
399 if (content->content_type == Pcre) {
400 return look_for_pcre(content, tvb, content_start_match, match_offset, match_length);
402 else {
403 return look_for_content(content, tvb, content_start_match, match_offset, match_length);
408 /* Gets called when snort process has died */
409 static void snort_reaper(GPid pid, int status _U_, void *data)
411 snort_session_t *session = (snort_session_t *)data;
412 if (session->running && session->pid == pid) {
413 session->working = session->running = false;
414 /* XXX, cleanup */
415 } else {
416 g_print("Errrrmm snort_reaper() %"PRIdMAX" != %"PRIdMAX"\n", (intmax_t)session->pid, (intmax_t)pid);
419 /* Close the snort pid (may only make a difference on Windows?) */
420 g_spawn_close_pid(pid);
423 /* Parse timestamp line of output. This is done in part to get the packet_number back out of usec field...
424 * Return value is the input stream moved onto the next field following the timestamp */
425 static const char* snort_parse_ts(const char *ts, uint32_t *frame_number)
427 struct tm tm;
428 unsigned int usec;
430 /* Timestamp */
431 memset(&tm, 0, sizeof(tm));
432 tm.tm_isdst = -1;
433 if (sscanf(ts, "%02d/%02d/%02d-%02d:%02d:%02d.%06u ",
434 &(tm.tm_mon), &(tm.tm_mday), &(tm.tm_year), &(tm.tm_hour), &(tm.tm_min), &(tm.tm_sec), &usec) != 7) {
435 return NULL;
437 tm.tm_mon -= 1;
438 tm.tm_year += 100;
440 /* Store frame number (which was passed into this position when packet was submitted to snort) */
441 *frame_number = usec;
443 return strchr(ts, ' ');
446 /* Parse a fast output alert string */
447 static bool snort_parse_fast_line(const char *line, Alert_t *alert)
449 static const char stars[] = " [**] ";
451 static const char classification[] = "[Classification: ";
452 static const char priority[] = "[Priority: ";
453 const char *tmp_msg;
455 /* Look for timestamp/frame-number */
456 if (!(line = snort_parse_ts(line, &(alert->original_frame)))) {
457 return false;
460 /* [**] */
461 if (!g_str_has_prefix(line+1, stars)) {
462 return false;
464 line += sizeof(stars);
466 /* [%u:%u:%u] */
467 if (sscanf(line, "[%u:%u:%u] ", &(alert->gen), &(alert->sid), &(alert->rev)) != 3) {
468 return false;
470 if (!(line = strchr(line, ' '))) {
471 return false;
474 /* [**] again */
475 tmp_msg = line+1;
476 if (!(line = strstr(line, stars))) {
477 return false;
480 /* msg */
481 alert->msg = g_strndup(tmp_msg, line - tmp_msg);
482 line += (sizeof(stars)-1);
484 /* [Classification: Attempted Administrator Privilege Gain] [Priority: 10] */
486 if (g_str_has_prefix(line, classification)) {
487 /* [Classification: %s] */
488 char *tmp;
489 line += (sizeof(classification)-1);
491 if (!(tmp = (char*)strstr(line, "] [Priority: "))) {
492 return false;
495 /* assume "] [Priority: " is not inside classification text :) */
496 alert->classification = g_strndup(line, tmp - line);
498 line = tmp+2;
499 } else
500 alert->classification = NULL;
502 /* Optimized: if al->classification we already checked this in strstr() above */
503 if (alert->classification || g_str_has_prefix(line, priority)) {
504 /* [Priority: %d] */
505 line += (sizeof(priority)-1);
507 if ((sscanf(line, "%d", &(alert->prio))) != 1) {
508 return false;
511 if (!strstr(line, "] ")) {
512 return false;
514 } else {
515 alert->prio = -1; /* XXX */
518 return true;
522 * snort_parse_user_comment()
524 * Parse line as written by TraceWrangler
525 * e.g. "1:2011768:4 - ET WEB_SERVER PHP tags in HTTP POST"
527 static bool snort_parse_user_comment(const char *line, Alert_t *alert)
529 /* %u:%u:%u */
530 if (sscanf(line, "%u:%u:%u", &(alert->gen), &(alert->sid), &(alert->rev)) != 3) {
531 return false;
534 /* Skip separator between numbers and msg */
535 if (!(line = strstr(line, " - "))) {
536 return false;
539 /* Copy to be consistent with other use of Alert_t */
540 alert->msg = g_strdup(line);
542 /* No need to set other fields as assume zero'd out before this call.. */
543 return true;
546 /* Output data has been received from snort. Read from channel and look for whole alerts. */
547 static gboolean snort_fast_output(GIOChannel *source, GIOCondition condition, void *data)
549 snort_session_t *session = (snort_session_t *)data;
551 /* Loop here until all available input read */
552 while (condition & G_IO_IN) {
553 GIOStatus status;
554 char _buf[1024];
555 size_t len = 0;
557 char *old_buf = NULL;
558 char *buf = _buf;
559 char *line;
561 /* Try to read snort output info _buf */
562 status = g_io_channel_read_chars(source, _buf, sizeof(_buf)-1, &len, NULL);
563 if (status != G_IO_STATUS_NORMAL) {
564 if (status == G_IO_STATUS_AGAIN) {
565 /* Blocked, so unset G_IO_IN and get out of this function */
566 condition = (GIOCondition)(condition & ~G_IO_IN);
567 break;
569 /* Other conditions here could be G_IO_STATUS_ERROR, G_IO_STATUS_EOF */
570 return FALSE;
572 /* Terminate buffer */
573 buf[len] = '\0';
575 /* If we previously had part of a line, append the new bit we just saw */
576 if (session->buf) {
577 g_string_append(session->buf, buf);
578 buf = old_buf = g_string_free(session->buf, false);
579 session->buf = NULL;
582 /* Extract every complete line we find in the output */
583 while ((line = strchr(buf, '\n'))) {
584 /* Have a whole line, so can parse */
585 Alert_t alert;
586 memset(&alert, 0, sizeof(alert));
588 /* Terminate received line */
589 *line = '\0';
591 if (snort_parse_fast_line(buf, &alert)) {
592 /*******************************************************/
593 /* We have an alert line. */
594 #if 0
595 g_print("%ld.%lu [%u,%u,%u] %s {%s} [%d]\n",
596 alert.tv.tv_sec, alert.tv.tv_usec,
597 alert.gen, alert.sid, alert.rev,
598 alert.msg,
599 alert.classification ? alert.classification : "(null)",
600 alert.prio);
601 #endif
603 /* Copy the raw alert string itself */
604 alert.raw_alert = g_strdup(buf);
606 /* See if we can get more info from the parsed config details */
607 fill_alert_config(g_snort_config, &alert);
609 /* Add parsed alert into session->tree */
610 /* Store in tree. Frame number hidden in fraction of second field, so associate
611 alert with that frame. */
612 add_alert_to_session_tree((unsigned)alert.original_frame, &alert);
614 else {
615 g_print("snort_fast_output() line: '%s'\n", buf);
618 buf = line+1;
621 if (buf[0]) {
622 /* Only had part of a line - store it */
623 /* N.B. typically happens maybe once every 5-6 alerts. */
624 session->buf = g_string_new(buf);
627 g_free(old_buf);
630 if ((condition == G_IO_ERR) || (condition == G_IO_HUP) || (condition == G_IO_NVAL)) {
631 /* Will report errors (hung-up, or error) */
633 /* g_print("snort_fast_output() cond: (h:%d,e:%d,r:%d)\n",
634 * !!(condition & G_IO_HUP), !!(condition & G_IO_ERR), condition); */
635 return FALSE;
638 return TRUE;
642 /* Return the offset in the frame where snort should begin looking inside payload. */
643 static unsigned get_protocol_payload_start(const char *protocol, proto_tree *tree)
645 unsigned value = 0;
647 /* For icmp, look from start, whereas for others start after them. */
648 bool look_after_protocol = (strcmp(protocol, "icmp") != 0);
650 if (tree != NULL) {
651 GPtrArray *items = proto_all_finfos(tree);
652 if (items) {
653 unsigned i;
654 for (i=0; i< items->len; i++) {
655 field_info *field = (field_info *)g_ptr_array_index(items,i);
656 if (strcmp(field->hfinfo->abbrev, protocol) == 0) {
657 value = field->start;
658 if (look_after_protocol) {
659 value += field->length;
661 break;
664 g_ptr_array_free(items,true);
667 return value;
671 /* Return offset that application layer traffic will begin from. */
672 static unsigned get_content_start_match(Rule_t *rule, proto_tree *tree)
674 /* Work out where snort would start looking for data in the frame */
675 return get_protocol_payload_start(rule->protocol, tree);
678 /* Where this frame is later part of a reassembled complete PDU running over TCP, look up
679 and return that frame number. */
680 static unsigned get_reassembled_in_frame(proto_tree *tree)
682 unsigned value = 0;
684 if (tree != NULL) {
685 GPtrArray *items = proto_all_finfos(tree);
686 if (items) {
687 unsigned i;
688 for (i=0; i< items->len; i++) {
689 field_info *field = (field_info *)g_ptr_array_index(items,i);
690 if (strcmp(field->hfinfo->abbrev, "tcp.reassembled_in") == 0) {
691 value = fvalue_get_uinteger(field->value);
692 break;
695 g_ptr_array_free(items,true);
698 return value;
702 /* Show the Snort protocol tree based on the info in alert */
703 static void snort_show_alert(proto_tree *tree, tvbuff_t *tvb, packet_info *pinfo, Alert_t *alert)
705 proto_tree *snort_tree = NULL;
706 unsigned n;
707 proto_item *ti, *rule_ti;
708 proto_tree *rule_tree;
709 Rule_t *rule = alert->matched_rule;
711 /* May need to move to reassembled frame to show there instead of here */
713 if (snort_alert_in_reassembled_frame && pinfo->fd->visited && (tree != NULL)) {
714 unsigned reassembled_frame = get_reassembled_in_frame(tree);
716 if (reassembled_frame && (reassembled_frame != pinfo->num)) {
717 Alerts_t *alerts;
719 /* Look up alerts for this frame */
720 alerts = (Alerts_t*)wmem_tree_lookup32(current_session.alerts_tree, pinfo->num);
722 if (!alerts->alerts[0].reassembled_frame) {
723 /* Update all alerts from this frame! */
724 for (n=0; n < alerts->num_alerts; n++) {
726 /* Set forward/back frame numbers */
727 alerts->alerts[n].original_frame = pinfo->num;
728 alerts->alerts[n].reassembled_frame = reassembled_frame;
730 /* Add these alerts to reassembled frame */
731 add_alert_to_session_tree(reassembled_frame, &alerts->alerts[n]);
737 /* Can only find start if we have the rule and know the protocol */
738 unsigned content_start_match = 0;
739 unsigned payload_start = 0;
740 if (rule) {
741 payload_start = content_start_match = get_content_start_match(rule, tree);
744 /* Snort output arrived and was previously stored - so add to tree */
745 /* Take care not to try to highlight bytes that aren't there.. */
746 proto_item *alert_ti = proto_tree_add_protocol_format(tree, proto_snort, tvb,
747 content_start_match >= tvb_captured_length(tvb) ? 0 : content_start_match,
748 content_start_match >= tvb_captured_length(tvb) ? 0 : -1,
749 "Snort: (msg: \"%s\" sid: %u rev: %u) [from %s]",
750 alert->msg, alert->sid, alert->rev,
751 (pref_snort_alerts_source == FromUserComments) ?
752 "User Comment" :
753 "Running Snort");
754 snort_tree = proto_item_add_subtree(alert_ti, ett_snort);
756 if (snort_alert_in_reassembled_frame && (alert->reassembled_frame != 0)) {
757 if (alert->original_frame == pinfo->num) {
758 /* Show link forward to where alert is now shown! */
759 ti = proto_tree_add_uint(tree, hf_snort_reassembled_in, tvb, 0, 0,
760 alert->reassembled_frame);
761 proto_item_set_generated(ti);
762 return;
764 else {
765 tvbuff_t *reassembled_tvb;
766 /* Show link back to segment where alert was detected. */
767 ti = proto_tree_add_uint(tree, hf_snort_reassembled_from, tvb, 0, 0,
768 alert->original_frame);
769 proto_item_set_generated(ti);
771 /* Should find this if look late enough.. */
772 reassembled_tvb = get_data_source_tvb_by_name(pinfo, "Reassembled TCP");
773 if (reassembled_tvb) {
774 /* Will look for content using the TVB instead of just this frame's one */
775 tvb = reassembled_tvb;
777 /* TODO: for correctness, would be good to lookup + remember the offset of the source
778 * frame within the reassembled PDU frame, to make sure we find the content in the
779 * correct place for every alert */
783 ws_debug("Showing alert (sid=%u) in frame %u", alert->sid, pinfo->num);
785 /* Show in expert info if configured to. */
786 if (snort_show_alert_expert_info) {
787 expert_add_info_format(pinfo, alert_ti, &ei_snort_alert, "Alert %u: \"%s\"", alert->sid, alert->msg);
790 /* Show the 'raw' alert string. */
791 if (rule) {
792 /* Fix up alert->raw_alert if not already done so first. */
793 if (!alert->raw_alert_ts_fixed) {
794 /* Write 6 figures to position after decimal place in timestamp. Must have managed to
795 parse out fields already, so will definitely be long enough for memcpy() to succeed. */
796 char digits[7];
797 snprintf(digits, 7, "%06d", pinfo->abs_ts.nsecs / 1000);
798 memcpy(alert->raw_alert+18, digits, 6);
799 alert->raw_alert_ts_fixed = true;
801 ti = proto_tree_add_string(snort_tree, hf_snort_raw_alert, tvb, 0, 0, alert->raw_alert);
802 proto_item_set_generated(ti);
805 /* Rule classification */
806 if (alert->classification) {
807 ti = proto_tree_add_string(snort_tree, hf_snort_classification, tvb, 0, 0, alert->classification);
808 proto_item_set_generated(ti);
811 /* Put rule fields under a rule subtree */
813 rule_ti = proto_tree_add_string_format(snort_tree, hf_snort_rule, tvb, 0, 0, "", "Rule");
814 proto_item_set_generated(rule_ti);
815 rule_tree = proto_item_add_subtree(rule_ti, ett_snort_rule);
817 /* msg/description */
818 ti = proto_tree_add_string(rule_tree, hf_snort_msg, tvb, 0, 0, alert->msg);
819 proto_item_set_generated(ti);
820 /* Snort ID */
821 ti = proto_tree_add_uint(rule_tree, hf_snort_sid, tvb, 0, 0, alert->sid);
822 proto_item_set_generated(ti);
823 /* Rule revision */
824 ti = proto_tree_add_uint(rule_tree, hf_snort_rev, tvb, 0, 0, alert->rev);
825 proto_item_set_generated(ti);
826 /* Generator seems to correspond to gid. */
827 ti = proto_tree_add_uint(rule_tree, hf_snort_generator, tvb, 0, 0, alert->gen);
828 proto_item_set_generated(ti);
829 /* Default priority is 2 - very few rules have a different priority... */
830 ti = proto_tree_add_uint(rule_tree, hf_snort_priority, tvb, 0, 0, alert->prio);
831 proto_item_set_generated(ti);
833 /* If we know the rule for this alert, show some of the rule fields */
834 if (rule && rule->rule_string) {
835 size_t rule_string_length = strlen(rule->rule_string);
837 /* Show rule string itself. Add it as a separate data source so can read it all */
838 if (rule_string_length > 60) {
839 tvbuff_t *rule_string_tvb = tvb_new_child_real_data(tvb, rule->rule_string,
840 (unsigned)rule_string_length,
841 (unsigned)rule_string_length);
842 add_new_data_source(pinfo, rule_string_tvb, "Rule String");
843 ti = proto_tree_add_string(rule_tree, hf_snort_rule_string, rule_string_tvb, 0,
844 (int)rule_string_length,
845 rule->rule_string);
847 else {
848 ti = proto_tree_add_string(rule_tree, hf_snort_rule_string, tvb, 0, 0,
849 rule->rule_string);
851 proto_item_set_generated(ti);
853 /* Protocol from rule */
854 ti = proto_tree_add_string(rule_tree, hf_snort_rule_protocol, tvb, 0, 0, rule->protocol);
855 proto_item_set_generated(ti);
857 /* Show file alert came from */
858 ti = proto_tree_add_string(rule_tree, hf_snort_rule_filename, tvb, 0, 0, rule->file);
859 proto_item_set_generated(ti);
860 /* Line number within file */
861 ti = proto_tree_add_uint(rule_tree, hf_snort_rule_line_number, tvb, 0, 0, rule->line_number);
862 proto_item_set_generated(ti);
864 /* Show IP vars */
865 for (n=0; n < rule->relevant_vars.num_ip_vars; n++) {
866 ti = proto_tree_add_none_format(rule_tree, hf_snort_rule_ip_var, tvb, 0, 0, "IP Var: ($%s -> %s)",
867 rule->relevant_vars.ip_vars[n].name,
868 rule->relevant_vars.ip_vars[n].value);
869 proto_item_set_generated(ti);
871 /* Show Port vars */
872 for (n=0; n < rule->relevant_vars.num_port_vars; n++) {
873 ti = proto_tree_add_none_format(rule_tree, hf_snort_rule_port_var, tvb, 0, 0, "Port Var: ($%s -> %s)",
874 rule->relevant_vars.port_vars[n].name,
875 rule->relevant_vars.port_vars[n].value);
876 proto_item_set_generated(ti);
881 /* Show summary information in rule tree root */
882 proto_item_append_text(rule_ti, " %s (sid=%u, rev=%u)",
883 alert->msg, alert->sid, alert->rev);
885 /* More fields retrieved from the parsed config */
886 if (rule) {
887 unsigned content_last_match_end = 0;
889 /* Work out which ip and port vars are relevant */
890 rule_set_relevant_vars(g_snort_config, rule);
892 /* Contents */
893 for (n=0; n < rule->number_contents; n++) {
895 /* Search for string among tvb contents so we can highlight likely bytes. */
896 unsigned int content_offset = 0;
897 bool match_found = false;
898 unsigned int converted_content_length = 0;
899 int content_hf_item;
900 char *content_text_template;
902 /* Choose type of content field to add */
903 switch (rule->contents[n].content_type) {
904 case Content:
905 content_hf_item = hf_snort_content;
906 content_text_template = "Content: \"%s\"";
907 break;
908 case UriContent:
909 content_hf_item = hf_snort_uricontent;
910 content_text_template = "Uricontent: \"%s\"";
911 break;
912 case Pcre:
913 content_hf_item = hf_snort_pcre;
914 content_text_template = "Pcre: \"%s\"";
915 break;
916 default:
917 continue;
920 /* Will only try to look for content in packet ourselves if not
921 a negated content entry (i.e. beginning with '!') */
922 if (!rule->contents[n].negation) {
923 /* Look up offset of match. N.B. would only expect to see on first content... */
924 unsigned distance_to_add = 0;
926 /* May need to start looking from absolute offset into packet... */
927 if (rule->contents[n].offset_set) {
928 content_start_match = payload_start + rule->contents[n].offset;
930 /* ... or a number of bytes beyond the previous content match */
931 else if (rule->contents[n].distance_set) {
932 distance_to_add = (content_last_match_end-content_start_match) + rule->contents[n].distance;
934 else {
935 /* No constraints about where it appears - go back to the start of the frame. */
936 content_start_match = payload_start;
940 /* Now actually look for match from calculated position */
941 /* TODO: could take 'depth' and 'within' into account to limit extent of search,
942 but OK if just trying to verify what Snort already found. */
943 match_found = get_content_match(alert, n,
944 tvb, content_start_match+distance_to_add,
945 &content_offset, &converted_content_length);
946 if (match_found) {
947 content_last_match_end = content_offset + converted_content_length;
952 /* Show content in tree (showing position if known) */
953 ti = proto_tree_add_string_format(snort_tree, content_hf_item, tvb,
954 (match_found) ? content_offset : 0,
955 (match_found) ? converted_content_length : 0,
956 rule->contents[n].str,
957 content_text_template,
958 rule->contents[n].str);
960 /* Next match position will be after this one */
961 if (match_found) {
962 content_start_match = content_last_match_end;
965 /* Show (only as text) attributes of content field */
966 if (rule->contents[n].fastpattern) {
967 proto_item_append_text(ti, " (fast_pattern)");
969 if (rule->contents[n].rawbytes) {
970 proto_item_append_text(ti, " (rawbytes)");
972 if (rule->contents[n].nocase) {
973 proto_item_append_text(ti, " (nocase)");
975 if (rule->contents[n].negation) {
976 proto_item_append_text(ti, " (negated)");
978 if (rule->contents[n].offset_set) {
979 proto_item_append_text(ti, " (offset=%d)", rule->contents[n].offset);
981 if (rule->contents[n].depth != 0) {
982 proto_item_append_text(ti, " (depth=%u)", rule->contents[n].depth);
984 if (rule->contents[n].distance_set) {
985 proto_item_append_text(ti, " (distance=%d)", rule->contents[n].distance);
987 if (rule->contents[n].within != 0) {
988 proto_item_append_text(ti, " (within=%u)", rule->contents[n].within);
991 /* HTTP preprocessor modifiers */
992 if (rule->contents[n].http_method != 0) {
993 proto_item_append_text(ti, " (http_method)");
995 if (rule->contents[n].http_client_body != 0) {
996 proto_item_append_text(ti, " (http_client_body)");
998 if (rule->contents[n].http_cookie != 0) {
999 proto_item_append_text(ti, " (http_cookie)");
1001 if (rule->contents[n].http_user_agent != 0) {
1002 proto_item_append_text(ti, " (http_user_agent)");
1005 if (!rule->contents[n].negation && !match_found) {
1006 /* Useful for debugging, may also happen when Snort is reassembling.. */
1007 /* TODO: not sure why, but PCREs might not be found first time through, but will be
1008 * found later, with the result that there will be 'not located' expert warnings,
1009 * but when you click on the packet, it is matched after all... */
1010 proto_item_append_text(ti, " - not located");
1011 expert_add_info_format(pinfo, ti, &ei_snort_content_not_matched,
1012 "%s \"%s\" not found in frame",
1013 rule->contents[n].content_type==Pcre ? "PCRE" : "Content",
1014 rule->contents[n].str);
1018 /* References */
1019 for (n=0; n < rule->number_references; n++) {
1020 /* Substitute prefix and add to tree as clickable web links */
1021 ti = proto_tree_add_string(snort_tree, hf_snort_reference, tvb, 0, 0,
1022 expand_reference(g_snort_config, rule->references[n]));
1023 /* Make clickable */
1024 proto_item_set_url(ti);
1025 proto_item_set_generated(ti);
1029 /* Global rule stats if configured to. */
1030 if (snort_show_rule_stats) {
1031 unsigned int number_rule_files, number_rules, alerts_detected, this_rule_alerts_detected;
1032 proto_item *stats_ti;
1033 proto_tree *stats_tree;
1035 /* Create tree for these items */
1036 stats_ti = proto_tree_add_string_format(snort_tree, hf_snort_global_stats, tvb, 0, 0, "", "Global Stats");
1037 proto_item_set_generated(rule_ti);
1038 stats_tree = proto_item_add_subtree(stats_ti, ett_snort_global_stats);
1040 /* Get overall number of rules */
1041 get_global_rule_stats(g_snort_config, alert->sid, &number_rule_files, &number_rules, &alerts_detected,
1042 &this_rule_alerts_detected);
1043 ti = proto_tree_add_uint(stats_tree, hf_snort_global_stats_rule_file_count, tvb, 0, 0, number_rule_files);
1044 proto_item_set_generated(ti);
1045 ti = proto_tree_add_uint(stats_tree, hf_snort_global_stats_rule_count, tvb, 0, 0, number_rules);
1046 proto_item_set_generated(ti);
1048 /* Overall alert stats (total, and where this one comes in order) */
1049 ti = proto_tree_add_uint(stats_tree, hf_snort_global_stats_total_alerts_count, tvb, 0, 0, alerts_detected);
1050 proto_item_set_generated(ti);
1051 ti = proto_tree_add_uint(stats_tree, hf_snort_global_stats_alert_match_number, tvb, 0, 0, alert->overall_match_number);
1052 proto_item_set_generated(ti);
1054 if (rule) {
1055 /* Stats just for this rule (overall, and where this one comes in order) */
1056 ti = proto_tree_add_uint(stats_tree, hf_snort_global_stats_rule_alerts_count, tvb, 0, 0, this_rule_alerts_detected);
1057 proto_item_set_generated(ti);
1058 ti = proto_tree_add_uint(stats_tree, hf_snort_global_stats_rule_match_number, tvb, 0, 0, alert->rule_match_number);
1059 proto_item_set_generated(ti);
1061 /* Add a summary to the stats root */
1062 proto_item_append_text(stats_ti, " (%u rules from %u files, #%u of %u alerts seen (%u/%u for sid %u))",
1063 number_rules, number_rule_files, alert->overall_match_number, alerts_detected,
1064 alert->rule_match_number, this_rule_alerts_detected, alert->sid);
1066 else {
1067 /* Add a summary to the stats root */
1068 proto_item_append_text(stats_ti, " (%u rules from %u files, #%u of %u alerts seen)",
1069 number_rules, number_rule_files, alert->overall_match_number, alerts_detected);
1074 /* Look for, and return, any user comment set for this packet.
1075 Currently used for fetching alerts in the format TraceWrangler can write out to */
1076 static const char *get_user_comment_string(proto_tree *tree)
1078 const char *value = NULL;
1080 if (tree != NULL) {
1081 GPtrArray *items = proto_all_finfos(tree);
1082 if (items) {
1083 unsigned i;
1085 for (i=0; i< items->len; i++) {
1086 field_info *field = (field_info *)g_ptr_array_index(items,i);
1087 if (strcmp(field->hfinfo->abbrev, "frame.comment") == 0) {
1088 value = fvalue_get_string(field->value);
1089 break;
1091 /* This is the only item that can come before "frame.comment", so otherwise break out */
1092 if (strncmp(field->hfinfo->abbrev, "pkt_comment", 11) != 0) {
1093 break;
1096 g_ptr_array_free(items,true);
1099 return value;
1103 /********************************************************************************/
1104 /* Main (post-)dissector function. */
1105 static int
1106 snort_dissector(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_)
1108 Alerts_t *alerts;
1110 /* If not looking for alerts, return quickly */
1111 if (pref_snort_alerts_source == FromNowhere) {
1112 return 0;
1115 /* Are we looking for alerts in user comments? */
1116 else if (pref_snort_alerts_source == FromUserComments) {
1117 /* Look for user comments containing alerts */
1118 const char *alert_string = get_user_comment_string(tree);
1119 if (alert_string) {
1120 alerts = (Alerts_t*)wmem_tree_lookup32(current_session.alerts_tree, pinfo->num);
1121 if (!alerts) {
1122 Alert_t alert;
1123 memset(&alert, 0, sizeof(alert));
1124 if (snort_parse_user_comment(alert_string, &alert)) {
1125 /* Copy the raw alert itself */
1126 alert.raw_alert = g_strdup(alert_string);
1128 /* See if we can get more info from the parsed config details */
1129 fill_alert_config(g_snort_config, &alert);
1131 /* Add parsed alert into session->tree */
1132 add_alert_to_session_tree(pinfo->num, &alert);
1137 else {
1138 /* We expect alerts from Snort. Pass frame into snort on first pass. */
1139 if (!pinfo->fd->visited && current_session.working) {
1140 int write_err = 0;
1141 char *err_info;
1142 wtap_rec rec;
1144 /* First time, open current_session.in to write to for dumping into snort with */
1145 if (!current_session.pdh) {
1146 wtap_dump_params params = WTAP_DUMP_PARAMS_INIT;
1147 int open_err;
1148 char *open_err_info;
1150 /* Older versions of Snort don't support capture file with several encapsulations (like pcapng),
1151 * so write in pcap format and hope we have just one encap.
1152 * Newer versions of Snort can read pcapng now, but still
1153 * write in pcap format; if "newer versions of Snort" really
1154 * means "Snort, when using newer versions of libpcap", then,
1155 * yes, they can read pcapng, but they can't read pcapng
1156 * files with more than one encapsulation type, as libpcap's
1157 * API currently can't handle that, so even those "newer
1158 * versions of Snort" wouldn't handle multiple encapsulation
1159 * types.
1161 params.encap = pinfo->rec->rec_header.packet_header.pkt_encap;
1162 params.snaplen = WTAP_MAX_PACKET_SIZE_STANDARD;
1163 current_session.pdh = wtap_dump_fdopen(current_session.in,
1164 wtap_pcap_file_type_subtype(),
1165 WTAP_UNCOMPRESSED,
1166 &params,
1167 &open_err,
1168 &open_err_info);
1169 if (!current_session.pdh) {
1170 /* XXX - report the error somehow? */
1171 g_free(open_err_info);
1172 current_session.working = false;
1173 return 0;
1177 /* Start with all same values... */
1178 rec = *pinfo->rec;
1180 /* Copying packet details into wtp for writing */
1181 rec.ts = pinfo->abs_ts;
1183 /* NB: overwriting the time stamp so we can see packet number back if an alert is written for this frame!!!! */
1184 /* TODO: does this seriously affect snort's ability to reason about time?
1185 * At least all packets will still be in order... */
1186 rec.ts.nsecs = pinfo->fd->num * 1000; /* XXX, max 999'999 frames */
1188 rec.rec_header.packet_header.caplen = tvb_captured_length(tvb);
1189 rec.rec_header.packet_header.len = tvb_reported_length(tvb);
1191 /* Dump frame into snort's stdin */
1192 if (!wtap_dump(current_session.pdh, &rec, tvb_get_ptr(tvb, 0, tvb_reported_length(tvb)), &write_err, &err_info)) {
1193 /* XXX - report the error somehow? */
1194 g_free(err_info);
1195 current_session.working = false;
1196 return 0;
1198 if (!wtap_dump_flush(current_session.pdh, &write_err)) {
1199 /* XXX - report the error somehow? */
1200 current_session.working = false;
1201 return 0;
1204 /* Give the io channel a chance to deliver alerts.
1205 TODO: g_main_context_iteration(NULL, false); causes crashes sometimes when Qt events get to execute.. */
1209 /* Now look up stored alerts for this packet number, and display if found */
1210 if (current_session.alerts_tree && (alerts = (Alerts_t*)wmem_tree_lookup32(current_session.alerts_tree, pinfo->fd->num))) {
1211 unsigned n;
1213 for (n=0; n < alerts->num_alerts; n++) {
1214 snort_show_alert(tree, tvb, pinfo, &(alerts->alerts[n]));
1216 } else {
1217 /* XXX, here either this frame doesn't generate alerts or we haven't received data from snort (async)
1219 * It's problem when user want to filter tree on initial run, or is running one-pass tshark.
1223 return tvb_reported_length(tvb);
1227 /*------------------------------------------------------------------*/
1228 /* Start up Snort. */
1229 static void snort_start(void)
1231 GIOChannel *channel;
1232 /* int snort_output_id; */
1233 const char *argv[] = {
1234 pref_snort_binary_filename, "-c", pref_snort_config_filename,
1235 /* read from stdin */
1236 "-r", "-",
1237 /* don't log */
1238 "-N",
1239 /* output to console and silence snort */
1240 "-A", "console", "-q",
1241 /* normalize time */
1242 "-y", /* -U", */
1243 /* Optionally ignore checksum errors */
1244 "-k", "none",
1245 NULL
1248 /* Truncate command to before -k if this pref off */
1249 if (!snort_ignore_checksum_errors) {
1250 argv[10] = NULL;
1253 /* Enable field priming if required. */
1254 if (snort_alert_in_reassembled_frame) {
1255 /* Add items we want to try to get to find before we get called.
1256 For now, just ask for tcp.reassembled_in, which won't be seen
1257 on the first pass through the packets. */
1258 GArray *wanted_hfids = g_array_new(false, false, (unsigned)sizeof(int));
1259 int id = proto_registrar_get_id_byname("tcp.reassembled_in");
1260 g_array_append_val(wanted_hfids, id);
1261 set_postdissector_wanted_hfids(snort_handle, wanted_hfids);
1264 /* Nothing to do if not enabled, but registered init function gets called anyway */
1265 if ((pref_snort_alerts_source == FromNowhere) ||
1266 !proto_is_protocol_enabled(find_protocol_by_id(proto_snort))) {
1267 return;
1270 /* Create tree mapping packet_number -> Alerts_t*. It will get recreated when packet list is reloaded */
1271 current_session.alerts_tree = wmem_tree_new_autoreset(wmem_epan_scope(), wmem_file_scope());
1273 /* Create afresh the config object by parsing the same file that snort uses */
1274 if (g_snort_config) {
1275 delete_config(&g_snort_config);
1277 create_config(&g_snort_config, pref_snort_config_filename);
1279 /* Don't run Snort if not configured to */
1280 if (pref_snort_alerts_source == FromUserComments) {
1281 return;
1284 /* Don't start if already running */
1285 if (current_session.running) {
1286 return;
1289 /* Reset global stats */
1290 reset_global_rule_stats(g_snort_config);
1292 /* Need to test that we can run snort --version and that config can be parsed... */
1293 /* Does nothing at present */
1294 if (!snort_config_ok) {
1295 /* Can carry on without snort... */
1296 return;
1299 /* About to run snort, so check that configured files exist, and that binary could be executed. */
1300 ws_statb64 binary_stat, config_stat;
1302 if (ws_stat64(pref_snort_binary_filename, &binary_stat) != 0) {
1303 ws_debug("Can't run snort - executable '%s' not found", pref_snort_binary_filename);
1304 report_failure("Snort dissector: Can't run snort - executable '%s' not found\n", pref_snort_binary_filename);
1305 return;
1308 if (ws_stat64(pref_snort_config_filename, &config_stat) != 0) {
1309 ws_debug("Can't run snort - config file '%s' not found", pref_snort_config_filename);
1310 report_failure("Snort dissector: Can't run snort - config file '%s' not found\n", pref_snort_config_filename);
1311 return;
1314 #ifdef S_IXUSR
1315 if (!(binary_stat.st_mode & S_IXUSR)) {
1316 ws_debug("Snort binary '%s' is not executable", pref_snort_binary_filename);
1317 report_failure("Snort dissector: Snort binary '%s' is not executable\n", pref_snort_binary_filename);
1318 return;
1320 #endif
1322 #ifdef _WIN32
1323 report_failure("Snort dissector: not yet able to launch Snort process under Windows");
1324 current_session.working = false;
1325 return;
1326 #endif
1328 /* Create snort process and set up pipes */
1329 ws_debug("Running %s with config file %s", pref_snort_binary_filename, pref_snort_config_filename);
1330 if (!g_spawn_async_with_pipes(NULL, /* working_directory */
1331 (char **)argv,
1332 NULL, /* envp */
1333 (GSpawnFlags)( G_SPAWN_DO_NOT_REAP_CHILD), /* Leave out G_SPAWN_SEARCH_PATH */
1334 NULL, /* child setup - not supported in Windows, so we can't use it */
1335 NULL, /* user-data */
1336 &current_session.pid, /* PID */
1337 &current_session.in, /* stdin */
1338 &current_session.out, /* stdout */
1339 &current_session.err, /* stderr */
1340 NULL)) /* error */
1342 current_session.running = false;
1343 current_session.working = false;
1344 return;
1346 else {
1347 current_session.running = true;
1348 current_session.working = true;
1351 /* Setup handler for when process goes away */
1352 g_child_watch_add(current_session.pid, snort_reaper, &current_session);
1354 /******************************************************************/
1355 /* Create channel to get notified of snort alert output on stdout */
1357 /* Create channel itself */
1358 channel = g_io_channel_unix_new(current_session.out);
1359 current_session.channel = channel;
1361 /* NULL encoding supports binary or whatever the application outputs */
1362 g_io_channel_set_encoding(channel, NULL, NULL);
1363 /* Don't buffer the channel (settable because encoding set to NULL). */
1364 g_io_channel_set_buffered(channel, false);
1365 /* Set flags */
1366 /* TODO: could set to be blocking and get sync that way? */
1367 g_io_channel_set_flags(channel, G_IO_FLAG_NONBLOCK, NULL);
1368 /* Try setting a large buffer here. */
1369 g_io_channel_set_buffer_size(channel, 256000);
1371 current_session.buf = NULL;
1373 /* Set callback for receiving data from the channel */
1374 g_io_add_watch_full(channel,
1375 G_PRIORITY_HIGH,
1376 (GIOCondition)(G_IO_IN|G_IO_ERR|G_IO_HUP),
1377 snort_fast_output, /* Callback upon data being written by snort */
1378 &current_session, /* User data */
1379 NULL); /* Destroy notification callback */
1381 current_session.working = true;
1384 /* This is the cleanup routine registered with register_postseq_cleanup_routine() */
1385 static void snort_cleanup(void)
1387 /* Only close if we think its running */
1388 if (!current_session.running) {
1389 return;
1392 /* Close dumper writing into snort's stdin. This will cause snort to exit! */
1393 if (current_session.pdh) {
1394 int write_err;
1395 char *write_err_info;
1396 if (!wtap_dump_close(current_session.pdh, NULL, &write_err, &write_err_info)) {
1397 /* XXX - somehow report the error? */
1398 g_free(write_err_info);
1400 current_session.pdh = NULL;
1404 static void snort_file_cleanup(void)
1406 if (g_snort_config) {
1407 delete_config(&g_snort_config);
1410 /* Disable field priming that got enabled in the init routine. */
1411 set_postdissector_wanted_hfids(snort_handle, NULL);
1414 void
1415 proto_reg_handoff_snort(void)
1417 /* N.B. snort self-test here deleted, as I was struggling to get it to
1418 * work as a non-root user (couldn't read stdin)
1419 * TODO: could run snort just to get the version number and check the config file is readable?
1420 * TODO: could make snort config parsing less forgiving and use that as a test? */
1423 void
1424 proto_register_snort(void)
1426 static hf_register_info hf[] = {
1427 { &hf_snort_sid,
1428 { "Rule SID", "snort.sid", FT_UINT32, BASE_DEC, NULL, 0x00,
1429 "Snort Rule identifier", HFILL }},
1430 { &hf_snort_raw_alert,
1431 { "Raw Alert", "snort.raw-alert", FT_STRING, BASE_NONE, NULL, 0x00,
1432 "Full text of Snort alert", HFILL }},
1433 { &hf_snort_rule,
1434 { "Rule", "snort.rule", FT_STRING, BASE_NONE, NULL, 0x00,
1435 "Entire Snort rule string", HFILL }},
1436 { &hf_snort_msg,
1437 { "Alert Message", "snort.msg", FT_STRINGZ, BASE_NONE, NULL, 0x00,
1438 "Description of what the rule detects", HFILL }},
1439 { &hf_snort_classification,
1440 { "Alert Classification", "snort.class", FT_STRINGZ, BASE_NONE, NULL, 0x00,
1441 NULL, HFILL }},
1442 { &hf_snort_priority,
1443 { "Alert Priority", "snort.priority", FT_UINT32, BASE_DEC, NULL, 0x00,
1444 NULL, HFILL }},
1445 { &hf_snort_generator,
1446 { "Rule Generator", "snort.generator", FT_UINT32, BASE_DEC, NULL, 0x00,
1447 NULL, HFILL }},
1448 { &hf_snort_rev,
1449 { "Rule Revision", "snort.rev", FT_UINT32, BASE_DEC, NULL, 0x00,
1450 NULL, HFILL }},
1451 { &hf_snort_rule_string,
1452 { "Rule String", "snort.rule-string", FT_STRINGZ, BASE_NONE, NULL, 0x00,
1453 "Full text of Snort rule", HFILL }},
1454 { &hf_snort_rule_protocol,
1455 { "Protocol", "snort.protocol", FT_STRINGZ, BASE_NONE, NULL, 0x00,
1456 "Protocol name as given in the rule", HFILL }},
1457 { &hf_snort_rule_filename,
1458 { "Rule Filename", "snort.rule-filename", FT_STRINGZ, BASE_NONE, NULL, 0x00,
1459 "Rules file where Snort rule was parsed from", HFILL }},
1460 { &hf_snort_rule_line_number,
1461 { "Line number within rules file where rule was parsed from", "snort.rule-line-number", FT_UINT32, BASE_DEC, NULL, 0x00,
1462 NULL, HFILL }},
1463 { &hf_snort_rule_ip_var,
1464 { "IP variable", "snort.rule-ip-var", FT_NONE, BASE_NONE, NULL, 0x00,
1465 "IP variable used in rule", HFILL }},
1466 { &hf_snort_rule_port_var,
1467 { "Port variable used in rule", "snort.rule-port-var", FT_NONE, BASE_NONE, NULL, 0x00,
1468 NULL, HFILL }},
1469 { &hf_snort_reassembled_in,
1470 { "Reassembled frame where alert is shown", "snort.reassembled_in", FT_FRAMENUM, BASE_NONE, NULL, 0x00,
1471 NULL, HFILL }},
1472 { &hf_snort_reassembled_from,
1473 { "Segment where alert was triggered", "snort.reassembled_from", FT_FRAMENUM, BASE_NONE, NULL, 0x00,
1474 NULL, HFILL }},
1475 { &hf_snort_content,
1476 { "Content", "snort.content", FT_STRINGZ, BASE_NONE, NULL, 0x00,
1477 "Snort content field", HFILL }},
1478 { &hf_snort_uricontent,
1479 { "URI Content", "snort.uricontent", FT_STRINGZ, BASE_NONE, NULL, 0x00,
1480 "Snort URI content field", HFILL }},
1481 { &hf_snort_pcre,
1482 { "PCRE", "snort.pcre", FT_STRINGZ, BASE_NONE, NULL, 0x00,
1483 "Perl Compatible Regular Expression", HFILL }},
1484 { &hf_snort_reference,
1485 { "Reference", "snort.reference", FT_STRINGZ, BASE_NONE, NULL, 0x00,
1486 "Web reference provided as part of rule", HFILL }},
1488 /* Global stats */
1489 { &hf_snort_global_stats,
1490 { "Global Stats", "snort.global-stats", FT_STRING, BASE_NONE, NULL, 0x00,
1491 "Global statistics for rules and alerts", HFILL }},
1492 { &hf_snort_global_stats_rule_file_count,
1493 { "Number of rule files", "snort.global-stats.rule-file-count", FT_UINT32, BASE_DEC, NULL, 0x00,
1494 "Total number of rules files found in Snort config", HFILL }},
1495 { &hf_snort_global_stats_rule_count,
1496 { "Number of rules", "snort.global-stats.rule-count", FT_UINT32, BASE_DEC, NULL, 0x00,
1497 "Total number of rules found in Snort config", HFILL }},
1498 { &hf_snort_global_stats_total_alerts_count,
1499 { "Number of alerts detected", "snort.global-stats.total-alerts", FT_UINT32, BASE_DEC, NULL, 0x00,
1500 "Total number of alerts detected in this capture", HFILL }},
1501 { &hf_snort_global_stats_alert_match_number,
1502 { "Match number", "snort.global-stats.match-number", FT_UINT32, BASE_DEC, NULL, 0x00,
1503 "Number of match for this alert among all alerts", HFILL }},
1505 { &hf_snort_global_stats_rule_alerts_count,
1506 { "Number of alerts for this rule", "snort.global-stats.rule.alerts-count", FT_UINT32, BASE_DEC, NULL, 0x00,
1507 "Number of alerts detected for this rule", HFILL }},
1508 { &hf_snort_global_stats_rule_match_number,
1509 { "Match number for this rule", "snort.global-stats.rule.match-number", FT_UINT32, BASE_DEC, NULL, 0x00,
1510 "Number of match for this alert among those for this rule", HFILL }}
1512 static int *ett[] = {
1513 &ett_snort,
1514 &ett_snort_rule,
1515 &ett_snort_global_stats
1518 static const enum_val_t alerts_source_vals[] = {
1519 {"from-nowhere", "Not looking for Snort alerts", FromNowhere},
1520 {"from-running-snort", "From running Snort", FromRunningSnort},
1521 {"from-user-comments", "From user packet comments", FromUserComments},
1522 {NULL, NULL, -1}
1525 static ei_register_info ei[] = {
1526 { &ei_snort_alert, { "snort.alert.expert", PI_SECURITY, PI_WARN, "Snort alert detected", EXPFILL }},
1527 { &ei_snort_content_not_matched, { "snort.content.not-matched", PI_PROTOCOL, PI_NOTE, "Failed to find content field of alert in frame", EXPFILL }},
1530 expert_module_t* expert_snort;
1532 module_t *snort_module;
1534 proto_snort = proto_register_protocol("Snort Alerts", "Snort", "snort");
1536 proto_register_field_array(proto_snort, hf, array_length(hf));
1537 proto_register_subtree_array(ett, array_length(ett));
1539 /* Expert info */
1540 expert_snort = expert_register_protocol(proto_snort);
1541 expert_register_field_array(expert_snort, ei, array_length(ei));
1543 snort_module = prefs_register_protocol(proto_snort, NULL);
1545 prefs_register_obsolete_preference(snort_module, "enable_snort_dissector");
1547 prefs_register_enum_preference(snort_module, "alerts_source",
1548 "Source of Snort alerts",
1549 "Set whether dissector should run Snort and pass frames into it, or read alerts from user packet comments",
1550 &pref_snort_alerts_source, alerts_source_vals, false);
1552 prefs_register_filename_preference(snort_module, "binary",
1553 "Snort binary",
1554 "The name of the snort binary file to run",
1555 &pref_snort_binary_filename, false);
1556 prefs_register_filename_preference(snort_module, "config",
1557 "Configuration filename",
1558 "The name of the file containing the snort IDS configuration. Typically snort.conf",
1559 &pref_snort_config_filename, false);
1561 prefs_register_bool_preference(snort_module, "show_rule_set_stats",
1562 "Show rule stats in protocol tree",
1563 "Whether or not information about the rule set and detected alerts should "
1564 "be shown in the tree of every snort PDU tree",
1565 &snort_show_rule_stats);
1566 prefs_register_bool_preference(snort_module, "show_alert_expert_info",
1567 "Show alerts in expert info",
1568 "Whether or not expert info should be used to highlight fired alerts",
1569 &snort_show_alert_expert_info);
1570 prefs_register_bool_preference(snort_module, "show_alert_in_reassembled_frame",
1571 "Try to show alerts in reassembled frame",
1572 "Attempt to show alert in reassembled frame where possible. Note that this won't work during live capture",
1573 &snort_alert_in_reassembled_frame);
1574 prefs_register_bool_preference(snort_module, "ignore_checksum_errors",
1575 "Tell Snort to ignore checksum errors",
1576 "When enabled, will run Snort with '-k none'",
1577 &snort_ignore_checksum_errors);
1580 snort_handle = register_dissector("snort", snort_dissector, proto_snort);
1582 register_init_routine(snort_start);
1583 register_postdissector(snort_handle);
1585 /* Callback to make sure we cleanup dumper being used to deliver packets to snort (this will tsnort). */
1586 register_postseq_cleanup_routine(snort_cleanup);
1587 /* Callback to allow us to delete snort config */
1588 register_cleanup_routine(snort_file_cleanup);
1592 * Editor modelines - https://www.wireshark.org/tools/modelines.html
1594 * Local variables:
1595 * c-basic-offset: 4
1596 * tab-width: 8
1597 * indent-tabs-mode: nil
1598 * End:
1600 * vi: set shiftwidth=4 tabstop=8 expandtab:
1601 * :indentSize=4:tabSize=8:noTabs=true: