# $Id: notice.bro 5898 2008-07-01 21:57:25Z vern $

const use_tagging = F &redef;

type Notice: enum {
	NoticeNone,	# placeholder
	NoticeTally,	# notice reporting count of how often a notice occurred
};

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

	# Automatically set attributes.
	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;
};

type NoticeAction: enum {
	# Similar to WeirdAction in weird.bro.
	NOTICE_IGNORE, NOTICE_FILE, NOTICE_ALARM_ALWAYS,
	NOTICE_EMAIL, NOTICE_PAGE,
	NOTICE_DROP, # drops the address via Drop::drop_address, and alarms
};

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 add_info(cur_info: string, tag: string, val: string): string
	{
	if ( use_tagging )
		{
		val = string_escape(val, "= ");

		if ( cur_info == "" )
			return fmt("%s=%s", tag, val);
		else
			return fmt("%s %s=%s", cur_info, tag, val);
		}
	else
		{
		val = string_escape(val, ":");

		if ( cur_info == "" )
			return val;
		else
			return fmt("%s:%s", cur_info, val);
		}
	}

function add_nil_info(cur_info: string): string
	{
	if ( use_tagging )
		return cur_info;
	else
		{
		if ( cur_info == "" )
			return ":";
		else
			return fmt("%s:", cur_info);
		}
	}

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);
	}

# 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();

	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);

	if ( action != NOTICE_IGNORE )
		{
		local info = "";

		info = add_info(info, "t", fmt("%.06f",
						is_remote_event() ?
							current_time() :
							network_time()));

		info = add_info(info, "no", fmt("%s", n$note));
		info = add_info(info, "na", fmt("%s", action));

		if ( is_remote_event() )
			{
			if ( n$src_peer$descr != "" )
				info = add_info(info, "es", n$src_peer$descr);
			else
				info = add_info(info, "es",
					fmt("%s/%s", n$src_peer$host,
						n$src_peer$p));
			}
		else
			{
			if ( peer_description != "" )
				info = add_info(info, "es", peer_description);
			else
				info = add_nil_info(info);
			}

		if ( n?$src )
			info = add_info(info, "sa", fmt("%s", n$src));
		else
			info = add_nil_info(info);
		if ( n?$id && n$id$orig_h == n$src )
			info = add_info(info, "sp", fmt("%s", n$id$orig_p));
		else
			info = add_nil_info(info);

		if ( n?$dst )
			info = add_info(info, "da", fmt("%s", n$dst));
		else
			info = add_nil_info(info);
		if ( n?$id && n$id$resp_h == n$dst )
			info = add_info(info, "dp", fmt("%s", n$id$resp_p));
		else
			info = add_nil_info(info);

		if ( n?$user )
			info = add_info(info, "user", n$user);
		else
			info = add_nil_info(info);
		if ( n?$filename )
			info = add_info(info, "file", n$filename);
		else
			info = add_nil_info(info);
		if ( n?$method )
			info = add_info(info, "method", n$method);
		else
			info = add_nil_info(info);
		if ( n?$URL )
			info = add_info(info, "url", n$URL);
		else
			info = add_nil_info(info);

		if ( n?$n )
			info = add_info(info, "num", fmt("%s", n$n));
		else
			info = add_nil_info(info);

		if ( n?$msg )
			info = add_info(info, "msg", n$msg);
		else
			info = add_nil_info(info);
		if ( n?$sub )
			info = add_info(info, "sub", n$sub);
		else
			info = add_nil_info(info);

		if ( use_tagging && n?$captured )
			info = add_info(info, "captured", n$captured);

		if ( n?$conn )
			n$tag = add_notice_tag(n$conn);

		if ( ! n?$tag )
			n$tag = new_notice_tag();

		info = add_info(info, "tag", fmt("@%s", n$tag));

		print notice_file, info;

		if ( action != NOTICE_FILE )
			{
			if ( action == NOTICE_EMAIL || action == NOTICE_PAGE )
				{
				email_notice(n, action);
				event notice_alarm(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 ( 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
