# $Id: conn.bro,v 1.77 2003/06/16 04:12:14 vern Exp $

@load alert
@load hot
@load port-name
@load scan
@load netstats
    
redef enum Alert += {
	SensitiveConnection,	# connection marked "hot"
	TerminatingConnection,	# connection would have been terminated
};

const conn_closed = { TCP_CLOSED, TCP_RESET };

global have_FTP = F;	# if true, we've loaded ftp.bro
global have_SMTP = F;	# if true, we've loaded smtp.bro
global is_ftp_data_conn: function(c: connection): bool;

# Maps a given port on a given server's address to an RPC service.
# If we haven't loaded portmapper.bro, then it will be empty
# (and, ideally, queries to it would be optimized away ...).
global RPC_server_map: table[addr, port] of string;


function conn_state(c: connection, is_tcp: bool): string
	{
	local os = c$orig$state;
	local rs = c$resp$state;

	local o_inactive = os == TCP_INACTIVE || os == TCP_PARTIAL;
	local r_inactive = rs == TCP_INACTIVE || rs == TCP_PARTIAL;

	if ( is_tcp )
		{
		if ( rs == TCP_RESET )
			return (os == TCP_SYN_SENT || os == TCP_SYN_ACK_SENT) ?
				"REJ" : (o_inactive ? "RSTRH" : "RSTR");
		else if ( os == TCP_RESET )
			return r_inactive ? "RSTOS0" : "RSTO";
		else if ( rs == TCP_CLOSED && os == TCP_CLOSED )
			return "SF";
		else if ( os == TCP_CLOSED )
			return r_inactive ? "SH" : "S2";
		else if ( rs == TCP_CLOSED )
			return o_inactive ? "SHR" : "S3";
		else if ( os == TCP_SYN_SENT && rs == TCP_INACTIVE )
			return "S0";
		else if ( os == TCP_ESTABLISHED && rs == TCP_ESTABLISHED )
			return "S1";
		else
			return "OTH";
		}
	else
		{
		if ( os == UDP_ACTIVE )
			return rs == UDP_ACTIVE ? "SF" : "S0";
		else
			return rs == UDP_ACTIVE ? "SHR" : "OTH";
		}
	}

function conn_size(e: endpoint, is_tcp: bool): string
	{
	if ( e$size > 0 || (is_tcp && e$state == TCP_CLOSED) )
		return fmt("%d", e$size);
	else
		### should return 0 for TCP_RESET that went through TCP_CLOSED
		return "?";
	}

function service_name(c: connection): string
	{
	local p = c$id$resp_p;

	if ( p in port_names )
		return port_names[p];

	else if ( is_tcp_port(p) )
		{
		if ( p < 1024/tcp )
			return fmt("priv-%d", p);
		else
			return fmt("other-%d", p);
		}

	else
		{
		if ( p < 1024/udp )
			return fmt("upriv-%d", p);
		else
			return fmt("uother-%d", p);
		}
	}

const state_graphic = {
	["OTH"] = "?>?", ["REJ"] = "[",
	["RSTO"] = ">]", ["RSTOS0"] = "}]", ["RSTR"] = ">[", ["RSTRH"] = "<[",
	["S0"] = "}", ["S1"] = ">", ["S2"] = "}2", ["S3"] = "}3",
	["SF"] = ">", ["SH"] = ">h", ["SHR"] = "<h",
};

function 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 reverse_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 directed_id_string(id: conn_id, is_orig: bool): string
	{
	return is_orig ? id_string(id) : reverse_id_string(id);
	}

function full_id_string(c: connection): string
	{
	local id = c$id;
	local is_tcp = is_tcp_port(id$orig_p);
	local state = conn_state(c, is_tcp);
	local state_gr = state_graphic[state];
	local service = c$service == "" ? service_name(c) : c$service;

	if ( state == "S0" || state == "S1" || state == "REJ" )
		return fmt("%s %s %s/%s %s", id$orig_h, state_gr,
				id$resp_h, service, c$addl);

	else
		return fmt("%s %sb %s %s/%s %sb %.1fs %s",
			id$orig_h, conn_size(c$orig, is_tcp),
			state_gr, id$resp_h, service,
			conn_size(c$resp, is_tcp), c$duration, c$addl);
	}

# Indexed by the complete hot message.
global hot_conns_reported: set[string];

function log_hot_conn(c: connection): bool
	{
	local msg = full_id_string(c);
	if ( msg !in hot_conns_reported )
		{
		ALERT([$alert=SensitiveConnection, $conn=c,
			$msg=fmt("hot: %s", full_id_string(c))]);

		add hot_conns_reported[msg];
		}
	}

function terminate_connection(c: connection): bool
	{
	local id = c$id;
	local local_init = is_local_addr(id$orig_h);

	local term_cmd = fmt("./rst %s -n 32 -d 20 %s %d %d %s %d %d",
			local_init ? "-R" : "",
			id$orig_h, id$orig_p, get_orig_seq(id),
			id$resp_h, id$resp_p, get_resp_seq(id));

	if ( reading_live_traffic() )
		system(term_cmd);
	else
		ALERT([$alert=TerminatingConnection, $conn=c,
			$msg=term_cmd, $sub="first termination command"]);


	term_cmd = fmt("./rst %s -r 2 -n 4 -s 512 -d 20 %s %d %d %s %d %d",
			local_init ? "-R" : "",
			id$orig_h, id$orig_p, get_orig_seq(id),
			id$resp_h, id$resp_p, get_resp_seq(id));

	if ( reading_live_traffic() )
		system(term_cmd);
	else
		ALERT([$alert=TerminatingConnection, $conn=c,
			$msg=term_cmd, $sub="second termination command"]);

	ALERT([$alert=TerminatingConnection, $conn=c,
		$msg=fmt("terminating %s", full_id_string(c))]);
	}

function record_connection(c: connection, disposition: string): int
	{
	local id = c$id;
	local local_init = is_local_addr(id$orig_h);

	local local_addr = local_init ? id$orig_h : id$resp_h;
	local remote_addr = local_init ? id$resp_h : id$orig_h;

	local flags = local_init ? "L" : "";
	if ( remote_addr in neighbor_nets )
		flags = cat(flags, "U");	# using 'U' is historical

	if ( flags == "" )
		flags = "X";

	local is_tcp = is_tcp_port(id$orig_p);
	local duration: string;
	if ( is_tcp )
		{
		if ( c$orig$state in conn_closed || c$resp$state in conn_closed )
			duration = fmt("%.6g", c$duration);
		else
			duration = "?";
		}
	else
		duration = fmt("%.6g", c$duration);

	local addl = c$addl == "" ? "" : cat(" ", c$addl);
	local s = c$service == "" ? service_name(c) : c$service;
	local state = conn_state(c, is_tcp);

	print fmt("%.6f %s %s %s %s %s %s %s %s%s",
		c$start_time, duration, s,
		conn_size(c$orig, is_tcp), conn_size(c$resp, is_tcp),
		local_addr, remote_addr, state, flags, addl);

	if ( c$hot > 0 )
		log_hot_conn(c);
	}

function determine_service(c: connection): bool
 	{
	if ( have_FTP && is_ftp_data_conn(c) )
		c$service = port_names[20/tcp];

	else if ( [c$id$resp_h, c$id$resp_p] in RPC_server_map )
		# Alternatively, perhaps this should be stored in $addl
		# rather than $service, so the port number remains
		# visible .... ?
		c$service = RPC_server_map[c$id$resp_h, c$id$resp_p];

	else if ( c$orig$state == TCP_INACTIVE )
		{
		# We're seeing a half-established connection.  Use the
		# service of the originator if it's well-known and the
		# responder isn't.
		if ( c$id$resp_p !in port_names && c$id$orig_p in port_names )
			c$service = port_names[c$id$orig_p];
		}
 	}

event connection_established(c: connection)
 	{
	determine_service(c);

	local is_reverse_scan = (c$orig$state == TCP_INACTIVE);
	check_scan(c, T, is_reverse_scan);

	check_hot(c, CONN_ESTABLISHED);

	if ( c$hot > 0 )
		log_hot_conn(c);
 	}

event partial_connection(c: connection)
 	{
	check_scan(c, T, F);

	if ( c$orig$state == TCP_PARTIAL && c$resp$state == TCP_INACTIVE )
		# This appears to be a stealth scan.  Don't do hot-checking
		# as there wasn't an established connection.
		;
	else
		{
		check_hot(c, CONN_ESTABLISHED);
		check_hot(c, APPL_ESTABLISHED);	# assume it's been established
		}

	if ( c$hot > 0 )
		log_hot_conn(c);
 	}

event connection_attempt(c: connection)
 	{
	determine_service(c);
	check_scan(c, F, F);
	check_spoof(c);
	check_hot(c, CONN_ATTEMPTED);
	record_connection(c, "attempt");
 	}

event connection_partial_close(c: connection)
	{
	record_connection(c, "partial_close");
	}

event connection_finished(c: connection)
	{
	if ( c$orig$size == 0 || c$resp$size == 0 )
		# Hard to get excited about this - not worth logging again.
		c$hot = 0;
	else
		check_hot(c, CONN_FINISHED);

	record_connection(c, "finished");
	}

event connection_half_finished(c: connection)
	{
	# Half connections never were "established", so do scan-checking here.
	# (But don't do hot-checking, since the connection was never
	# established; it's likely the result of stealth-scanning.)
	determine_service(c);
	check_scan(c, F, F);
	check_hot(c, CONN_ATTEMPTED);
	record_connection(c, "half_finished");
	}

event connection_rejected(c: connection)
	{
	determine_service(c);
	check_scan(c, F, c$orig$state == TCP_RESET);
	check_hot(c, CONN_REJECTED);
	record_connection(c, "rejected");
	}

event connection_reset(c: connection)
	{
	if ( c$orig$state == TCP_INACTIVE || c$resp$state == TCP_INACTIVE )
		# We never heard from one side - that looks like a scan.
		check_scan(c, c$orig$size + c$resp$size > 0,
				c$orig$state == TCP_INACTIVE);

	check_hot(c, CONN_FINISHED);
	record_connection(c, "reset");
	}

event connection_pending(c: connection)
	{
	if ( c$orig$state in conn_closed &&
	     (c$resp$state == TCP_INACTIVE || c$resp$state == TCP_PARTIAL) )
		# This is a stray FIN or RST - don't bother reporting.
		return;

	if ( c$orig$state == TCP_RESET || c$resp$state == TCP_RESET )
		# We already reported this connection when the RST
		# occurred.
		return;

	check_hot(c, CONN_FINISHED);

	record_connection(c, "pending");
	}

function connection_gone(c: connection, gone_type: string)
	{
	if ( c$orig$size == 0 || c$resp$size == 0 )
		{
		if ( c$orig$state == TCP_RESET && c$resp$state == TCP_INACTIVE)
			# A bare RST, no other context.  Ignore it.
			return;

		# Hard to get excited about this - not worth logging again,
		# per connection_finished().
		c$hot = 0;
		}
	else
		check_hot(c, CONN_TIMEOUT);

	record_connection(c, gone_type);
	}

# event connection_timeout(c: connection)
# 	{
# 	if ( is_tcp_port(c$id$orig_p) )
# 		connection_gone(c, "timeout");
# 
# 	# else let connection_timeout in udp.bro handle this
# 	}

# event connection_reused(c: connection)
# 	{
# 	connection_gone(c, "reused");
# 	}

event connection_state_remove(c: connection)
	{
	local os = c$orig$state;
	local rs = c$resp$state;

	if ( os == TCP_ESTABLISHED && rs == TCP_ESTABLISHED )
		# It was still active, no summary generated.
		connection_gone(c, "remove");

	else if ( (os == TCP_CLOSED || rs == TCP_CLOSED) &&
		  (os == TCP_ESTABLISHED || rs == TCP_ESTABLISHED) )
		# One side has closed, the other hasn't - it's in state S2
		# or S3, hasn't been reported yet.
		connection_gone(c, "remove");
	}

