aboutsummaryrefslogtreecommitdiffstats
path: root/epan
diff options
context:
space:
mode:
authorMartin Mathieson <martin.r.mathieson@googlemail.com>2016-11-16 12:33:09 -0800
committerMartin Mathieson <martin.r.mathieson@googlemail.com>2016-11-23 23:15:24 +0000
commit28fb531cdd96ea1bbd48c6907a60f444ec2415a2 (patch)
tree9f672ef2cc7a4ad7e2371fb1466f9fa76233e943 /epan
parent24f7b93dc12577b5d9d72ff4f3fe786b65c517a1 (diff)
Initial commit of Snort post-dissector.
This dissector allows Snort to process all of the packets passed to Wireshark, and for the alerts to be shown in the protocol tree. It is also possible to set the source of alerts to be packet comments. Change-Id: I6e0a50d3418001cbac2d185639adda2553a40de8 Reviewed-on: https://code.wireshark.org/review/18848 Petri-Dish: Martin Mathieson <martin.r.mathieson@googlemail.com> Tested-by: Petri Dish Buildbot <buildbot-no-reply@wireshark.org> Reviewed-by: Alexis La Goutte <alexis.lagoutte@gmail.com> Reviewed-by: Martin Mathieson <martin.r.mathieson@googlemail.com>
Diffstat (limited to 'epan')
-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
5 files changed, 2685 insertions, 0 deletions
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:
+ */