zeek/policy/notice.bro
Robin Sommer 2b6ad76bd5 Creating a branch release/1.5 with the current 1.5.3 release code.
This is so that people working from the current stable version can
still start using git.
2011-03-09 15:26:01 -08:00

410 lines
11 KiB
Text

# $Id: notice.bro 6756 2009-06-14 21:31:19Z vern $
const use_tagging = F &redef;
type Notice: enum {
NoticeNone, # placeholder
NoticeTally, # notice reporting count of how often a notice occurred
};
type NoticeAction: enum {
# Similar to WeirdAction in weird.bro.
NOTICE_UNKNOWN, # placeholder
NOTICE_IGNORE, NOTICE_FILE, NOTICE_ALARM_ALWAYS,
NOTICE_EMAIL, NOTICE_PAGE,
NOTICE_DROP, # drops the address via Drop::drop_address, and alarms
};
type notice_info: record {
note: Notice;
msg: string &default="";
sub: string &optional; # sub-message
conn: connection &optional; # connection associated with notice
iconn: icmp_conn &optional; # associated ICMP "connection"
id: conn_id &optional; # connection-ID, if we don't have a connection handy
src: addr &optional; # source address, if we don't have a connection
dst: addr &optional; # destination address
p: port &optional; # associated port, if we don't have a conn.
# The following are detailed attributes that are associated with some
# notices, but not all.
user: string &optional;
filename: string &optional;
method: string &optional;
URL: string &optional;
n: count &optional; # associated count, or perhaps status code
aux: table[string] of string &optional; # further NOTICE-specific data
# Automatically set attributes.
action: NoticeAction &default=NOTICE_UNKNOWN; # once action determined
src_peer: event_peer &optional; # source that raised this notice
tag: string &optional; # tag associated with this notice
dropped: bool &optional &default=F; # true if src successfully dropped
# If we asked the Time Machine to capture, the filename prefix.
captured: string &optional;
# If false, don't alarm independent of the determined notice action.
# If true, alarm dependening on notice action.
do_alarm: bool &default=T;
};
type notice_policy_item: record {
result: NoticeAction &default=NOTICE_FILE;
pred: function(n: notice_info): bool;
priority: count &default=1;
};
global notice_policy: set[notice_policy_item] = {
[$pred(n: notice_info) = { return T; },
$result = NOTICE_ALARM_ALWAYS,
$priority = 0],
} &redef;
global NOTICE: function(n: notice_info);
# Variables the control email notification.
const mail_script = "/bin/mail" &redef; # local system mail program
const mail_dest = "" &redef; # email address to send mail to
const mail_page_dest = "bro-page" &redef; # email address of pager
# Table that maps notices into a function that should be called
# to determine the action.
global notice_action_filters:
table[Notice] of
function(n: notice_info, a: NoticeAction): NoticeAction &redef;
# Each notice has a unique ID associated with it.
global notice_id = 0;
# This should have a high probability of being unique without
# generating overly long tags. This is redef'able in case you need
# determinism in tags (such as for regression testing).
global notice_tag_prefix =
fmt("%x-%x-",
double_to_count(time_to_double(current_time())) % 255,
getpid())
&redef;
# Likewise redef'able for regression testing.
global new_notice_tag =
function(): string
{
return fmt("%s%x", notice_tag_prefix, ++notice_id);
}
&redef;
# Function to add a unique NOTICE tag to a connection. This is done
# automatically whenever a NOTICE is raised, but sometimes one might need
# to call this function in advance of that to ensure that the tag appears
# in the connection summaries (i.e., when connection_state_remove() can be
# raised before the NOTICE is generated.)
global notice_tags: table[conn_id] of string;
function add_notice_tag(c: connection): string
{
if ( c$id in notice_tags )
return notice_tags[c$id];
local tag_id = new_notice_tag();
append_addl(c, fmt("@%s", tag_id));
notice_tags[c$id] = tag_id;
return tag_id;
}
event delete_notice_tags(c: connection)
{
delete notice_tags[c$id];
}
event connection_state_remove(c: connection)
{
# We do not delete the tag right here because there may be other
# connection_state_remove handlers invoked after us which
# want to generate a notice.
schedule 1 secs { delete_notice_tags(c) };
}
const notice_file = open_log_file("notice") &redef;
# This handler is useful for processing notices after the notice filters
# have been applied and yielded an NoticeAction.
#
# It's tempting to make the default handler do the logging and
# printing to notice_file, rather than NOTICE. I hesitate to do that,
# though, because it perhaps could slow down notification, because
# in the absence of event priorities, the event would have to wait
# behind any other already-queued events.
event notice_action(n: notice_info, action: NoticeAction)
{
}
# Do not generate notice_action events for these NOTICE types.
global suppress_notice_actions: set[Notice] &redef;
# Similar to notice_action but only generated if the notice also
# triggers an alarm.
event notice_alarm(n: notice_info, action: NoticeAction)
{
}
# Hack to suppress duplicate notice_actions for remote notices.
global suppress_notice_action = F;
function notice_info_tags(n: notice_info) : table[string] of string
{
local tags: table[string] of string;
local t = is_remote_event() ? current_time() : network_time();
tags["t"] = fmt("%.06f", t);
tags["no"] = fmt("%s", n$note);
tags["na"] = fmt("%s", n$action);
tags["sa"] = n?$src ? fmt("%s", n$src) : "";
tags["sp"] = n?$id && n$id$orig_h == n$src ? fmt("%s", n$id$orig_p) : "";
tags["da"] = n?$dst ? fmt("%s", n$dst) : "";
tags["dp"] = n?$id && n$id$resp_h == n$dst ? fmt("%s", n$id$resp_p) : "";
tags["p"] = n?$p ? fmt("%s", n$p) : "";
tags["user"] = n?$user ? n$user : "";
tags["file"] = n?$filename ? n$filename : "";
tags["method"] = n?$method ? n$method : "";
tags["url"] = n?$URL ? n$URL : "";
tags["num"] = n?$n ? fmt("%s", n$n) : "";
tags["msg"] = n?$msg ? n$msg : "";
tags["sub"] = n?$sub ? n$sub : "";
tags["captured"] = n?$captured ? n$captured : "";
tags["tag"] = fmt("@%s", n$tag);
tags["dropped"] = n$dropped ? "1" : "";
if ( n?$aux )
{
for ( a in n$aux )
tags[fmt("aux_%s", a)] = n$aux[a];
}
if ( is_remote_event() )
{
if ( n$src_peer$descr != "" )
tags["es"] = n$src_peer$descr;
else
tags["es"] = fmt("%s/%s", n$src_peer$host, n$src_peer$p);
}
else
tags["es"] = peer_description;
return tags;
}
function build_notice_info_string_untagged(n: notice_info) : string
{
# We add the fields in this order. Fields not listed won't be added.
local fields = vector("t", "no", "na", "es", "sa", "sp", "da", "dp",
"user", "file", "method", "url", "num", "msg", "sub", "tag");
local tags = notice_info_tags(n);
local cur_info = "";
for ( i in fields )
{
local val = tags[fields[i]];
val = string_escape(val, ":");
if ( cur_info == "" )
cur_info = val;
else
cur_info = fmt("%s:%s", cur_info, val);
}
return cur_info;
}
function build_notice_info_string_tagged(n: notice_info) : string
{
# We add the fields in this order. Fields not listed won't be added
# (except aux_*).
local fields = vector("t", "no", "na", "dropped", "es", "sa", "sp",
"da", "dp", "p", "user", "file", "method", "url",
"num", "msg", "sub", "captured", "tag");
local tags = notice_info_tags(n);
local cur_info = "";
for ( i in fields )
{
local val = tags[fields[i]];
local f = fields[i];
if ( val == "" )
next;
val = string_escape(val, "= ");
if ( cur_info == "" )
cur_info = fmt("%s=%s", f, val);
else
cur_info = fmt("%s %s=%s", cur_info, f, val);
}
for ( t in tags )
{
if ( t == /aux_.*/ )
{
if ( cur_info == "" )
cur_info = fmt("%s=%s", t, tags[t]);
else
cur_info = fmt("%s %s=%s", cur_info, t, tags[t]);
}
}
return cur_info;
}
global email_notice_to: function(n: notice_info, dest: string) &redef;
function email_notice_to(n: notice_info, dest: string)
{
if ( reading_traces() || dest == "" )
return;
# The contortions here ensure that the arguments to the mail
# script will not be confused. Re-evaluate if 'system' is reworked.
local mail_cmd =
fmt("echo \"%s\" | %s -s \"[Bro Alarm] %s\" %s",
str_shell_escape(n$msg), mail_script, n$note, dest);
system(mail_cmd);
}
function email_notice(n: notice_info, action: NoticeAction)
{
# Choose destination address based on action type.
local destination =
(action == NOTICE_EMAIL) ? mail_dest : mail_page_dest;
email_notice_to(n, destination);
}
# Executes a script with all of the notice fields put into the
# new process' environment as "BRO_ARG_<field>" variables.
function execute_with_notice(cmd: string, n: notice_info)
{
local tags = notice_info_tags(n);
system_env(cmd, tags);
}
# Can't load it at the beginning due to circular dependencies.
@load drop
function NOTICE(n: notice_info)
{
# Fill in some defaults.
if ( ! n?$id && n?$conn )
n$id = n$conn$id;
if ( ! n?$src && n?$id )
n$src = n$id$orig_h;
if ( ! n?$dst && n?$id )
n$dst = n$id$resp_h;
if ( ! n?$p && n?$id )
n$p = n$id$resp_p;
if ( ! n?$src && n?$iconn )
n$src = n$iconn$orig_h;
if ( ! n?$dst && n?$iconn )
n$dst = n$iconn$resp_h;
if ( ! n?$src_peer )
n$src_peer = get_event_peer();
if ( n?$conn )
n$tag = add_notice_tag(n$conn);
if ( ! n?$tag )
n$tag = new_notice_tag();
local action = match n using notice_policy;
local n_id = "";
if ( action != NOTICE_IGNORE && action != NOTICE_FILE &&
n$note in notice_action_filters )
action = notice_action_filters[n$note](n, action);
n$action = action;
if ( action == NOTICE_EMAIL || action == NOTICE_PAGE )
email_notice(n, action);
if ( action == NOTICE_DROP )
{
local drop = Drop::drop_address(n$src, "");
local addl = drop?$sub ? fmt(" %s", drop$sub) : "";
n$dropped = drop$note != Drop::AddressDropIgnored;
n$msg += fmt(" [%s%s]", drop$note, addl);
}
if ( action != NOTICE_IGNORE )
{
# Build the info here after we had a chance to set the
# $dropped field.
local info: string;
if ( use_tagging )
info = build_notice_info_string_tagged(n);
else
info = build_notice_info_string_untagged(n);
print notice_file, info;
if ( action != NOTICE_FILE && n$do_alarm )
{
if ( use_tagging )
{
alarm info;
event notice_alarm(n, action);
}
else
{
local descr = "";
if ( is_remote_event() )
{
if ( n$src_peer$descr != "" )
descr = fmt("<%s> ",
n$src_peer$descr);
else
descr = fmt("<%s:%s> ",
n$src_peer$host,
n$src_peer$p);
}
alarm fmt("%s %s%s", n$note, descr, n$msg);
event notice_alarm(n, action);
}
}
}
@ifdef ( IDMEF_support )
if ( n?$id )
generate_idmef(n$id$orig_h, n$id$orig_p,
n$id$resp_h, n$id$resp_p);
@endif
if ( ! suppress_notice_action && n$note !in suppress_notice_actions )
event notice_action(n, action);
}
@load notice-action-filters