summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docbook/release-notes.asciidoc1
-rw-r--r--epan/dissectors/CMakeLists.txt2
-rw-r--r--epan/dissectors/Makefile.am2
-rw-r--r--epan/dissectors/packet-snort.c1384
-rw-r--r--epan/dissectors/snort-config.c1103
-rw-r--r--epan/dissectors/snort-config.h194
6 files changed, 2686 insertions, 0 deletions
diff --git a/docbook/release-notes.asciidoc b/docbook/release-notes.asciidoc
index 5837bd56b6..1bf32ac781 100644
--- a/docbook/release-notes.asciidoc
+++ b/docbook/release-notes.asciidoc
@@ -64,6 +64,7 @@ Health Level 7 (HL7)
Local Service Discovery (LSD)
Fc00/cjdns Protocol
iPerf2
+Snort Post-dissector
--sort-and-group--
diff --git a/epan/dissectors/CMakeLists.txt b/epan/dissectors/CMakeLists.txt
index aebdea0279..23ff9f4db3 100644
--- a/epan/dissectors/CMakeLists.txt
+++ b/epan/dissectors/CMakeLists.txt
@@ -1222,6 +1222,7 @@ set(DISSECTOR_SRC
packet-snaeth.c
packet-sndcp-xid.c
packet-sndcp.c
+ packet-snort.c
packet-socketcan.c
packet-socks.c
packet-soupbintcp.c
@@ -1422,6 +1423,7 @@ set(DISSECTOR_SRC
set(DISSECTOR_SUPPORT_SRC
packet-dcerpc-nt.c
usb.c
+ snort-config.c
register.c
)
source_group(dissector-support FILES ${DISSECTOR_SUPPORT_SRC})
diff --git a/epan/dissectors/Makefile.am b/epan/dissectors/Makefile.am
index 21f800dd53..2d6d83241e 100644
--- a/epan/dissectors/Makefile.am
+++ b/epan/dissectors/Makefile.am
@@ -1243,6 +1243,7 @@ DISSECTOR_SRC = \
packet-snaeth.c \
packet-sndcp-xid.c \
packet-sndcp.c \
+ packet-snort.c \
packet-socketcan.c \
packet-socks.c \
packet-soupbintcp.c \
@@ -1826,6 +1827,7 @@ DISSECTOR_INCLUDES = \
# used to generate "register.c").
DISSECTOR_SUPPORT_SRC = \
packet-dcerpc-nt.c \
+ snort-config.c \
usb.c \
register.c
diff --git a/epan/dissectors/packet-snort.c b/epan/dissectors/packet-snort.c
new file mode 100644
index 0000000000..4bed149eda
--- /dev/null
+++ b/epan/dissectors/packet-snort.c
@@ -0,0 +1,1384 @@
+/* packet-snort.c
+ *
+ * Copyright 2011, Jakub Zawadzki <darkjames-ws@darkjames.pl>
+ * Copyright 2016, Martin Mathieson
+ *
+ * Google Summer of Code 2011 for The Honeynet Project
+ * Mentors:
+ * Guillaume Arcas <guillaume.arcas (at) retiaire.org>
+ * Jeff Nathan <jeffnathan (at) gmail.com>
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+
+/* TODO:
+ * - sort out threading/channel-sync so works reliably in tshark
+ * - postponed for now, as Qt crashes if call g_main_context_iteration()
+ * at an inopportune time
+ * - would be good if could set [Snort Running] in the title bar while Snort is running,
+ * but don't see how a dissector could do that.
+ * - for a content match, find all protocol fields that cover same bytes and show in tree
+ * - after tcp.reassembled_in fixed, offer to move alert to that frame?
+ * - other use-cases as suggested in https://sharkfesteurope.wireshark.org/assets/presentations16eu/14.pptx
+ */
+
+
+#include "config.h"
+
+#include <errno.h>
+#include <ctype.h>
+
+#include <epan/epan.h>
+#include <epan/proto.h>
+#include <epan/packet.h>
+#include <epan/prefs.h>
+#include <epan/expert.h>
+#include <wsutil/report_err.h>
+#include <epan/wmem/wmem.h>
+#include <wiretap/wtap-int.h>
+
+#include "snort-config.h"
+
+/* Forward declarations */
+void proto_register_snort(void);
+void proto_reg_handoff_snort(void);
+
+
+static int proto_snort = -1;
+
+/* These are from parsing snort fast_alert output and/or looking up snort config */
+static int hf_snort_raw_alert = -1;
+static int hf_snort_classification = -1;
+static int hf_snort_rule = -1;
+static int hf_snort_msg = -1;
+static int hf_snort_rev = -1;
+static int hf_snort_sid = -1;
+static int hf_snort_generator = -1;
+static int hf_snort_priority = -1;
+static int hf_snort_rule_string = -1;
+static int hf_snort_rule_protocol = -1;
+static int hf_snort_rule_filename = -1;
+static int hf_snort_rule_line_number = -1;
+static int hf_snort_rule_ip_var = -1;
+static int hf_snort_rule_port_var = -1;
+
+/* Patterns to match */
+static int hf_snort_content = -1;
+static int hf_snort_uricontent = -1;
+static int hf_snort_pcre = -1;
+
+/* Web links */
+static int hf_snort_reference = -1;
+
+/* General stats about the rule set */
+static int hf_snort_global_stats = -1;
+static int hf_snort_global_stats_rule_file_count = -1; /* number of rules files */
+static int hf_snort_global_stats_rule_count = -1; /* number of rules in config */
+
+static int hf_snort_global_stats_total_alerts_count = -1;
+static int hf_snort_global_stats_alert_match_number = -1;
+
+static int hf_snort_global_stats_rule_alerts_count = -1;
+static int hf_snort_global_stats_rule_match_number = -1;
+
+
+/* Subtrees */
+static int ett_snort = -1;
+static int ett_snort_rule = -1;
+static int ett_snort_global_stats = -1;
+
+/* Expert info */
+static expert_field ei_snort_alert = EI_INIT;
+static expert_field ei_snort_content_not_matched = EI_INIT;
+
+
+/*****************************************/
+/* Preferences */
+
+/* Use explicit preference as want to disable this dissector by default */
+static gboolean snort_enable_dissector = FALSE;
+
+/* Where to look for alerts. */
+enum alerts_source {
+ FromRunningSnort,
+ FromUserComments /* see https://blog.packet-foo.com/2015/08/verifying-iocs-with-snort-and-tracewrangler/ */
+};
+/* By default schoose to run Snort to look for alerts */
+static gint pref_snort_alerts_source = (gint)FromRunningSnort;
+
+/* Snort binary and config file */
+#ifndef _WIN32
+static const char *pref_snort_binary_filename = "/usr/sbin/snort";
+static const char *pref_snort_config_filename = "/etc/snort/snort.conf";
+#else
+/* Default locations from Snort Windows installer */
+static const char *pref_snort_binary_filename = "C:\\Snort\\bin\\snort.exe";
+static const char *pref_snort_config_filename = "C:\\Snort\\etc\\snort.conf";
+#endif
+
+/* Should rule stats be shown in protocol tree? */
+static gboolean snort_show_rule_stats = FALSE;
+
+/* Should alerts be added as expert info? */
+static gboolean snort_show_alert_expert_info = FALSE;
+
+#if 0
+/* Should we try to attach the alert to the tcp.reassembled_in frame instead of current one? */
+static gboolean snort_alert_in_reassembled_frame = FALSE;
+#endif
+
+
+
+/********************************************************/
+/* Global variable with single parsed snort config */
+static SnortConfig_t *g_snort_config = NULL;
+
+
+/******************************************************/
+/* This is to keep track of the running Snort process */
+typedef struct {
+ gboolean running;
+ gboolean working;
+
+ GPid pid;
+ int in, out, err; /* fds for talking to snort process */
+
+ GString *buf; /* Incomplete alert output that has been read */
+ wtap_dumper *pdh; /* wiretap dumper used to deliver packets to 'in' */
+
+ GIOChannel *channel; /* IO channel used for readimg stdout (alerts) */
+
+ wmem_tree_t *alerts_tree; /* Lookup from frame-number -> Alerts_t* */
+} snort_session_t;
+
+/* Global instance of the snort session */
+static snort_session_t current_session;
+
+static int snort_config_ok = TRUE; /* N.B. Not running test at the moment... */
+
+
+
+/*************************************************/
+/* An alert.
+ Created by parsing alert from snort, hopefully with more details linked from matched_rule. */
+typedef struct Alert_t {
+ /* Time */
+ struct timeval tv;
+ /* Rule */
+ guint32 sid; /* Rule identifier */
+ guint32 rev; /* Revision number of rule */
+ guint32 gen; /* Which engine generated alert (not often interesting) */
+ int prio; /* Priority as reported in alert (not usually interesting) */
+
+ const char *raw_alert; /* The whole alert string as reported by snort */
+
+ char *msg; /* Rule msg/description as it appears in the alert */
+ char *classification; /* Classification type of rule */
+
+ Rule_t *matched_rule; /* Link to corresponding rule from snort config */
+
+ /* Stats for this alert among the capture file. */
+ unsigned int overall_match_number;
+ unsigned int rule_match_number;
+} Alert_t;
+
+/* Can have multiple alerts fire on same frame, so define this container */
+typedef struct Alerts_t {
+/* N.B. Snort limit appears to be 6 (at least with default config..) */
+#define MAX_ALERTS_PER_FRAME 8
+ Alert_t alerts[MAX_ALERTS_PER_FRAME];
+ guint num_alerts;
+} Alerts_t;
+
+
+/* Add an alert to the map stored in current_session */
+static void add_alert_to_session_tree(guint frame_number, Alert_t *alert)
+{
+ /* First look up tree to see if there is an existing entry */
+ Alerts_t *alerts = (Alerts_t*)wmem_tree_lookup32(current_session.alerts_tree, frame_number);
+ if (alerts == NULL) {
+ /* Create a new entry for the table */
+ alerts = (Alerts_t*)g_malloc(sizeof(Alerts_t));
+ alerts->alerts[0] = *alert;
+ alerts->num_alerts = 1;
+ wmem_tree_insert32(current_session.alerts_tree, frame_number, alerts);
+ }
+ else {
+ /* See if there is room in the existing Alerts_t struct for this frame */
+ if (alerts->num_alerts < MAX_ALERTS_PER_FRAME) {
+ alerts->alerts[alerts->num_alerts++] = *alert;
+ }
+ }
+}
+
+
+/******************************************************************/
+
+/* Given an alert struct, look up by Snort ID (sid) and try to fill in other details to display. */
+static void fill_alert_config(SnortConfig_t *snort_config, Alert_t *alert)
+{
+ guint global_match_number=0, rule_match_number=0;
+
+ /* Look up rule by sid */
+ alert->matched_rule = get_rule(snort_config, alert->sid);
+
+ /* Classtype usually filled in from alert rather than rule, but missing for supsported
+ comment format. */
+ if (pref_snort_alerts_source == FromUserComments) {
+ alert->classification = g_strdup(alert->matched_rule->classtype);
+ }
+
+ /* Inform the config/rule about the alert */
+ rule_set_alert(snort_config, alert->matched_rule,
+ &global_match_number, &rule_match_number);
+ /* Copy updated counts into the alert */
+ alert->overall_match_number = global_match_number;
+ alert->rule_match_number = rule_match_number;
+}
+
+
+/* Helper functions for matching expected bytes against the packet buffer.
+ Case-sensitive comparison - can just memcmp().
+ Case-insensitive comparison - need to look at each byte and compare uppercase version */
+static gboolean content_compare_case_sensitive(const guint8* memory, const char* target, guint length)
+{
+ return (memcmp(memory, target, length) == 0);
+}
+
+static gboolean content_compare_case_insensitive(const guint8* memory, const char* target, guint length)
+{
+ for (guint n=0; n < length; n++) {
+ if (g_ascii_isalpha(target[n])) {
+ if (g_ascii_toupper(memory[n]) != g_ascii_toupper(target[n])) {
+ return FALSE;
+ }
+ }
+ else {
+ if ((guint8)memory[n] != (guint8)target[n]) {
+ return FALSE;
+ }
+ }
+ }
+
+ return TRUE;
+}
+
+
+/* Move through the bytes of the tvbuff, looking for a match against the expanded
+ binary contents of this content object.
+ */
+static gboolean look_for_content(content_t *content, tvbuff_t *tvb, guint start_offset, guint *match_offset, guint *match_length)
+{
+ gint tvb_len = tvb_captured_length(tvb);
+
+ /* Make sure content has been translated into binary string. */
+ guint converted_content_length = content_convert_to_binary(content);
+
+ /* Look for a match at each position. */
+ for (guint m=start_offset; m <= (tvb_len-converted_content_length); m++) {
+ const guint8 *ptr = tvb_get_ptr(tvb, m, converted_content_length);
+ if (content->nocase) {
+ if (content_compare_case_insensitive(ptr, content->binary_str, content->translated_length)) {
+ *match_offset = m;
+ *match_length = content->translated_length;
+ return TRUE;
+ }
+ }
+ else {
+ if (content_compare_case_sensitive(ptr, content->binary_str, content->translated_length)) {
+ *match_offset = m;
+ *match_length = content->translated_length;
+ return TRUE;
+ }
+ }
+ }
+
+ return FALSE;
+}
+
+
+
+
+/* Look for where the content match happens within the tvb.
+ * Set out parameters match_offset and match_length */
+static gboolean get_content_match(Alert_t *alert, guint content_idx,
+ tvbuff_t *tvb, guint content_start_match,
+ guint *match_offset, guint *match_length)
+{
+ content_t *content;
+ Rule_t *rule = alert->matched_rule;
+
+ /* Can't match if don't know rule */
+ if (rule == NULL) {
+ return FALSE;
+ }
+
+ /* Get content object. */
+ content = &(rule->contents[content_idx]);
+
+ /* Look for content match in the packet */
+ return look_for_content(content, tvb, content_start_match, match_offset, match_length);
+}
+
+
+/* Gets called when snort process has died */
+static void snort_reaper(GPid pid, gint status _U_, gpointer data)
+{
+ snort_session_t *session = (snort_session_t *) data;
+ if (session->running && session->pid == pid) {
+ session->working = session->running = FALSE;
+ /* XXX, cleanup */
+ } else {
+ g_print("Errrrmm snort_reaper() %d != %d\n", session->pid, pid);
+ }
+
+ /* Close the snort pid (may only make a difference on Windows?) */
+ g_spawn_close_pid(pid);
+}
+
+/* Parse timestamp line of output. This is done in part to get the packet_number back out of usec field...
+ * Return valuee is the input stream moved onto the next field following the timestamp */
+static const char* snort_parse_ts(const char *ts, struct timeval *tv)
+{
+ struct tm tm;
+ unsigned int usec;
+
+ /* Timestamp */
+ memset(&tm, 0, sizeof(tm));
+ tm.tm_isdst = -1;
+ if (sscanf(ts, "%02d/%02d/%02d-%02d:%02d:%02d.%06u ",
+ &(tm.tm_mon), &(tm.tm_mday), &(tm.tm_year), &(tm.tm_hour), &(tm.tm_min), &(tm.tm_sec), &usec) != 7) {
+ return NULL;
+ }
+ tm.tm_mon -= 1;
+ tm.tm_year += 100;
+
+ tv->tv_sec = (long)mktime(&tm);
+ tv->tv_usec = usec;
+
+ return strchr(ts, ' ');
+}
+
+/* Parse a fast output alert string */
+static gboolean snort_parse_fast_line(const char *line, Alert_t *alert)
+{
+ static const char stars[] = " [**] ";
+
+ static const char classification[] = "[Classification: ";
+ static const char priority[] = "[Priority: ";
+ const char *tmp_msg;
+
+ /* Look for timestamp */
+ if (!(line = snort_parse_ts(line, &(alert->tv)))) {
+ return FALSE;
+ }
+
+ /* [**] */
+ if (!g_str_has_prefix(line+1, stars)) {
+ return FALSE;
+ }
+ line += sizeof(stars);
+
+ /* [%u:%u:%u] */
+ if (sscanf(line, "[%u:%u:%u] ", &(alert->gen), &(alert->sid), &(alert->rev)) != 3) {
+ return FALSE;
+ }
+ if (!(line = strchr(line, ' '))) {
+ return FALSE;
+ }
+
+ /* [**] again */
+ tmp_msg = line+1;
+ if (!(line = strstr(line, stars))) {
+ return FALSE;
+ }
+
+ /* msg */
+ alert->msg = g_strndup(tmp_msg, line - tmp_msg);
+ line += (sizeof(stars)-1);
+
+ /* [Classification: Attempted Administrator Privilege Gain] [Priority: 10] */
+
+ if (g_str_has_prefix(line, classification)) {
+ /* [Classification: %s] */
+ char *tmp;
+ line += (sizeof(classification)-1);
+
+ if (!(tmp = (char*)strstr(line, "] [Priority: "))) {
+ return FALSE;
+ }
+
+ /* assume "] [Priority: " is not inside classification text :) */
+ alert->classification = g_strndup(line, tmp - line);
+
+ line = tmp+2;
+ } else
+ alert->classification = NULL;
+
+ /* Optimized: if al->classification we already checked this in strstr() above */
+ if (alert->classification || g_str_has_prefix(line, priority)) {
+ /* [Priority: %d] */
+ line += (sizeof(priority)-1);
+
+ if ((sscanf(line, "%d", &(alert->prio))) != 1) {
+ return FALSE;
+ }
+
+ if (!(line = strstr(line, "] "))) {
+ return FALSE;
+ }
+ } else {
+ alert->prio = -1; /* XXX */
+ }
+
+ return TRUE;
+}
+
+/**
+ * snort_parse_user_comment()
+ *
+ * Parse line as written by TraceWranger
+ * e.g. "1:2011768:4 - ET WEB_SERVER PHP tags in HTTP POST"
+ */
+static gboolean snort_parse_user_comment(const char *line, Alert_t *alert)
+{
+ /* %u:%u:%u */
+ if (sscanf(line, "%u:%u:%u", &(alert->gen), &(alert->sid), &(alert->rev)) != 3) {
+ return FALSE;
+ }
+
+ /* Skip separator between numbers and msg */
+ if (!(line = strstr(line, " - "))) {
+ return FALSE;
+ }
+
+ /* Copy to be consistent with other use of Alert_t */
+ alert->msg = g_strdup(line);
+
+ /* No need to set other fields as assume zero'd out before this call.. */
+ return TRUE;
+}
+
+/* Output data has been received from snort. Read from channel and look for whole alerts. */
+static gboolean snort_fast_output(GIOChannel *source, GIOCondition condition, gpointer data)
+{
+ snort_session_t *session = (snort_session_t *)data;
+
+ /* Loop here until all available input read */
+ while (condition & G_IO_IN) {
+ GIOStatus status;
+ char _buf[1024];
+ gsize len = 0;
+
+ char *old_buf = NULL;
+ char *buf = _buf;
+ char *line;
+
+ /* Try to read snort output info _buf */
+ status = g_io_channel_read_chars(source, _buf, sizeof(_buf)-1, &len, NULL);
+ if (status != G_IO_STATUS_NORMAL) {
+ if (status == G_IO_STATUS_AGAIN) {
+ /* Blocked, so unset G_IO_IN and get out of this function */
+ condition = (GIOCondition)(condition & ~G_IO_IN);
+ break;
+ }
+ /* Other conditions here could be G_IO_STATUS_ERROR, G_IO_STATUS_EOF */
+ return FALSE;
+ }
+ /* Terminate buffer */
+ buf[len] = '\0';
+
+ /* If we previously had part of a line, append the new bit we just saw */
+ if (session->buf) {
+ g_string_append(session->buf, buf);
+ buf = old_buf = g_string_free(session->buf, FALSE);
+ session->buf = NULL;
+ }
+
+ /* Extract every complete line we find in the output */
+ while ((line = strchr(buf, '\n'))) {
+ /* Have a whole line, so can parse */
+ Alert_t alert;
+
+ /* Terminate received line */
+ *line = '\0';
+
+ if (snort_parse_fast_line(buf, &alert)) {
+ /*******************************************************/
+ /* We have an alert line. */
+#if 0
+ g_print("%ld.%lu [%u,%u,%u] %s {%s} [%d]\n",
+ alert.tv.tv_sec, alert.tv.tv_usec,
+ alert.gen, alert.sid, alert.rev,
+ alert.msg,
+ alert.classification ? alert.classification : "(null)",
+ alert.prio);
+#endif
+
+ /* Copy the raw alert string itself */
+ alert.raw_alert = g_strdup(buf);
+
+ /* See if we can get more info from the parsed config details */
+ fill_alert_config(g_snort_config, &alert);
+
+ /* Add parsed alert into session->tree */
+ /* Store in tree. pfino->fd->num is hidden in usec time field. */
+ add_alert_to_session_tree((guint)alert.tv.tv_usec, &alert);
+
+ }
+ else {
+ g_print("snort_fast_output() line: '%s'\n", buf);
+ }
+
+ buf = line+1;
+ }
+
+ if (buf[0]) {
+ /* Only had part of a line - store it */
+ /* N.B. typically happens maybe once every 5-6 alerts. */
+ session->buf = g_string_new(buf);
+ }
+
+ g_free(old_buf);
+ }
+
+ if (condition) {
+ /* Will report errors (hung-up, or error) */
+
+ /* g_print("snort_fast_output() cond: (h:%d,e:%d,r:%d)\n",
+ * !!(condition & G_IO_HUP), !!(condition & G_IO_ERR), condition); */
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+
+/* Return the offset in the frame where snort should begin looking inside payload. */
+static guint get_protocol_payload_start(const char *protocol, proto_tree *tree)
+{
+ guint value = 0;
+
+ /* For icmp, look from start, whereas for others start after them. */
+ gboolean look_after_protocol = (strcmp(protocol, "icmp") != 0);
+
+ if (tree != NULL) {
+ GPtrArray *items = proto_all_finfos(tree);
+ if (items) {
+ guint i;
+ for (i=0; i< items->len; i++) {
+ field_info *field = (field_info *)g_ptr_array_index(items,i);
+ if (strcmp(field->hfinfo->abbrev, protocol) == 0) {
+ value = field->start;
+ if (look_after_protocol) {
+ value += field->length;
+ }
+ break;
+ }
+ }
+ g_ptr_array_free(items,TRUE);
+ }
+ }
+ return value;
+}
+
+
+/* Return offset that application layer traffic will begin from. */
+static guint get_content_start_match(Rule_t *rule, proto_tree *tree)
+{
+ /* Work out where snort would start looking for data in the frame */
+ return get_protocol_payload_start(rule->protocol, tree);
+}
+
+/* Show the Snort protocol tree based on the info in alert */
+static void snort_show_alert(proto_tree *tree, tvbuff_t *tvb, packet_info *pinfo, Alert_t *alert)
+{
+ proto_tree *snort_tree = NULL;
+ unsigned int n;
+ proto_item *ti, *rule_ti;
+ proto_tree *rule_tree;
+ Rule_t *rule = alert->matched_rule;
+
+ /* Can only find start if we have the rule and know the protocol */
+ guint content_start_match = 0;
+ if (rule) {
+ content_start_match = get_content_start_match(rule, tree);
+ }
+
+ /* Snort output arrived and was previously stored - so add to tree */
+ /* Take care not to try to highlight bytes that aren't there.. */
+ proto_item *alert_ti = proto_tree_add_protocol_format(tree, proto_snort, tvb,
+ content_start_match >= tvb_captured_length(tvb) ? 0 : content_start_match,
+ content_start_match >= tvb_captured_length(tvb) ? 0 : -1,
+ "Snort: (msg: \"%s\" sid: %u rev: %u) [from %s]",
+ alert->msg, alert->sid, alert->rev,
+ (pref_snort_alerts_source == FromUserComments) ?
+ "User Comment" :
+ "Running Snort");
+ snort_tree = proto_item_add_subtree(alert_ti, ett_snort);
+
+ /* Show in expert info if configured to. */
+ if (snort_show_alert_expert_info) {
+ expert_add_info_format(pinfo, alert_ti, &ei_snort_alert, "Alert %u: \"%s\"", alert->sid, alert->msg);
+ }
+
+ /* Show the raw alert string. */
+ if (rule) {
+ ti = proto_tree_add_string(snort_tree, hf_snort_raw_alert, tvb, 0, 0, alert->raw_alert);
+ PROTO_ITEM_SET_GENERATED(ti);
+ }
+
+ /* Rule classification */
+ if (alert->classification) {
+ ti = proto_tree_add_string(snort_tree, hf_snort_classification, tvb, 0, 0, alert->classification);
+ PROTO_ITEM_SET_GENERATED(ti);
+ }
+
+ /* Put rule fields under a rule subtree */
+
+ rule_ti = proto_tree_add_string_format(snort_tree, hf_snort_rule, tvb, 0, 0, "", "Rule");
+ PROTO_ITEM_SET_GENERATED(rule_ti);
+ rule_tree = proto_item_add_subtree(rule_ti, ett_snort_rule);
+
+ /* msg/description */
+ ti = proto_tree_add_string(rule_tree, hf_snort_msg, tvb, 0, 0, alert->msg);
+ PROTO_ITEM_SET_GENERATED(ti);
+ /* Snort ID */
+ ti = proto_tree_add_uint(rule_tree, hf_snort_sid, tvb, 0, 0, alert->sid);
+ PROTO_ITEM_SET_GENERATED(ti);
+ /* Rule revision */
+ ti = proto_tree_add_uint(rule_tree, hf_snort_rev, tvb, 0, 0, alert->rev);
+ PROTO_ITEM_SET_GENERATED(ti);
+ /* Generator seems to correspond to gid. */
+ ti = proto_tree_add_uint(rule_tree, hf_snort_generator, tvb, 0, 0, alert->gen);
+ PROTO_ITEM_SET_GENERATED(ti);
+ /* Default priority is 2 - very few rules have a different priority... */
+ ti = proto_tree_add_uint(rule_tree, hf_snort_priority, tvb, 0, 0, alert->prio);
+ PROTO_ITEM_SET_GENERATED(ti);
+
+ /* If we know the rule for this alert, show some of the rule fields */
+ if (rule && rule->rule_string) {
+ size_t rule_string_length = strlen(rule->rule_string);
+
+ /* Show rule string itself. Add it as a separate data source so can read it all */
+ if (rule_string_length > 60) {
+ tvbuff_t *rule_string_tvb = tvb_new_child_real_data(tvb, rule->rule_string,
+ (guint)rule_string_length,
+ (guint)rule_string_length);
+ add_new_data_source(pinfo, rule_string_tvb, "Rule String");
+ ti = proto_tree_add_string(rule_tree, hf_snort_rule_string, rule_string_tvb, 0,
+ (gint)rule_string_length,
+ rule->rule_string);
+ }
+ else {
+ ti = proto_tree_add_string(rule_tree, hf_snort_rule_string, tvb, 0, 0,
+ rule->rule_string);
+ }
+ PROTO_ITEM_SET_GENERATED(ti);
+
+ /* Protocol from rule */
+ ti = proto_tree_add_string(rule_tree, hf_snort_rule_protocol, tvb, 0, 0, rule->protocol);
+ PROTO_ITEM_SET_GENERATED(ti);
+
+ /* Show file alert came from */
+ ti = proto_tree_add_string(rule_tree, hf_snort_rule_filename, tvb, 0, 0, rule->file);
+ PROTO_ITEM_SET_GENERATED(ti);
+ /* Line number within file */
+ ti = proto_tree_add_uint(rule_tree, hf_snort_rule_line_number, tvb, 0, 0, rule->line_number);
+ PROTO_ITEM_SET_GENERATED(ti);
+
+ /* Show IP vars */
+ for (n=0; n < rule->relevant_vars.num_ip_vars; n++) {
+ ti = proto_tree_add_none_format(rule_tree, hf_snort_rule_ip_var, tvb, 0, 0, "IP Var: ($%s -> %s)",
+ rule->relevant_vars.ip_vars[n].name,
+ rule->relevant_vars.ip_vars[n].value);
+ PROTO_ITEM_SET_GENERATED(ti);
+ }
+ /* Show Port vars */
+ for (n=0; n < rule->relevant_vars.num_port_vars; n++) {
+ ti = proto_tree_add_none_format(rule_tree, hf_snort_rule_ip_var, tvb, 0, 0, "Port Var: ($%s -> %s)",
+ rule->relevant_vars.port_vars[n].name,
+ rule->relevant_vars.port_vars[n].value);
+ PROTO_ITEM_SET_GENERATED(ti);
+ }
+ }
+
+
+ /* Show summary information in rule tree root */
+ proto_item_append_text(rule_ti, " %s (sid=%u, rev=%u)",
+ alert->msg, alert->sid, alert->rev);
+
+ /* More fields retrieved from the parsed config */
+ if (rule) {
+ guint content_last_match_end = 0;
+
+ /* Work out which ip and port vars are relevant */
+ rule_set_relevant_vars(g_snort_config, rule);
+
+ /* Contents */
+ for (n=0; n < rule->number_contents; n++) {
+
+ /* Search for string among tvb contents so we can highlight likely bytes. */
+ unsigned int content_offset;
+ gboolean match_found = FALSE;
+ unsigned int converted_content_length;
+ int content_hf_item;
+ char *content_text_template;
+ gboolean attempt_match;
+
+ /* Choose type of content field to add */
+ switch (rule->contents[n].content_type) {
+ case Content:
+ content_hf_item = hf_snort_content;
+ content_text_template = "Content: \"%s\"";
+ attempt_match = TRUE;
+ break;
+ case UriContent:
+ content_hf_item = hf_snort_uricontent;
+ content_text_template = "Uricontent: \"%s\"";
+ attempt_match = TRUE;
+ break;
+ case Pcre:
+ content_hf_item = hf_snort_pcre;
+ content_text_template = "Pcre: \"%s\"";
+ attempt_match = FALSE;
+ break;
+ default:
+ continue;
+ }
+
+ /* Will only try to look for content in packet ourselves if not
+ a negated content entry (i.e. beginning with '!') */
+ if (attempt_match && !rule->contents[n].negation) {
+ /* Look up offset of match. N.B. would only expect to see on first content... */
+ guint offset_to_add = 0;
+
+ /* May need to add absolute offset into packet... */
+ if (rule->contents[n].offset_set) {
+ offset_to_add = rule->contents[n].offset;
+ }
+ /* ... or a number of bytes beyond the previous content match */
+ else if (rule->contents[n].distance_set) {
+ offset_to_add = (content_last_match_end-content_start_match) + rule->contents[n].distance;
+ }
+
+ /* Now actually look for match from calculated position */
+ /* TODO: could take 'depth' and 'within' into account to limit extent of search,
+ but OK if just trying to verify what Snort already found. */
+ match_found = get_content_match(alert, n,
+ tvb, content_start_match+offset_to_add,
+ &content_offset, &converted_content_length);
+ if (match_found) {
+ content_last_match_end = content_offset + converted_content_length;
+ }
+ }
+
+
+ /* Show content in tree (showing position if known) */
+ ti = proto_tree_add_string_format(snort_tree, content_hf_item, tvb,
+ (match_found) ? content_offset : 0,
+ (match_found) ? converted_content_length : 0,
+ rule->contents[n].str,
+ content_text_template,
+ rule->contents[n].str);
+ if (!attempt_match) {
+ /* TODO: for pcre could try to use same library used by
+ display filter 'matches' operator? */
+ proto_item_append_text(ti, " (no match attempt made)");
+ }
+
+ /* Show (only as text) attributes of content field */
+ if (rule->contents[n].fastpattern) {
+ proto_item_append_text(ti, " (fast_pattern)");
+ }
+ if (rule->contents[n].nocase) {
+ proto_item_append_text(ti, " (nocase)");
+ }
+ if (rule->contents[n].negation) {
+ proto_item_append_text(ti, " (negated)");
+ }
+ if (rule->contents[n].offset_set) {
+ proto_item_append_text(ti, " (offset=%d)", rule->contents[n].offset);
+ }
+ if (rule->contents[n].depth != 0) {
+ proto_item_append_text(ti, " (depth=%u)", rule->contents[n].depth);
+ }
+ if (rule->contents[n].distance_set) {
+ proto_item_append_text(ti, " (distance=%d)", rule->contents[n].distance);
+ }
+ if (rule->contents[n].within != 0) {
+ proto_item_append_text(ti, " (within=%u)", rule->contents[n].within);
+ }
+
+ /* HTTP preprocessor modifiers */
+ if (rule->contents[n].http_method != 0) {
+ proto_item_append_text(ti, " (http_method)");
+ }
+ if (rule->contents[n].http_client_body != 0) {
+ proto_item_append_text(ti, " (http_client_body)");
+ }
+ if (rule->contents[n].http_cookie != 0) {
+ proto_item_append_text(ti, " (http_cookie)");
+ }
+
+ if (attempt_match && !rule->contents[n].negation && !match_found) {
+ /* Useful for debugging, may also happen when Snort is reassembling.. */
+ proto_item_append_text(ti, " - not located");
+ expert_add_info_format(pinfo, ti, &ei_snort_content_not_matched,
+ "Content \"%s\" not found in frame",
+ rule->contents[n].str);
+ }
+ }
+
+ /* References */
+ for (n=0; n < rule->number_references; n++) {
+ /* Substitute prefix and add to tree as clickable web links */
+ ti = proto_tree_add_string(snort_tree, hf_snort_reference, tvb, 0, 0,
+ expand_reference(g_snort_config, rule->references[n]));
+ /* Make clickable */
+ PROTO_ITEM_SET_URL(ti);
+ PROTO_ITEM_SET_GENERATED(ti);
+ }
+ }
+
+ /* Global rule stats if configured to. */
+ if (snort_show_rule_stats) {
+ unsigned int number_rule_files, number_rules, alerts_detected, this_rule_alerts_detected;
+ proto_item *stats_ti;
+ proto_tree *stats_tree;
+
+ /* Create tree for these items */
+ stats_ti = proto_tree_add_string_format(snort_tree, hf_snort_global_stats, tvb, 0, 0, "", "Global Stats");
+ PROTO_ITEM_SET_GENERATED(rule_ti);
+ stats_tree = proto_item_add_subtree(stats_ti, ett_snort_global_stats);
+
+ /* Get overall number of rules */
+ get_global_rule_stats(g_snort_config, alert->sid, &number_rule_files, &number_rules, &alerts_detected,
+ &this_rule_alerts_detected);
+ ti = proto_tree_add_uint(stats_tree, hf_snort_global_stats_rule_file_count, tvb, 0, 0, number_rule_files);
+ PROTO_ITEM_SET_GENERATED(ti);
+ ti = proto_tree_add_uint(stats_tree, hf_snort_global_stats_rule_count, tvb, 0, 0, number_rules);
+ PROTO_ITEM_SET_GENERATED(ti);
+
+ /* Overall alert stats (total, and where this one comes in order) */
+ ti = proto_tree_add_uint(stats_tree, hf_snort_global_stats_total_alerts_count, tvb, 0, 0, alerts_detected);
+ PROTO_ITEM_SET_GENERATED(ti);
+ ti = proto_tree_add_uint(stats_tree, hf_snort_global_stats_alert_match_number, tvb, 0, 0, alert->overall_match_number);
+ PROTO_ITEM_SET_GENERATED(ti);
+
+ if (rule) {
+ /* Stats just for this rule (overall, and where this one comes in order) */
+ ti = proto_tree_add_uint(stats_tree, hf_snort_global_stats_rule_alerts_count, tvb, 0, 0, this_rule_alerts_detected);
+ PROTO_ITEM_SET_GENERATED(ti);
+ ti = proto_tree_add_uint(stats_tree, hf_snort_global_stats_rule_match_number, tvb, 0, 0, alert->rule_match_number);
+ PROTO_ITEM_SET_GENERATED(ti);
+
+ /* Add a summary to the stats root */
+ proto_item_append_text(stats_ti, " (%u rules from %u files, #%u of %u alerts seen (%u/%u for sid %u))",
+ number_rules, number_rule_files, alert->overall_match_number, alerts_detected,
+ alert->rule_match_number, this_rule_alerts_detected, alert->sid);
+ }
+ else {
+ /* Add a summary to the stats root */
+ proto_item_append_text(stats_ti, " (%u rules from %u files, #%u of %u alerts seen)",
+ number_rules, number_rule_files, alert->overall_match_number, alerts_detected);
+ }
+ }
+}
+
+/* Look for, and return, any user comment set for this packet.
+ Currently used for fetching alerts in the format TraceWrangler can write out to */
+static const char *get_user_comment_string(proto_tree *tree)
+{
+ const char *value = NULL;
+
+ if (tree != NULL) {
+ GPtrArray *items = proto_all_finfos(tree);
+ if (items) {
+ guint i;
+
+ for (i=0; i< items->len; i++) {
+ field_info *field = (field_info *)g_ptr_array_index(items,i);
+ if (strcmp(field->hfinfo->abbrev, "frame.comment") == 0) {
+ value = field->value.value.string;
+ break;
+ }
+ /* This is the only item that can come before "frame.comment", so otherwise break out */
+ if (strncmp(field->hfinfo->abbrev, "pkt_comment", 11) != 0) {
+ break;
+ }
+ }
+ g_ptr_array_free(items,TRUE);
+ }
+ }
+ return value;
+}
+
+
+#if 0
+/* TODO: unfortunately, the first frame in a series of frames to be reassembled has often been
+ * seen to lack this field, despite being referenced in the reassmbled frame! */
+static guint get_reassembled_in_frame(proto_tree *tree)
+{
+ guint value = 0;
+
+ if (tree != NULL) {
+ GPtrArray *items = proto_all_finfos(tree);
+ if (items) {
+ guint i;
+ for (i=0; i< items->len; i++) {
+ field_info *field = (field_info *)g_ptr_array_index(items,i);
+ if (strcmp(field->hfinfo->abbrev, "tcp.reassembled_in") == 0) {
+ value = field->value.value.uinteger;
+ break;
+ }
+ }
+ g_ptr_array_free(items,TRUE);
+ }
+ }
+ return value;
+}
+#endif
+
+/********************************************************************************/
+/* Main (post-)dissector function. */
+static int
+snort_dissector(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_)
+{
+ Alerts_t *alerts;
+
+ /* Are we looking for alerts in user comments? */
+ if (pref_snort_alerts_source == FromUserComments) {
+ /* Look for user comments containing alerts */
+ const char *alert_string = get_user_comment_string(tree);
+ if (alert_string) {
+ alerts = (Alerts_t*)wmem_tree_lookup32(current_session.alerts_tree, pinfo->num);
+ if (!alerts) {
+ Alert_t alert;
+ memset(&alert, 0, sizeof(alert));
+ if (snort_parse_user_comment(alert_string, &alert)) {
+ /* Copy the raw alert itself */
+ alert.raw_alert = g_strdup(alert_string);
+
+ /* See if we can get more info from the parsed config details */
+ fill_alert_config(g_snort_config, &alert);
+
+ /* Add parsed alert into session->tree */
+ add_alert_to_session_tree(pinfo->num, &alert);
+ }
+ }
+ }
+ }
+ else {
+ /* We expect alerts from Snort. Pass frame into snort on first pass. */
+ if (!pinfo->fd->flags.visited && current_session.working) {
+ int write_err = 0;
+ gchar *err_info;
+ struct wtap_pkthdr wtp;
+
+ /* First time, open current_session.in to write to for dumping into snort with */
+ if (!current_session.pdh) {
+ int open_err;
+
+ /* Older versions of Snort don't support capture file with several encapsulations (like pcapng),
+ * so write in pcap format and hope we have just one encap.
+ * Newer versions of Snort can read pcapng now, but still write in pcap format.
+ */
+ current_session.pdh = wtap_dump_fdopen(current_session.in,
+ WTAP_FILE_TYPE_SUBTYPE_PCAP,
+ pinfo->pkt_encap,
+ WTAP_MAX_PACKET_SIZE,
+ FALSE, /* compressed */
+ &open_err);
+ if (!current_session.pdh) {
+ current_session.working = FALSE;
+ return 0;
+ }
+ }
+
+ /* Start with all same values... */
+ memcpy(&wtp, pinfo->phdr, sizeof(wtp));
+
+ /* Copying packet details into wtp for writing */
+ wtp.ts.secs = pinfo->fd->abs_ts.secs;
+ wtp.ts.nsecs = pinfo->fd->abs_ts.nsecs;
+
+ /* NB: overwriting wtp.ts.nsecs so we can see packet number back if an alert is written for this frame!!!! */
+ /* TODO: does this seriously affect snort's ability to reason about time?
+ * At least all packets will still be in order... */
+ wtp.ts.nsecs = pinfo->fd->num * 1000; /* XXX, max 999'999 frames */
+
+ wtp.caplen = tvb_captured_length(tvb);
+ wtp.len = tvb_reported_length(tvb);
+ wtp.pkt_encap = pinfo->pkt_encap;
+ if (current_session.pdh->encap != wtp.pkt_encap) {
+ /* XXX, warning! convert? */
+ }
+
+ /* Dump frame into snort's stdin */
+ if (!wtap_dump(current_session.pdh, &wtp, tvb_get_ptr(tvb, 0, tvb_reported_length(tvb)), &write_err, &err_info)) {
+ current_session.working = FALSE;
+ return 0;
+ }
+ wtap_dump_flush(current_session.pdh);
+
+ /* Give the io channel a chance to deliver alerts.
+ TODO: g_main_context_iteration(NULL, FALSE); causes crashes sometimes when Qt events get to execute.. */
+ }
+ }
+
+ /* Now look up stored alerts for this packet number, and display if found */
+ if (current_session.alerts_tree && (alerts = (Alerts_t*)wmem_tree_lookup32(current_session.alerts_tree, pinfo->fd->num))) {
+ guint n;
+
+ for (n=0; n < alerts->num_alerts; n++) {
+ snort_show_alert(tree, tvb, pinfo, &(alerts->alerts[n]));
+ }
+ } else {
+ /* XXX, here either this frame doesn't generate alerts or we haven't received data from snort (async)
+ *
+ * It's problem when user want to filter tree on initial run, or is running one-pass tshark.
+ */
+ }
+
+ return tvb_reported_length(tvb);
+}
+
+/* N.B. is being called.. */
+static void snort_config(gpointer user_data _U_)
+{
+ /* N.B. original code tried to get line-buffered (or unbuffered) output from snort.
+ It wasn't very portable, and measurements indicated it didn't make any difference
+ to how often whole lines were output. */
+}
+
+/*------------------------------------------------------------------*/
+/* Start up Snort. */
+static void snort_start(void)
+{
+ GIOChannel *channel;
+ /* int snort_output_id; */
+ const gchar *argv[] = {
+ pref_snort_binary_filename, "-c", pref_snort_config_filename,
+ /* read from stdin */
+ "-r", "-",
+ /* don't log */
+ "-N",
+ /* output to console and silence snort */
+ "-A", "console", "-q",
+ /* normalize time */
+ "-y", /* -U", */
+ NULL
+ };
+
+ /* Create tree mapping packet_number -> Alerts_t*. It will get recreated when packet list is reloaded */
+ current_session.alerts_tree = wmem_tree_new_autoreset(wmem_epan_scope(), wmem_file_scope());
+
+ /* Create afresh the config object by parsing the same file that snort uses */
+ if (g_snort_config) {
+ delete_config(&g_snort_config);
+ }
+ create_config(&g_snort_config, pref_snort_config_filename);
+
+ /* Don't run Snort if not configured to */
+ if (pref_snort_alerts_source == FromUserComments) {
+ return;
+ }
+
+ if (current_session.running) {
+ return;
+ }
+ current_session.running = TRUE;
+
+ /* Reset global stats */
+ reset_global_rule_stats(g_snort_config);
+
+ /* Need to test that we can run snort --version and that config can be parsed... */
+ /* Does nothing at present */
+ if (!snort_config_ok) {
+ current_session.running = FALSE;
+ /* Can carry on without snort... */
+ return;
+ }
+
+ /* Create snort process and set up pipes */
+ if (!g_spawn_async_with_pipes(NULL, /* working_directory */
+ (char **)argv,
+ NULL, /* envp */
+ (GSpawnFlags)( G_SPAWN_DO_NOT_REAP_CHILD), /* Leave out G_SPAWN_SEARCH_PATH */
+ snort_config, /* child setup - not currently doing anything.. */
+ NULL, /* user-data */
+ &current_session.pid, /* PID */
+ &current_session.in, /* stdin */
+ &current_session.out, /* stdout */
+ &current_session.err, /* stderr */
+ NULL)) /* error */
+ {
+ current_session.running = FALSE;
+ current_session.working = FALSE;
+ return;
+ }
+
+ /* Setup handler for when process goes away */
+ g_child_watch_add(current_session.pid, snort_reaper, &current_session);
+
+ /******************************************************************/
+ /* Create channel to get notified of snort alert output on stdout */
+
+ /* Create channel itself */
+ channel = g_io_channel_unix_new(current_session.out);
+ current_session.channel = channel;
+
+ /* NULL encoding supports binary or whatever the application outputs */
+ g_io_channel_set_encoding(channel, NULL, NULL);
+ /* Don't buffer the channel (settable because encoding set to NULL). */
+ g_io_channel_set_buffered(channel, FALSE);
+ /* Set flags */
+ /* TODO: could set to be blocking and get sync that way? */
+ g_io_channel_set_flags(channel, G_IO_FLAG_NONBLOCK, NULL);
+ /* Try setting a large buffer here. */
+ g_io_channel_set_buffer_size(channel, 256000);
+
+ current_session.buf = NULL;
+
+ /* Set callback for receiving data from the channel */
+ g_io_add_watch_full(channel,
+ G_PRIORITY_HIGH,
+ (GIOCondition)(G_IO_IN|G_IO_ERR|G_IO_HUP),
+ snort_fast_output, /* Callback upon data being written by snort */
+ &current_session, /* User data */
+ NULL); /* Destroy notification callback */
+
+ current_session.working = TRUE;
+}
+
+/* This is the cleanup routine registered with register_postseq_cleanup_routine() */
+static void snort_cleanup(void)
+{
+ /* Only close if we think its running */
+ if (!current_session.running) {
+ return;
+ }
+
+ /* Close dumper writing into snort's stdin. This will cause snort to exit! */
+ if (current_session.pdh) {
+ int write_err;
+ if (!wtap_dump_close(current_session.pdh, &write_err)) {
+
+ }
+ current_session.pdh = NULL;
+ }
+}
+
+static void snort_file_cleanup(void)
+{
+ if (g_snort_config) {
+ delete_config(&g_snort_config);
+ }
+}
+
+void
+proto_reg_handoff_snort(void)
+{
+ /* N.B. snort self-test here deleted, as I was struggling to get it to
+ * work as a non-root user (couldn't read stdin)
+ * TODO: could run snort just to get the version number and check the config file is readable?
+ * TODO: could make snort config parsing less forgiving and use that as a test? */
+
+ /* Our own preference for turning off completely. Don't want to run at all unless turned on */
+ proto_set_decoding(proto_snort, snort_enable_dissector);
+}
+
+void
+proto_register_snort(void)
+{
+ static hf_register_info hf[] = {
+ { &hf_snort_sid,
+ { "Rule SID", "snort.sid", FT_UINT32, BASE_DEC, NULL, 0x00,
+ "Snort Rule identifier", HFILL }},
+ { &hf_snort_raw_alert,
+ { "Raw Alert", "snort.raw-alert", FT_STRING, BASE_NONE, NULL, 0x00,
+ "Full text of Snort alert", HFILL }},
+ { &hf_snort_rule,
+ { "Rule", "snort.rule", FT_STRING, BASE_NONE, NULL, 0x00,
+ "Entire Snort rule string", HFILL }},
+ { &hf_snort_msg,
+ { "Alert Message", "snort.msg", FT_STRINGZ, BASE_NONE, NULL, 0x00,
+ "Description of what the rule detects", HFILL }},
+ { &hf_snort_classification,
+ { "Alert Classification", "snort.class", FT_STRINGZ, BASE_NONE, NULL, 0x00,
+ NULL, HFILL }},
+ { &hf_snort_priority,
+ { "Alert Priority", "snort.priority", FT_UINT32, BASE_DEC, NULL, 0x00,
+ NULL, HFILL }},
+ { &hf_snort_generator,
+ { "Rule Generator", "snort.generator", FT_UINT32, BASE_DEC, NULL, 0x00,
+ NULL, HFILL }},
+ { &hf_snort_rev,
+ { "Rule Revision", "snort.rev", FT_UINT32, BASE_DEC, NULL, 0x00,
+ NULL, HFILL }},
+ { &hf_snort_rule_string,
+ { "Rule String", "snort.rule-string", FT_STRINGZ, BASE_NONE, NULL, 0x00,
+ "Full text of Snort rule", HFILL }},
+ { &hf_snort_rule_protocol,
+ { "Protocol", "snort.protocol", FT_STRINGZ, BASE_NONE, NULL, 0x00,
+ "Protocol name as given in the rule", HFILL }},
+ { &hf_snort_rule_filename,
+ { "Rule Filename", "snort.rule-filename", FT_STRINGZ, BASE_NONE, NULL, 0x00,
+ "Rules file where Snort rule was parsed from", HFILL }},
+ { &hf_snort_rule_line_number,
+ { "Line number within rules file where rule was parsed from", "snort.rule-line-number", FT_UINT32, BASE_DEC, NULL, 0x00,
+ NULL, HFILL }},
+ { &hf_snort_rule_ip_var,
+ { "IP variable", "snort.rule-ip-var", FT_NONE, BASE_NONE, NULL, 0x00,
+ "IP variable used in rule", HFILL }},
+ { &hf_snort_rule_port_var,
+ { "Port variable used in rule", "snort.rule-port-var", FT_NONE, BASE_NONE, NULL, 0x00,
+ NULL, HFILL }},
+ { &hf_snort_content,
+ { "Content", "snort.content", FT_STRINGZ, BASE_NONE, NULL, 0x00,
+ "Snort content field", HFILL }},
+ { &hf_snort_uricontent,
+ { "URI Content", "snort.uricontent", FT_STRINGZ, BASE_NONE, NULL, 0x00,
+ "Snort URI content field", HFILL }},
+ { &hf_snort_pcre,
+ { "PCRE", "snort.pcre", FT_STRINGZ, BASE_NONE, NULL, 0x00,
+ "Perl Compatible Regular Expression", HFILL }},
+ { &hf_snort_reference,
+ { "Reference", "snort.reference", FT_STRINGZ, BASE_NONE, NULL, 0x00,
+ "Web reference provided as part of rule", HFILL }},
+
+ /* Global stats */
+ { &hf_snort_global_stats,
+ { "Global Stats", "snort.global-stats", FT_STRING, BASE_NONE, NULL, 0x00,
+ "Global statistics for rules and alerts", HFILL }},
+ { &hf_snort_global_stats_rule_file_count,
+ { "Number of rule files", "snort.global-stats.rule-file-count", FT_UINT32, BASE_DEC, NULL, 0x00,
+ "Total number of rules files found in Snort config", HFILL }},
+ { &hf_snort_global_stats_rule_count,
+ { "Number of rules", "snort.global-stats.rule-count", FT_UINT32, BASE_DEC, NULL, 0x00,
+ "Total number of rules found in Snort config", HFILL }},
+ { &hf_snort_global_stats_total_alerts_count,
+ { "Number of alerts detected", "snort.global-stats.total-alerts", FT_UINT32, BASE_DEC, NULL, 0x00,
+ "Total number of alerts detected in this capture", HFILL }},
+ { &hf_snort_global_stats_alert_match_number,
+ { "Match number", "snort.global-stats.match-number", FT_UINT32, BASE_DEC, NULL, 0x00,
+ "Number of match for this alert among all alerts", HFILL }},
+
+ { &hf_snort_global_stats_rule_alerts_count,
+ { "Number of alerts for this rule", "snort.global-stats.rule.match-number", FT_UINT32, BASE_DEC, NULL, 0x00,
+ "Number of alerts detected for this rule", HFILL }},
+ { &hf_snort_global_stats_rule_match_number,
+ { "Match number for this rule", "snort.global-stats.rule.match-number", FT_UINT32, BASE_DEC, NULL, 0x00,
+ "Number of match for this alert among those for this rule", HFILL }}
+ };
+ static gint *ett[] = {
+ &ett_snort,
+ &ett_snort_rule,
+ &ett_snort_global_stats
+ };
+
+ static const enum_val_t alerts_source_vals[] = {
+ {"from-running-snort", "From running Snort", FromRunningSnort},
+ {"from-user-comments", "From user comments", FromUserComments},
+ {NULL, NULL, -1}
+ };
+
+ static ei_register_info ei[] = {
+ { &ei_snort_alert, { "snort.alert.expert", PI_SECURITY, PI_WARN, "Snort alert detected", EXPFILL }},
+ { &ei_snort_content_not_matched, { "snort.content.not-matched", PI_PROTOCOL, PI_NOTE, "Failed to find content field of alert in frame", EXPFILL }},
+ };
+
+ expert_module_t* expert_snort;
+
+
+ dissector_handle_t snort_handle;
+ module_t *snort_module;
+
+ proto_snort = proto_register_protocol("Snort Alerts", "Snort", "snort");
+
+ proto_register_field_array(proto_snort, hf, array_length(hf));
+ proto_register_subtree_array(ett, array_length(ett));
+
+ /* Expert info */
+ expert_snort = expert_register_protocol(proto_snort);
+ expert_register_field_array(expert_snort, ei, array_length(ei));
+
+ snort_module = prefs_register_protocol(proto_snort, proto_reg_handoff_snort);
+
+ prefs_register_bool_preference(snort_module, "enable_snort_dissector",
+ "Enable the snort dissector",
+ "Whether or not the snort post-dissector should run.",
+ &snort_enable_dissector);
+
+ prefs_register_enum_preference(snort_module, "alerts_source",
+ "Source of Snort alerts",
+ "Set whether dissector should run Snort itself or use user packet comments",
+ &pref_snort_alerts_source, alerts_source_vals, FALSE);
+
+ prefs_register_filename_preference(snort_module, "binary",
+ "Snort binary",
+ "The name of the snort binary file to run",
+ &pref_snort_binary_filename);
+ prefs_register_filename_preference(snort_module, "config",
+ "Configuration filename",
+ "The name of the file containing the snort IDS configuration. Typically snort.conf",
+ &pref_snort_config_filename);
+
+ prefs_register_bool_preference(snort_module, "show_rule_set_stats",
+ "Show rule stats in protocol tree",
+ "Whether or not information about the rule set and detected alerts should "
+ "be shown in the tree of every snort PDU tree",
+ &snort_show_rule_stats);
+ prefs_register_bool_preference(snort_module, "show_alert_expert_info",
+ "Show alerts in expert info",
+ "Whether or not expert info should be used to highlight fired alerts",
+ &snort_show_alert_expert_info);
+#if 0
+ prefs_register_bool_preference(snort_module, "show_alert_in_reassembled_frame",
+ "Try to show alerts in reassembled frame",
+ "Attempt to show alert in reassembled frame where possible",
+ &snort_alert_in_reassembled_frame);
+#endif
+
+
+ snort_handle = create_dissector_handle(snort_dissector, proto_snort);
+
+ register_init_routine(snort_start);
+ register_postdissector(snort_handle);
+
+ /* Callback to make sure we cleanup dumper being used to deliver packets to snort (this will tsnort). */
+ register_postseq_cleanup_routine(snort_cleanup);
+ /* Callback to allow us to delete snort config */
+ register_cleanup_routine(snort_file_cleanup);
+}
+
+/*
+ * Editor modelines - http://www.wireshark.org/tools/modelines.html
+ *
+ * Local variables:
+ * c-basic-offset: 4
+ * tab-width: 8
+ * indent-tabs-mode: nil
+ * End:
+ *
+ * vi: set shiftwidth=4 tabstop=8 expandtab:
+ * :indentSize=4:tabSize=8:noTabs=true:
+ */
diff --git a/epan/dissectors/snort-config.c b/epan/dissectors/snort-config.c
new file mode 100644
index 0000000000..1d53e5de5c
--- /dev/null
+++ b/epan/dissectors/snort-config.c
@@ -0,0 +1,1103 @@
+/* snort-config.c
+ *
+ * Copyright 2016, Martin Mathieson
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+
+#include <wsutil/file_util.h>
+#include <wsutil/strtoi.h>
+
+#include "snort-config.h"
+
+/* #define SNORT_CONFIG_DEBUG */
+#ifdef SNORT_CONFIG_DEBUG
+#define snort_debug_printf printf
+#else
+#define snort_debug_printf(...)
+#endif
+
+#ifndef _WIN32
+const char* g_file_separator = "/";
+#else
+const char* g_file_separator = "\\";
+#endif
+
+
+/* Forward declaration */
+static void parse_config_file(SnortConfig_t *snort_config, FILE *config_file_fd, const char *filename, const char *dirname, int recursion_level);
+
+/* Skip white space from 'source', return pointer to first non-whitespace char */
+static char *skipWhiteSpace(char *source, int *accumulated_offset)
+{
+ int offset = 0;
+
+ /* Skip any leading whitespace */
+ while (source[offset] != '\0' && source[offset] == ' ') {
+ offset++;
+ }
+
+ *accumulated_offset += offset;
+ return source + offset;
+}
+
+/* Read a token from source, stop when get to end of string or delimiter. */
+/* - source: input string
+ * - delimiter: char to stop at
+ * - length: out param set to delimiter or end-of-string offset
+ * - accumulated_Length: out param that gets length added to it
+ * - copy: whether or an allocated string should be returned
+ * - returns: requested string. Returns from static buffer when copy is FALSE */
+static char* read_token(char* source, char delimeter, int *length, int *accumulated_length, gboolean copy)
+{
+ static char static_buffer[512];
+ int offset = 0;
+
+ char *source_proper = skipWhiteSpace(source, accumulated_length);
+
+ while (source_proper[offset] != '\0' && source_proper[offset] != delimeter) {
+ offset++;
+ }
+
+ *length = offset;
+ *accumulated_length += offset;
+ if (copy) {
+ /* Copy into new string */
+ char *new_string = g_strndup(source_proper, offset+1);
+ new_string[offset] = '\0';
+ return new_string;
+ }
+ else {
+ /* Return in static buffer */
+ memcpy(&static_buffer, source_proper, offset);
+ static_buffer[offset] = '\0';
+ return static_buffer;
+ }
+}
+
+/* Add a new content field to the rule */
+static gboolean rule_add_content(Rule_t *rule, const char *content_string, gboolean negated)
+{
+ if (rule->number_contents < MAX_CONTENT_ENTRIES) {
+ content_t *new_content = &(rule->contents[rule->number_contents++]);
+ new_content->str = g_strdup(content_string);
+ new_content->negation = negated;
+ rule->last_added_content = new_content;
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/* Set the nocase property for a rule */
+static void rule_set_content_nocase(Rule_t *rule)
+{
+ if (rule->last_added_content) {
+ rule->last_added_content->nocase = TRUE;
+ }
+}
+
+/* Set the offset property of a content field */
+static void rule_set_content_offset(Rule_t *rule, gint value)
+{
+ if (rule->last_added_content) {
+ rule->last_added_content->offset = value;
+ rule->last_added_content->offset_set = TRUE;
+ }
+}
+
+/* Set the depth property of a content field */
+static void rule_set_content_depth(Rule_t *rule, guint value)
+{
+ if (rule->last_added_content) {
+ rule->last_added_content->depth = value;
+ }
+}
+
+/* Set the distance property of a content field */
+static void rule_set_content_distance(Rule_t *rule, gint value)
+{
+ if (rule->last_added_content) {
+ rule->last_added_content->distance = value;
+ rule->last_added_content->distance_set = TRUE;
+ }
+}
+
+/* Set the distance property of a content field */
+static void rule_set_content_within(Rule_t *rule, guint value)
+{
+ if (rule->last_added_content) {
+ /* Assuming won't be 0... */
+ rule->last_added_content->within = value;
+ }
+}
+
+/* Set the fastpattern property of a content field */
+static void rule_set_content_fast_pattern(Rule_t *rule)
+{
+ if (rule->last_added_content) {
+ rule->last_added_content->fastpattern = TRUE;
+ }
+}
+
+/* Set the http_method property of a content field */
+static void rule_set_content_http_method(Rule_t *rule)
+{
+ if (rule->last_added_content) {
+ rule->last_added_content->http_method = TRUE;
+ }
+}
+
+/* Set the http_client property of a content field */
+static void rule_set_content_http_client_body(Rule_t *rule)
+{
+ if (rule->last_added_content) {
+ rule->last_added_content->http_client_body = TRUE;
+ }
+}
+
+/* Set the http_cookie property of a content field */
+static void rule_set_content_http_cookie(Rule_t *rule)
+{
+ if (rule->last_added_content) {
+ rule->last_added_content->http_cookie = TRUE;
+ }
+}
+
+/* Add a uricontent field to the rule */
+static gboolean rule_add_uricontent(Rule_t *rule, const char *uricontent_string, gboolean negated)
+{
+ if (rule_add_content(rule, uricontent_string, negated)) {
+ rule->last_added_content->content_type = UriContent;
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/* This content field now becomes a uricontent after seeing modifier */
+static void rule_set_http_uri(Rule_t *rule)
+{
+ if (rule->last_added_content != NULL) {
+ rule->last_added_content->content_type = UriContent;
+ }
+}
+
+/* Add a pcre field to the rule */
+static gboolean rule_add_pcre(Rule_t *rule, const char *pcre_string)
+{
+ if (rule_add_content(rule, pcre_string, FALSE)) {
+ rule->last_added_content->content_type = Pcre;
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/* Set the rule's classtype field */
+static gboolean rule_set_classtype(Rule_t *rule, const char *classtype)
+{
+ rule->classtype = g_strdup(classtype);
+ return TRUE;
+}
+
+/* Add a reference string to the rule */
+static void rule_add_reference(Rule_t *rule, const char *reference_string)
+{
+ if (rule->number_references < MAX_REFERENCE_ENTRIES) {
+ rule->references[rule->number_references++] = g_strdup(reference_string);
+ }
+}
+
+/* Check to see if the ip 'field' corresponds to an entry in the ipvar dictionary.
+ * If it is add entry to rule */
+static void rule_check_ip_vars(SnortConfig_t *snort_config, Rule_t *rule, char *field)
+{
+ gpointer original_key = NULL;
+ gpointer value = NULL;
+
+ /* Make sure field+1 not NULL. */
+ if (strlen(field) < 2) {
+ return;
+ }
+
+ /* Make sure there is room for another entry */
+ if (rule->relevant_vars.num_ip_vars >= MAX_RULE_IP_VARS) {
+ return;
+ }
+
+ /* TODO: a loop re-looking up the answer until its not just another ipvar! */
+ if (g_hash_table_lookup_extended(snort_config->ipvars, field+1, &original_key, &value)) {
+
+ rule->relevant_vars.ip_vars[rule->relevant_vars.num_ip_vars].name = (char*)original_key;
+ rule->relevant_vars.ip_vars[rule->relevant_vars.num_ip_vars].value = (char*)value;
+
+ rule->relevant_vars.num_ip_vars++;
+ }
+}
+
+/* Check to see if the port 'field' corresponds to an entry in the portvar dictionary.
+ * If it is add entry to rule */
+static void rule_check_port_vars(SnortConfig_t *snort_config _U_, Rule_t *rule, char *field)
+{
+ gpointer original_key = NULL;
+ gpointer value = NULL;
+
+ /* Make sure field+1 not NULL. */
+ if (strlen(field) < 2) {
+ return;
+ }
+
+ /* Make sure there is room for another entry */
+ if (rule->relevant_vars.num_port_vars >= MAX_RULE_PORT_VARS) {
+ return;
+ }
+
+ /* TODO: a loop re-looking up the answer until its not just another portvar! */
+ if (g_hash_table_lookup_extended(snort_config->portvars, field+1, &original_key, &value)) {
+ rule->relevant_vars.port_vars[rule->relevant_vars.num_port_vars].name = (char*)original_key;
+ rule->relevant_vars.port_vars[rule->relevant_vars.num_port_vars].value = (char*)value;
+
+ rule->relevant_vars.num_port_vars++;
+ }
+}
+
+/* Look over the IP addresses and ports, and work out which variables/values are being used */
+void rule_set_relevant_vars(SnortConfig_t *snort_config, Rule_t *rule)
+{
+ int length;
+ int accumulated_length = 0;
+ char *field;
+
+ /* No need to do this twice */
+ if (rule->relevant_vars.relevant_vars_set) {
+ return;
+ }
+
+ /* Walk tokens up to the options, and look up ones that are addresses or ports. */
+
+ /* Skip "alert" */
+ read_token(rule->rule_string+accumulated_length, ' ', &length, &accumulated_length, FALSE);
+
+ /* Skip protocol. */
+ read_token(rule->rule_string+accumulated_length, ' ', &length, &accumulated_length, FALSE);
+
+ /* Read source address */
+ field = read_token(rule->rule_string+accumulated_length, ' ', &length, &accumulated_length, FALSE);
+ snort_debug_printf("source address is (%s)\n", field);
+ rule_check_ip_vars(snort_config, rule, field);
+
+ /* Read source port */
+ field = read_token(rule->rule_string+accumulated_length, ' ', &length, &accumulated_length, FALSE);
+ snort_debug_printf("source port is (%s)\n", field);
+ rule_check_port_vars(snort_config, rule, field);
+
+ /* Read direction */
+ read_token(rule->rule_string+accumulated_length, ' ', &length, &accumulated_length, FALSE);
+
+ /* Dest address */
+ field = read_token(rule->rule_string+accumulated_length, ' ', &length, &accumulated_length, FALSE);
+ snort_debug_printf("dest address is (%s)\n", field);
+ rule_check_ip_vars(snort_config, rule, field);
+
+ /* Dest port */
+ field = read_token(rule->rule_string+accumulated_length, ' ', &length, &accumulated_length, FALSE);
+ snort_debug_printf("dest port is (%s)\n", field);
+ rule_check_port_vars(snort_config, rule, field);
+
+ /* Set flag so won't do again for this rule */
+ rule->relevant_vars.relevant_vars_set = TRUE;
+}
+
+
+typedef enum vartype_e { var, ipvar, portvar, unknownvar } vartype_e;
+
+/* Look for a "var", "ipvar" or "portvar" entry in this line */
+static gboolean parse_variables_line(SnortConfig_t *snort_config, char *line)
+{
+ vartype_e var_type = unknownvar;
+
+ char * variable_type;
+ char * variable_name;
+ char * value;
+
+ int length;
+ int accumulated_length = 0;
+
+ /* Get variable type */
+ variable_type = read_token(line, ' ', &length, &accumulated_length, FALSE);
+ if (variable_type == NULL) {
+ return FALSE;
+ }
+
+ if (strncmp(variable_type, "var", 3) == 0) {
+ var_type = var;
+ }
+ else if (strncmp(variable_type, "ipvar", 5) == 0) {
+ var_type = ipvar;
+ }
+ else if (strncmp(variable_type, "portvar", 7) == 0) {
+ var_type = portvar;
+ }
+ else {
+ return FALSE;
+ }
+
+ /* Get variable name */
+ variable_name = read_token(line+ accumulated_length, ' ', &length, &accumulated_length, TRUE);
+ if (variable_name == NULL) {
+ return FALSE;
+ }
+
+ /* Now value */
+ value = read_token(line + accumulated_length, ' ', &length, &accumulated_length, TRUE);
+ if (value == NULL) {
+ return FALSE;
+ }
+
+ /* Add (name->value) to table according to variable type. */
+ switch (var_type) {
+ case var:
+ if (strcmp(variable_name, "RULE_PATH") == 0) {
+ /* This can be relative or absolute. */
+ snort_config->rule_path = value;
+ snort_config->rule_path_is_absolute = g_path_is_absolute(value);
+ snort_debug_printf("rule_path set to %s (is_absolute=%d)\n",
+ snort_config->rule_path, snort_config->rule_path_is_absolute);
+ }
+ g_hash_table_insert(snort_config->vars, variable_name, value);
+ break;
+ case ipvar:
+ g_hash_table_insert(snort_config->ipvars, variable_name, value);
+ break;
+ case portvar:
+ g_hash_table_insert(snort_config->portvars, variable_name, value);
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ return FALSE;
+}
+
+/* Hash function for where key is a string. Just add up the value of each character and return that.. */
+static guint string_hash(gconstpointer key)
+{
+ guint total=0, n=0;
+ const char *key_string = (const char *)key;
+ char c = key_string[n];
+
+ while (c != '\0') {
+ total += (int)c;
+ c = key_string[++n];
+ }
+ return total;
+}
+
+/* Comparison function for where key is a string. Simple comparison using strcmp() */
+static gboolean string_equal(gconstpointer a, gconstpointer b)
+{
+ const char *stringa = (const char*)a;
+ const char *stringb = (const char*)b;
+
+ return (strcmp(stringa, stringb) == 0);
+}
+
+/* Process a line that configures a reference line (invariably from 'reference.config') */
+static gboolean parse_references_prefix_file_line(SnortConfig_t *snort_config, char *line)
+{
+ char *source;
+ char *prefix_name, *prefix_value;
+ int length=0, accumulated_length=0;
+ int n;
+
+ if (strncmp(line, "config reference: ", 18) != 0) {
+ return FALSE;
+ }
+
+ /* Read the prefix and value */
+ source = line+18;
+ prefix_name = read_token(source, ' ', &length, &accumulated_length, TRUE);
+ /* Store all name chars in lower case. */
+ for (n=0; prefix_name[n] != '\0'; n++) {
+ prefix_name[n] = g_ascii_tolower(prefix_name[n]);
+ }
+
+ prefix_value = read_token(source+accumulated_length, ' ', &length, &accumulated_length, TRUE);
+
+ /* Add entry into table */
+ g_hash_table_insert(snort_config->references_prefixes, prefix_name, prefix_value);
+
+ return FALSE;
+}
+
+/* Try to expand the reference using the prefixes stored in the config */
+char *expand_reference(SnortConfig_t *snort_config, char *reference)
+{
+ static char expanded_reference[512];
+ int length = (int)strlen(reference);
+ int accumulated_length = 0;
+
+ /* Extract up to ',', then substitute prefix! */
+ snort_debug_printf("expand_reference(%s)\n", reference);
+ char *prefix = read_token(reference, ',', &length, &accumulated_length, FALSE);
+
+ if (prefix != '\0') {
+ /* Convert to lowercase before lookup */
+ guint n;
+ for (n=0; prefix[n] != '\0'; n++) {
+ prefix[n] = g_ascii_tolower(prefix[n]);
+ }
+
+ /* Look up prefix in table. */
+ char *prefix_replacement;
+ prefix_replacement = (char*)g_hash_table_lookup(snort_config->references_prefixes, prefix);
+
+ /* Append prefix and remainder, and return!!!! */
+ g_snprintf(expanded_reference, 512, "%s%s", prefix_replacement, reference+length+1);
+ return expanded_reference;
+ }
+ return "ERROR: Reference didn't contain prefix and ','!";
+}
+
+/* The rule has been matched with an alert, so update global config stats */
+void rule_set_alert(SnortConfig_t *snort_config, Rule_t *rule,
+ guint *global_match_number,
+ guint *rule_match_number)
+{
+ snort_config->stat_alerts_detected++;
+ *global_match_number = snort_config->stat_alerts_detected;
+ if (rule != NULL) {
+ *rule_match_number = ++rule->matches_seen;
+ }
+}
+
+
+
+/* Delete an individual entry from a string table. */
+static gboolean delete_string_entry(gpointer key,
+ gpointer value,
+ gpointer user_data _U_)
+{
+ char *key_string = (char*)key;
+ char *value_string = (char*)value;
+
+ g_free(key_string);
+ g_free(value_string);
+
+ return TRUE;
+}
+
+/* See if this is an include line, if it is open the file and call parse_config_file() */
+static gboolean parse_include_file(SnortConfig_t *snort_config, char *line, const char *config_directory, int recursion_level)
+{
+ int length;
+ int accumulated_length = 0;
+ char *include_filename;
+
+ /* Look for "include " */
+ char *include_token = read_token(line, ' ', &length, &accumulated_length, FALSE);
+ if (strlen(include_token) == 0) {
+ return FALSE;
+ }
+ if (strncmp(include_token, "include", 7) != 0) {
+ return FALSE;
+ }
+
+ /* Read the filename */
+ include_filename = read_token(line+accumulated_length, ' ', &length, &accumulated_length, FALSE);
+ if (include_filename != '\0') {
+ FILE *new_config_fd;
+ char substituted_filename[512];
+ gboolean is_rule_file = FALSE;
+
+ /* May need to substitute variables into include path. */
+ if (strncmp(include_filename, "$RULE_PATH", 10) == 0) {
+ /* Write rule path variable value */
+ /* Don't assume $RULE_PATH will end in a file separator */
+ if (snort_config->rule_path_is_absolute) {
+ g_snprintf(substituted_filename, 512, "%s%s%s",
+ snort_config->rule_path,
+ g_file_separator,
+ include_filename + 10);
+ }
+ else {
+ g_snprintf(substituted_filename, 512, "%s%s%s%s%s",
+ config_directory,
+ g_file_separator,
+ snort_config->rule_path,
+ g_file_separator,
+ include_filename + 10);
+ }
+ is_rule_file = TRUE;
+ }
+ else {
+ /* No $RULE_PATH, just use directory and filename */
+ g_snprintf(substituted_filename, 512, "%s/%s", config_directory, include_filename);
+ }
+
+ /* Try to open the file. */
+ snort_debug_printf("Trying to open: %s\n", substituted_filename);
+ new_config_fd = ws_fopen(substituted_filename, "r");
+ if (new_config_fd == NULL) {
+ snort_debug_printf("Failed to open config file %s\n", substituted_filename);
+ return FALSE;
+ }
+
+ /* Parse the file */
+ if (is_rule_file) {
+ snort_config->stat_rules_files++;
+ }
+ parse_config_file(snort_config, new_config_fd, substituted_filename, config_directory, recursion_level + 1);
+
+ /* Close the file */
+ fclose(new_config_fd);
+
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/* Process an individual option - i.e. the elements found between '(' and ')' */
+static void process_rule_option(Rule_t *rule, char *options, int option_start_offset, int options_end_offset, int colon_offset)
+{
+ static char name[1024], value[1024];
+ name[0] = '\0';
+ value[0] = '\0';
+ gint value_length = 0;
+ guint32 value32 = 0;
+
+ if (colon_offset != 0) {
+ /* Name and value */
+ g_snprintf(name, colon_offset-option_start_offset, "%s", options+option_start_offset);
+ g_snprintf(value, options_end_offset-colon_offset, "%s", options+colon_offset);
+ value_length = (gint)strlen(value);
+ }
+ else {
+ /* Just name */
+ g_snprintf(name, options_end_offset-option_start_offset, "%s", options+option_start_offset);
+ }
+
+ /* Do this extraction in one place (may not be number but should be OK) */
+ ws_strtoi32(value, (const gchar**)&value[value_length], &value32);
+
+ /* Process the rule options that we are interested in */
+ if (strcmp(name, "msg") == 0) {
+ rule->msg = g_strdup(value);
+ }
+ else if (strcmp(name, "sid") == 0) {
+ rule->sid = value32;
+ }
+ else if (strcmp(name, "rev") == 0) {
+ value32 = rule->rev;
+ }
+ else if (strcmp(name, "content") == 0) {
+ int value_start = 0;
+
+ if (value_length < 3) {
+ return;
+ }
+
+ /* Need to trim off " ", but first check for ! */
+ if (value[0] == '!') {
+ value_start = 1;
+ if (value_length < 4) {
+ return;
+ }
+ }
+
+ value[options_end_offset-colon_offset-2] = '\0';
+ rule_add_content(rule, value+value_start+1, value_start == 1);
+ }
+ else if (strcmp(name, "uricontent") == 0) {
+ int value_start = 0;
+
+ if (value_length < 3) {
+ return;
+ }
+
+ /* Need to trim off " ", but first check for ! */
+ if (value[0] == '!') {
+ value_start = 1;
+ if (value_length < 4) {
+ return;
+ }
+ }
+
+ value[options_end_offset-colon_offset-2] = '\0';
+ rule_add_uricontent(rule, value+value_start+1, value_start == 1);
+ }
+ else if (strcmp(name, "http_uri") == 0) {
+ rule_set_http_uri(rule);
+ }
+ else if (strcmp(name, "pcre") == 0) {
+ rule_add_pcre(rule, value);
+ }
+ else if (strcmp(name, "nocase") == 0) {
+ rule_set_content_nocase(rule);
+ }
+ else if (strcmp(name, "offset") == 0) {
+ rule_set_content_offset(rule, value32);
+ }
+ else if (strcmp(name, "depth") == 0) {
+ rule_set_content_depth(rule, value32);
+ }
+ else if (strcmp(name, "within") == 0) {
+ rule_set_content_within(rule, value32);
+ }
+ else if (strcmp(name, "distance") == 0) {
+ rule_set_content_distance(rule, value32);
+ }
+ else if (strcmp(name, "fast_pattern") == 0) {
+ rule_set_content_fast_pattern(rule);
+ }
+ else if (strcmp(name, "http_method") == 0) {
+ rule_set_content_http_method(rule);
+ }
+ else if (strcmp(name, "http_client_body") == 0) {
+ rule_set_content_http_client_body(rule);
+ }
+ else if (strcmp(name, "http_cookie") == 0) {
+ rule_set_content_http_cookie(rule);
+ }
+
+ else if (strcmp(name, "classtype") == 0) {
+ rule_set_classtype(rule, value);
+ }
+ else if (strcmp(name, "reference") == 0) {
+ rule_add_reference(rule, value);
+ }
+ else {
+ /* Ignore an option we don't currently handle */
+ }
+}
+
+/* Parse a Snort alert, return TRUE if successful */
+static gboolean parse_rule(SnortConfig_t *snort_config, char *line, const char *filename, int line_number, int line_length)
+{
+ char *options_start;
+ char *options;
+ gboolean in_quotes = FALSE;
+ int options_start_index = 0, options_index = 0, colon_offset = 0;
+ char c;
+ int length;
+ Rule_t *rule = NULL;
+
+ /* Rule will begin with alert */
+ if (strncmp(line, "alert ", 6) != 0) {
+ return FALSE;
+ }
+
+ /* Allocate the rule itself */
+ rule = (Rule_t*)g_malloc(sizeof(Rule_t));
+
+ snort_debug_printf("looks like a rule: %s\n", line);
+ memset(rule, 0, sizeof(Rule_t));
+
+ rule->rule_string = g_strdup(line);
+ rule->file = g_strdup(filename);
+ rule->line_number = line_number;
+
+ /* Next token is the protocol */
+ rule->protocol = read_token(line+6, ' ', &length, &length, TRUE);
+
+ /* Find start of options. */
+ options_start = strstr(line, "(");
+ if (options_start == NULL) {
+ snort_debug_printf("start of options not found\n");
+ return FALSE;
+ }
+ options_index = (int)(options_start-line) + 1;
+
+ /* To make parsing simpler, replace final ')' with ';' */
+ if (line[line_length-1] != ')') {
+ g_free(rule);
+ return FALSE;
+ }
+ else {
+ line[line_length-1] = ';';
+ }
+
+ /* Now look for next ';', process one option at a time */
+ options = &line[options_index];
+ options_index = 0;
+
+ while ((c = options[options_index++])) {
+ if (c == '"') {
+ in_quotes = !in_quotes;
+ }
+ /* Ignore ; or ; if inside quotes */
+ if (!in_quotes) {
+ if (c == ':') {
+ colon_offset = options_index;
+ }
+ if (c == ';') {
+ /* End of current option - add to rule. */
+ process_rule_option(rule, options, options_start_index, options_index, colon_offset);
+
+ /* Skip any spaces before next option */
+ while (options[options_index] == ' ') options_index++;
+
+ /* Next rule will start here */
+ options_start_index = options_index;
+ colon_offset = 0;
+ in_quotes = FALSE;
+ }
+ }
+ }
+
+ /* Add rule to map of rules. */
+ g_hash_table_insert(snort_config->rules, GUINT_TO_POINTER((guint)rule->sid), rule);
+
+ return TRUE;
+}
+
+/* Delete an individual rule */
+static gboolean delete_rule(gpointer key _U_,
+ gpointer value,
+ gpointer user_data _U_)
+{
+ Rule_t *rule = (Rule_t*)value;
+ unsigned int n;
+
+ snort_debug_printf("delete_rule(value=%p)\n", value);
+
+ /* Delete strings on heap. */
+ g_free(rule->rule_string);
+ g_free(rule->file);
+ g_free(rule->msg);
+ g_free(rule->classtype);
+ g_free(rule->protocol);
+
+ for (n=0; n < rule->number_contents; n++) {
+ g_free(rule->contents[n].str);
+ g_free(rule->contents[n].binary_str);
+ }
+
+ for (n=0; n < rule->number_references; n++) {
+ g_free(rule->references[n]);
+ }
+
+ snort_debug_printf("Freeing rule at :%p\n", rule);
+ g_free(rule);
+ return TRUE;
+}
+
+
+/* Create a new config, starting with the given snort config file. */
+/* N.B. using recursion_level to limit stack depth. */
+static void parse_config_file(SnortConfig_t *snort_config, FILE *config_file_fd,
+ const char *filename, const char *dirname, int recursion_level)
+{
+ #define MAX_LINE_LENGTH 4096
+ char line[MAX_LINE_LENGTH];
+ int line_number = 0;
+
+ snort_debug_printf("parse_config_file(filename=%s, recursion_level=%d)\n", filename, recursion_level);
+
+ if (recursion_level > 8) {
+ return;
+ }
+
+ /* Read each line of the file in turn, and see if we want any info from it. */
+ while (fgets(line, MAX_LINE_LENGTH, config_file_fd)) {
+
+ int line_length;
+ ++line_number;
+
+ /* Nothing interesting to parse */
+ if ((line[0] == '\0') || (line[0] == '#')) {
+ continue;
+ }
+
+ /* Trim newline from end */
+ line_length = (int)strlen(line);
+ while (line_length && ((line[line_length - 1] == '\n') || (line[line_length - 1] == '\r'))) {
+ --line_length;
+ }
+ line[line_length] = '\0';
+ if (line_length == 0) {
+ continue;
+ }
+
+ /* Offer line to the various parsing functions. Could optimise order.. */
+ if (parse_variables_line(snort_config, line)) {
+ continue;
+ }
+ if (parse_references_prefix_file_line(snort_config, line)) {
+ continue;
+ }
+ if (parse_include_file(snort_config, line, dirname, recursion_level)) {
+ continue;
+ }
+ if (parse_rule(snort_config, line, filename, line_number, line_length)) {
+ snort_config->stat_rules++;
+ continue;
+ }
+ }
+}
+
+
+
+/* Create the global ConfigParser */
+void create_config(SnortConfig_t **snort_config, const char *snort_config_file)
+{
+ gchar* dirname;
+ gchar* basename;
+ FILE *config_file_fd;
+
+ snort_debug_printf("create_config (%s)\n", snort_config_file);
+
+ *snort_config = (SnortConfig_t*)g_malloc(sizeof(SnortConfig_t));
+ memset(*snort_config, 0, sizeof(SnortConfig_t));
+
+ /* Create rule table */
+ (*snort_config)->rules = g_hash_table_new(g_direct_hash, g_direct_equal);
+
+ /* Create reference prefix table */
+ (*snort_config)->references_prefixes = g_hash_table_new(string_hash, string_equal);
+
+ /* Vars tables */
+ (*snort_config)->vars = g_hash_table_new(string_hash, string_equal);
+ (*snort_config)->ipvars = g_hash_table_new(string_hash, string_equal);
+ (*snort_config)->portvars = g_hash_table_new(string_hash, string_equal);
+
+ /* Extract separate directory and filename. */
+ dirname = g_path_get_dirname(snort_config_file);
+ basename = g_path_get_basename(snort_config_file);
+
+ /* Attempt to open the config file */
+ config_file_fd = ws_fopen(snort_config_file, "r");
+ if (config_file_fd == NULL) {
+ snort_debug_printf("Failed to open config file %s\n", snort_config_file);
+ return;
+ }
+
+ /* Start parsing from the top-level config file. */
+ parse_config_file(*snort_config, config_file_fd, snort_config_file, dirname, 0);
+
+ g_free(dirname);
+ g_free(basename);
+
+ fclose(config_file_fd);
+}
+
+
+/* Delete the entire config */
+void delete_config(SnortConfig_t **snort_config)
+{
+ snort_debug_printf("delete_config()\n");
+
+ /* Iterate over all rules, freeing each one! */
+ g_hash_table_foreach_remove((*snort_config)->rules, delete_rule, NULL);
+ g_hash_table_destroy((*snort_config)->rules);
+
+ /* References table */
+ g_hash_table_foreach_remove((*snort_config)->references_prefixes, delete_string_entry, NULL);
+ g_hash_table_destroy((*snort_config)->references_prefixes);
+
+ /* Free up variable tables */
+ g_hash_table_foreach_remove((*snort_config)->vars, delete_string_entry, NULL);
+ g_hash_table_destroy((*snort_config)->vars);
+ g_hash_table_foreach_remove((*snort_config)->ipvars, delete_string_entry, NULL);
+ g_hash_table_destroy((*snort_config)->ipvars);
+ g_hash_table_foreach_remove((*snort_config)->portvars, delete_string_entry, NULL);
+ g_hash_table_destroy((*snort_config)->portvars);
+
+ g_free(*snort_config);
+
+ *snort_config = NULL;
+}
+
+/* Look for a rule corresponding to the given SID */
+Rule_t *get_rule(SnortConfig_t *snort_config, guint32 sid)
+{
+ if ((snort_config == NULL) || (snort_config->rules == NULL)) {
+ return NULL;
+ }
+ else {
+ return (Rule_t*)g_hash_table_lookup(snort_config->rules, GUINT_TO_POINTER(sid));
+ }
+}
+
+/* Fetch some statistics. */
+void get_global_rule_stats(SnortConfig_t *snort_config, unsigned int sid,
+ unsigned int *number_rules_files, unsigned int *number_rules,
+ unsigned int *alerts_detected, unsigned int *this_rule_alerts_detected)
+{
+ *number_rules_files = snort_config->stat_rules_files;
+ *number_rules = snort_config->stat_rules;
+ *alerts_detected = snort_config->stat_alerts_detected;
+ Rule_t *rule;
+
+ /* Look up rule and get current/total matches */
+ rule = get_rule(snort_config, sid);
+ if (rule) {
+ *this_rule_alerts_detected = rule->matches_seen;
+ }
+ else {
+ *this_rule_alerts_detected = 0;
+ }
+}
+
+/* Reset stats on individual rule */
+static void reset_rule_stats(gpointer key _U_,
+ gpointer value,
+ gpointer user_data _U_)
+{
+ Rule_t *rule = (Rule_t*)value;
+ rule->matches_seen = 0;
+}
+
+void reset_global_rule_stats(SnortConfig_t *snort_config)
+{
+ /* Reset global stats */
+ if (snort_config == NULL) {
+ return;
+ }
+ snort_config->stat_alerts_detected = 0;
+
+ /* Iterate over all rules, resetting the stats of each */
+ g_hash_table_foreach(snort_config->rules, reset_rule_stats, NULL);
+}
+
+
+/*************************************************************************************/
+/* Dealing with content fields and trying to find where it matches within the packet */
+/* Parse content strings to interpret binary and escaped characters. Do this */
+/* so we can look for in frame using memcmp(). */
+static unsigned char content_get_nibble_value(char c)
+{
+ static unsigned char values[256];
+ static gboolean values_set = FALSE;
+
+ if (!values_set) {
+ /* Set table once and for all */
+ unsigned char ch;
+ for (ch='a'; ch <= 'f'; ch++) {
+ values[ch] = 0xa + (ch-'a');
+ }
+ for (ch='A'; ch <= 'F'; ch++) {
+ values[ch] = 0xa + (ch-'A');
+ }
+ for (ch='0'; ch <= '9'; ch++) {
+ values[ch] = (ch-'0');
+ }
+ values_set = TRUE;
+ }
+
+ return values[(unsigned char)c];
+}
+
+/* Go through string, converting hex digits into guint8, and removing escape characters. */
+guint content_convert_to_binary(content_t *content)
+{
+ int output_idx = 0;
+ gboolean in_binary_mode = FALSE; /* Are we in a binary region of the string? */
+ gboolean have_one_nibble = FALSE; /* Do we have the first nibble of the pair needed to make a byte? */
+ unsigned char one_nibble = 0; /* Value of first nibble if we have it */
+ char c;
+ int n;
+ gboolean have_backslash = FALSE;
+ static gchar binary_str[1024];
+
+ /* Just return length if have previously translated in binary string. */
+ if (content->translated) {
+ return content->translated_length;
+ }
+
+ /* Walk over each character, work out what needs to be written into output */
+ for (n=0; content->str[n] != '\0'; n++) {
+ c = content->str[n];
+ if (c == '|') {
+ /* Flip binary mode */
+ in_binary_mode = !in_binary_mode;
+ continue;
+ }
+
+ if (!in_binary_mode) {
+ /* Not binary mode. Copying characters into output buffer, but watching out for escaped chars. */
+ if (!have_backslash) {
+ if (c == '\\') {
+ /* Just note that we have a backslash */
+ have_backslash = TRUE;
+ continue;
+ }
+ else {
+ /* Just copy the character straight into output. */
+ binary_str[output_idx++] = (unsigned char)c;
+ }
+ }
+ else {
+ /* Currently have a backslash. Reset flag. */
+ have_backslash = 0;
+ /* Just copy the character into output. Really, the only characters that should be escaped
+ are ';' and '\' and '"' */
+ binary_str[output_idx++] = (unsigned char)c;
+ }
+ }
+ else {
+ /* Binary mode. Handle pairs of hex digits and translate into guint8 */
+ if (c == ' ') {
+ /* Ignoring inside binary mode */
+ continue;
+ }
+ else {
+ unsigned char nibble = content_get_nibble_value(c);
+ if (!have_one_nibble) {
+ /* Store first nibble of a pair */
+ one_nibble = nibble;
+ have_one_nibble = TRUE;
+ }
+ else {
+ /* Combine both nibbles into a byte */
+ binary_str[output_idx++] = (one_nibble << 4) + nibble;
+ /* Reset flag - looking for new pair of nibbles */
+ have_one_nibble = FALSE;
+ }
+ }
+ }
+ }
+
+ /* Store result for next time. */
+ content->binary_str = (guchar*)g_malloc(output_idx+1);
+ memcpy(content->binary_str, binary_str, output_idx+1);
+ content->translated = TRUE;
+ content->translated_length = output_idx;
+
+ return output_idx;
+}
+
+/*
+ * Editor modelines - http://www.wireshark.org/tools/modelines.html
+ *
+ * Local variables:
+ * c-basic-offset: 4
+ * tab-width: 8
+ * indent-tabs-mode: nil
+ * End:
+ *
+ * vi: set shiftwidth=4 tabstop=8 expandtab:
+ * :indentSize=4:tabSize=8:noTabs=true:
+ */
diff --git a/epan/dissectors/snort-config.h b/epan/dissectors/snort-config.h
new file mode 100644
index 0000000000..ec0c23c761
--- /dev/null
+++ b/epan/dissectors/snort-config.h
@@ -0,0 +1,194 @@
+/* snort-config.h
+ *
+ * Copyright 2016, Martin Mathieson
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+
+#include <glib.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifndef SNORT_CONFIG_H
+#define SNORT_CONFIG_H
+
+/************************************************************************/
+/* Rule related data types */
+
+typedef enum content_type_t {
+ Content,
+ UriContent,
+ Pcre
+} content_type_t;
+
+/* Content (within an alert/rule) */
+typedef struct content_t {
+ /* Details as parsed from rule */
+ content_type_t content_type;
+
+ char *str;
+ gboolean negation; /* i.e. pattern must not appear */
+ gboolean nocase; /* when set, do case insensitive match */
+
+ gboolean offset_set; /* Where to start looking within packet. -65535 -> 65535 */
+ gint offset;
+
+ guint depth; /* How far to look into packet. Can't be 0 */
+
+ gboolean distance_set;
+ gint distance; /* Same as offset but relative to last match. -65535 -> 65535 */
+
+ guint within; /* Most bytes from end of previous match. Max 65535 */
+
+ gboolean fastpattern; /* Is most distinctive content in rule */
+
+ /* http preprocessor modifiers */
+ gboolean http_method;
+ gboolean http_client_body;
+ gboolean http_cookie;
+
+ /* Pattern converted into bytes for matching against packet */
+ guchar *binary_str;
+ gboolean translated;
+ guint translated_length;
+} content_t;
+
+/* This is to keep track of a variable referenced by a rule */
+typedef struct used_variable_t {
+ char *name;
+ char *value;
+} used_variable_t;
+
+/* The collection of variables referenced by a rule */
+typedef struct relevant_vars_t {
+ gboolean relevant_vars_set;
+
+ #define MAX_RULE_PORT_VARS 6
+ guint num_port_vars;
+ used_variable_t port_vars[MAX_RULE_PORT_VARS];
+
+ #define MAX_RULE_IP_VARS 6
+ guint num_ip_vars;
+ used_variable_t ip_vars[MAX_RULE_IP_VARS];
+
+} relevant_vars_t;
+
+
+/* This is purely the information parsed from the config */
+typedef struct Rule_t {
+
+ char *rule_string; /* The whole rule as read from the rule file */
+ char *file; /* Name of the rule file */
+ guint line_number; /* Line number of rule within rule file */
+
+ char *msg; /* Description of the rule */
+ char *classtype;
+ guint32 sid, rev;
+
+ char *protocol;
+
+ /* content strings to match on */
+ unsigned int number_contents;
+#define MAX_CONTENT_ENTRIES 30
+ content_t contents[MAX_CONTENT_ENTRIES];
+
+ /* Keep this pointer so can update attributes as parse modifier options */
+ content_t *last_added_content;
+
+ /* References describing the rule */
+ unsigned int number_references;
+#define MAX_REFERENCE_ENTRIES 20
+ char *references[MAX_REFERENCE_ENTRIES];
+
+ relevant_vars_t relevant_vars;
+
+ /* Statistics */
+ guint matches_seen;
+} Rule_t;
+
+
+
+/* Whole global snort config as learned by parsing config files */
+typedef struct SnortConfig_t
+{
+ /* Variables (var, ipvar, portvar) */
+ GHashTable *vars;
+ GHashTable *ipvars;
+ GHashTable *portvars;
+
+ char *rule_path;
+ gboolean rule_path_is_absolute;
+
+ /* (sid -> Rule_t*) table */
+ GHashTable *rules;
+ /* Reference (web .link) prefixes */
+ GHashTable *references_prefixes;
+
+ /* Statistics (that may be reset) */
+ guint stat_rules_files;
+ guint stat_rules;
+ guint stat_alerts_detected;
+
+} SnortConfig_t;
+
+
+/*************************************************************************************/
+/* API functions */
+void create_config(SnortConfig_t **snort_config, const char *snort_config_file);
+void delete_config(SnortConfig_t **snort_config);
+
+/* Look up rule by SID */
+Rule_t *get_rule(SnortConfig_t *snort_config, guint32 sid);
+void rule_set_alert(SnortConfig_t *snort_config, Rule_t *rule, guint *global_match_number, guint *rule_match_number);
+
+/* Debug only */
+void rule_print(Rule_t *rule);
+
+/* IP and port vars */
+void rule_set_relevant_vars(SnortConfig_t *snort_config, Rule_t *rule);
+
+/* Substitute prefix (from reference.config) into reference string */
+char *expand_reference(SnortConfig_t *snort_config, char *reference);
+
+/* Rule stats */
+void get_global_rule_stats(SnortConfig_t *snort_config, unsigned int sid,
+ unsigned int *number_rules_files, unsigned int *number_rules,
+ unsigned int *alerts_detected, unsigned int *this_rule_alerts_detected);
+void reset_global_rule_stats(SnortConfig_t *snort_config);
+
+/* Expanding a content field string to the expected binary bytes */
+guint content_convert_to_binary(content_t *content);
+
+#endif
+
+/*
+ * Editor modelines - http://www.wireshark.org/tools/modelines.html
+ *
+ * Local variables:
+ * c-basic-offset: 4
+ * tab-width: 8
+ * indent-tabs-mode: nil
+ * End:
+ *
+ * vi: set shiftwidth=4 tabstop=8 expandtab:
+ * :indentSize=4:tabSize=8:noTabs=true:
+ */