# $Id: weird.bro,v 1.32 2003/06/13 06:17:36 vern Exp $

@load alert
@load port-name

redef enum Alert += {
	WeirdActivity,		# generic unusual, log-worthy activity
	RetransmissionInconsistency,	# possible evasion; usually just chud
	AckAboveHole,		# could mean packet drop; could also be chud
	ContentGap,		# data has sequence hole; perhaps due to filtering
};

global weird_file = open_log_file("weird") &redef;

type WeirdAction: enum {
	WEIRD_UNSPECIFIED, WEIRD_IGNORE, WEIRD_FILE,
	WEIRD_LOG_ALWAYS, WEIRD_LOG_PER_CONN,
	WEIRD_LOG_PER_ORIG, WEIRD_LOG_ONCE,
};

# Which of the above actions lead to logging.  For internal use.
const log_actions = {
	WEIRD_LOG_ALWAYS, WEIRD_LOG_PER_CONN,
	WEIRD_LOG_PER_ORIG, WEIRD_LOG_ONCE,
};

# For WEIRD_LOG_PER_CONN.
global did_log_conn: set[addr, port, addr, port, string];

# For WEIRD_LOG_PER_ORIG.
global did_log_orig: set[addr, string];

# For WEIRD_LOG_ONCE.
global did_weird_log: set[string];

const weird_action: table[string] of WeirdAction = {
	# tcp_weird
	["active_connection_reuse"]	=	WEIRD_FILE,
	["bad_HTTP_reply"]	=	WEIRD_FILE,
	["bad_HTTP_version"]	=	WEIRD_FILE,
	["bad_ICMP_checksum"]	=	WEIRD_FILE,
	["bad_ident_reply"]	=	WEIRD_FILE,
	["bad_ident_request"]	=	WEIRD_FILE,
	["bad_rlogin_prolog"]	=	WEIRD_FILE,
	["bad_RPC"]	=	WEIRD_FILE,
	["bad_RPC_program"]	=	WEIRD_FILE,
	["bad_SYN_ack"]	=	WEIRD_FILE,
	["bad_TCP_checksum"]	=	WEIRD_FILE,
	["bad_UDP_checksum"]	=	WEIRD_FILE,
	["baroque_SYN"]	=	WEIRD_FILE,
	["connection_originator_SYN_ack"]	=	WEIRD_FILE,
	["crud_trailing_HTTP_request"]	=	WEIRD_FILE,
	["data_after_reset"]	=	WEIRD_FILE,
	["data_before_established"]	=	WEIRD_FILE,
	["excessive_RPC_len"]	=	WEIRD_FILE,
	["excess_RPC"]	=	WEIRD_FILE,
	["FIN_advanced_last_seq"]	=	WEIRD_FILE,
	["FIN_after_reset"]	=	WEIRD_FILE,
	["FIN_storm"]	=	WEIRD_FILE,
	["HTTP_bad_chunk_size"]	=	WEIRD_FILE,
	["HTTP_overlapping_messages"]	=	WEIRD_FILE,
	["HTTP_unknown_method"]	=	WEIRD_FILE,
	["HTTP_version_mismatch"]	=	WEIRD_FILE,
	["ident_request_addendum"]	=	WEIRD_FILE,
	["inappropriate_FIN"]	=	WEIRD_FILE,
	["line_terminated_with_single_CR"]	=	WEIRD_FILE,
	["line_terminated_with_single_LF"]	=	WEIRD_FILE,
	["malformed_ssh_identification"]	= WEIRD_FILE,
	["matching_undelivered_data"]	= WEIRD_FILE,
	["multiple_HTTP_request_elements"]	=	WEIRD_FILE,
	["multiple_RPCs"]	=	WEIRD_FILE,
	["NUL_in_line"]	=	WEIRD_FILE,
	["originator_RPC_reply"]	=	WEIRD_FILE,
	["partial_finger_request"]	=	WEIRD_FILE,
	["partial_ftp_request"]	=	WEIRD_FILE,
	["partial_ident_request"]	=	WEIRD_FILE,
	["partial_portmapper_request"]	=	WEIRD_FILE,
	["partial_RPC"]	=	WEIRD_FILE,
	["pending_data_when_closed"]	=	WEIRD_FILE,
	["possible_split_routing"]	=	WEIRD_FILE,
	["premature_connection_reuse"]	=	WEIRD_FILE,
	["repeated_SYN_reply_wo_ack"]	=	WEIRD_FILE,
	["repeated_SYN_with_ack"]	=	WEIRD_FILE,
	["responder_RPC_call"]	=	WEIRD_FILE,
	["rlogin_text_after_rejected"]	=	WEIRD_FILE,
	["RPC_rexmit_inconsistency"]	=	WEIRD_FILE,
	["RST_storm"]	=	WEIRD_FILE,
	["RST_with_data"]	=	WEIRD_FILE,	# PC's do this
	["simultaneous_open"]	=	WEIRD_FILE,
	["spontaneous_FIN"]	=	WEIRD_FILE,
	["spontaneous_RST"]	=	WEIRD_FILE,
	["SYN_after_close"]	=	WEIRD_FILE,
	["SYN_after_partial"]	=	WEIRD_FILE,
	["SYN_after_reset"]	=	WEIRD_FILE,
	["SYN_inside_connection"]	=	WEIRD_FILE,
	["SYN_seq_jump"]	=	WEIRD_FILE,
	["SYN_with_data"]	=	WEIRD_FILE,
	["TCP_christmas"]	=	WEIRD_FILE,
	["truncated_NTP"]	=	WEIRD_FILE,
	["UDP_datagram_length_mismatch"]	=	WEIRD_FILE,
	["unexpected_multiple_HTTP_requests"]	=	WEIRD_FILE,
	["unmatched_HTTP_reply"]	=	WEIRD_FILE,
	["unpaired_RPC_response"]	=	WEIRD_FILE,
	["unsolicited_SYN_response"]	=	WEIRD_FILE,
	["window_recision"]	= WEIRD_FILE,
	["double_%_in_URI"]	= WEIRD_FILE,
	["illegal_%_at_end_of_URI"]	= WEIRD_FILE,
	["unescaped_%_in_URI"]	= WEIRD_FILE,
	["unescaped_special_URI_char"]	= WEIRD_FILE,

	["DNS_RR_length_mismatch"]	= WEIRD_FILE,
	["DNS_label_too_long"]	= WEIRD_FILE,
	["DNS_name_too_long"]	= WEIRD_FILE,
	["DNS_truncated_label"]	= WEIRD_FILE,
	["UDP_zone_transfer"]	= WEIRD_LOG_ONCE,
	["bad_DNS_RR_length"]	= WEIRD_FILE,
	["forward_DNS_compression_offset"]	= WEIRD_FILE,
	["truncated_DNS"]	= WEIRD_FILE,
	["truncated_DNS_RR"]	= WEIRD_FILE,
	["unknown_DNS_RR_type"]	= WEIRD_FILE,

	["deficit_netbios_hdr_len"]	= WEIRD_FILE,
	["excess_netbios_hdr_len"]	= WEIRD_FILE,
	["netbios_client_session_reply"]	= WEIRD_FILE,
	["netbios_raw_session_msg"]	= WEIRD_FILE,
	["netbios_server_session_request"]	= WEIRD_FILE,
	["unknown_netbios_type"]	= WEIRD_FILE,

	# flow_weird
	["excessively_large_fragment"]	=	WEIRD_LOG_ALWAYS,
	["excessively_small_fragment"]	=	WEIRD_LOG_ALWAYS,
	["fragment_inconsistency"]	=	WEIRD_LOG_ALWAYS,
	["fragment_overlap"]	=	WEIRD_LOG_ALWAYS,
	["fragment_protocol_inconsistency"]	=	WEIRD_FILE,
	["fragment_size_inconsistency"]	=	WEIRD_FILE,
	["fragment_with_DF"]	=	WEIRD_FILE,	# these do indeed happen!
	["incompletely_captured_fragment"]	=	WEIRD_LOG_ALWAYS,

	# net_weird
	["bad_IP_checksum"]	=	WEIRD_FILE,
	["bad_TCP_header_len"]	=	WEIRD_FILE,
	["internally_truncated_header"]	=	WEIRD_LOG_ALWAYS,
	["truncated_IP"]	=	WEIRD_FILE,
	["truncated_header"]	=	WEIRD_FILE,

	# generated by policy script
	["bad_pm_port"]	=		WEIRD_FILE,
	["Land_attack"]	=		WEIRD_LOG_ALWAYS,
} &redef;

# table that maps weird types into a function that should be called
# to determine the action.
global weird_action_filters:
	table[string] of function(c: connection): WeirdAction &redef;

# id/msg pairs that should be ignored (because the problem has already
# been reported).
global weird_ignore: set[string, string];

const weird_ignore_host: set[addr, string] &redef;

# But don't ignore these (for the weird file), it's handy keeping
# track of clustered checksum errors.
const weird_do_not_ignore_repeats = {
	"bad_IP_checksum", "bad_TCP_checksum", "bad_UDP_checksum",
	"bad_ICMP_checksum",
} &redef;


function weird_id_string(id: conn_id): string
	{
	return fmt("%s > %s",
		endpoint_id(id$orig_h, id$orig_p),
		endpoint_id(id$resp_h, id$resp_p));
	}

function report_weird(t: time, name: string, id: string, action: WeirdAction, no_log: bool): bool
	{
	if ( action == WEIRD_IGNORE || [id, name] in weird_ignore )
		return T;

	local msg: string;

	if ( action == WEIRD_UNSPECIFIED )
		{
		if ( name in weird_action )
			{
			action = weird_action[name];
			if ( action == WEIRD_IGNORE )
				return T;

			msg = fmt("%s%s", id, name);
			}
		else
			{
			action = WEIRD_LOG_ALWAYS;
			msg = fmt("** %s%s", id, name);
			}
		}
	else
		msg = fmt("%s%s", id, name);

	if ( action in log_actions && ! no_log )
		ALERT([$alert=WeirdActivity, $msg=msg]);

	else if ( id != "" && name !in weird_do_not_ignore_repeats )
		add weird_ignore[id, name];

	print weird_file, fmt("%.6f %s", t, msg);
	}

function report_weird_conn(t: time, name: string, id: string, c: connection): bool
	{
	if ( [c$id$orig_h, name] in weird_ignore_host ||
	     [c$id$resp_h, name] in weird_ignore_host )
		return T;

	local no_log = F;
	local action = WEIRD_UNSPECIFIED;

	if ( name in weird_action )
		{
		if ( name in weird_action_filters )
			action = weird_action_filters[name](c);

		if ( action == WEIRD_UNSPECIFIED )
			action = weird_action[name];

		local cid = c$id;

		if ( action == WEIRD_LOG_PER_CONN )
			{
			if ( [cid$orig_h, cid$orig_p, cid$resp_h, cid$resp_p, name] in did_log_conn )
				no_log = T;
			else
				add did_log_conn[cid$orig_h, cid$orig_p, cid$resp_h, cid$resp_p, name];
			}

		else if ( action == WEIRD_LOG_PER_ORIG )
			{
			if ( [c$id$orig_h, name] in did_log_orig )
				no_log = T;
			else
				add did_log_orig[c$id$orig_h, name];
			}

		else if ( action == WEIRD_LOG_ONCE )
			{
			if ( name in did_weird_log )
				no_log = T;
			else
				add did_weird_log[name];
			}
		}

	report_weird(t, name, id, action, no_log);
	}

function report_weird_orig(t: time, name: string, id: string, orig: addr): bool
	{
	local no_log = F;
	local action = WEIRD_UNSPECIFIED;

	if ( name in weird_action )
		{
		action = weird_action[name];
		if ( action == WEIRD_LOG_PER_ORIG )
			{
			if ( [orig, name] in did_log_orig )
				no_log = T;
			else
				add did_log_orig[orig, name];
			}
		}

	report_weird(t, name, id, action, no_log);
	}

event conn_weird(name: string, c: connection)
	{
	report_weird_conn(network_time(), name, fmt("%s: ", weird_id_string(c$id)), c);
	}

event conn_weird_addl(name: string, c: connection, addl: string)
	{
	report_weird_conn(network_time(), name, fmt("%s: (%s) ", weird_id_string(c$id), addl), c);
	}

event flow_weird(name: string, src: addr, dst: addr)
	{
	report_weird_orig(network_time(), name, fmt("%s -> %s: ", src, dst), src);
	}

event net_weird(name: string)
	{
	report_weird(network_time(), name, "", WEIRD_UNSPECIFIED, F);
	}

global did_inconsistency_msg: set[conn_id];

event rexmit_inconsistency(c: connection, t1: string, t2: string)
	{
	if ( c$id !in did_inconsistency_msg )
		{
		ALERT([$alert=RetransmissionInconsistency, $conn=c,
			$msg=fmt("%s rexmit inconsistency (%s) (%s)",
				weird_id_string(c$id), t1, t2)]);
		add did_inconsistency_msg[c$id];
		}
	}

event ack_above_hole(c: connection)
	{
	ALERT([$alert=AckAboveHole, $conn=c,
		$msg=fmt("%s ack above a hole", weird_id_string(c$id))]);
	}

event content_gap(c: connection, is_orig: bool, seq: count, length: count)
	{
	ALERT([$alert=ContentGap, $conn=c,
		$msg=fmt("%s content gap (%s %d/%d)",
			weird_id_string(c$id), is_orig ? ">" : "<", seq, length)]);
	}
