// $Id: Conn.cc,v 1.41 2003/09/02 04:16:06 vern Exp $
//
// Copyright (c) 1996, 1997, 1998, 1999, 2000, 2001, 2002
//      The Regents of the University of California.  All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that: (1) source code distributions
// retain the above copyright notice and this paragraph in its entirety, (2)
// distributions including binary code include the above copyright notice and
// this paragraph in its entirety in the documentation or other materials
// provided with the distribution, and (3) all advertising materials mentioning
// features or use of this software display the following acknowledgement:
// ``This product includes software developed by the University of California,
// Lawrence Berkeley Laboratory and its contributors.'' Neither the name of
// the University nor the names of its contributors may be used to endorse
// or promote products derived from this software without specific prior
// written permission.
// THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
// WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.

#include "config.h"

#include <ctype.h>

#include "Net.h"
#include "NetVar.h"
#include "Conn.h"
#include "Event.h"
#include "Sessions.h"
#include "Logger.h"

HashKey* ConnID::BuildConnKey() const
	{
	static struct Key {
		uint32 ip1[NUM_ADDR_WORDS];
		uint32 ip2[NUM_ADDR_WORDS];
		uint16 port1;
		uint16 port2;
	} key;

	// Lookup up connection based on canonical ordering, which is
	// the smaller of <src addr, src port> and <dst addr, dst port>
	// followed by the other.
	if ( addr_port_canon_lt(src_addr, src_port, dst_addr, dst_port) )
		{
		copy_addr(src_addr, key.ip1);
		copy_addr(dst_addr, key.ip2);
		key.port1 = src_port;
		key.port2 = dst_port;
		}
	else
		{
		copy_addr(dst_addr, key.ip1);
		copy_addr(src_addr, key.ip2);
		key.port1 = dst_port;
		key.port2 = src_port;
		}

	return new HashKey(&key, sizeof(key));
	}

void ConnectionTimer::Init(Connection* arg_conn, timer_func arg_timer, int arg_do_expire)
	{
	conn = arg_conn;
	timer = arg_timer;
	do_expire = arg_do_expire;
	Ref(conn);
	}

ConnectionTimer::~ConnectionTimer()
	{
	if ( active )
		{
		conn->RemoveTimer(this);
		Unref(conn);
		}
	}

void ConnectionTimer::Dispatch(double t, int is_expire)
	{
	if ( ! active )
		{
		conn = 0;
		return;
		}

	if ( is_expire && ! do_expire )
		return;

	(conn->*timer)(t);

	if ( ! active )
		// The timer invocation deactivated us.  Unref the
		// connection here, as we're not going to get another
		// chance.
		Unref(conn);
	}


Connection::Connection(NetSessions* s, HashKey* k, double t, const ConnID* id)
	{
	sessions = s;
	key = k;
	start_time = last_time = t;

	copy_addr(id->src_addr, orig_addr);
	copy_addr(id->dst_addr, resp_addr);
	orig_port = id->src_port;
	resp_port = id->dst_port;

	conn_val = 0;
	orig_endp = resp_endp = 0;
	login_conn = 0;

	is_active = 1;
	skip = 0;
	weird = 0;

	suppress_event = 0;

	record_contents = record_packets = 1;

	orig_match_state = resp_match_state = 0;

	timers_canceled = 0;
	inactivity_timeout = 0;
	installed_status_timer = 0;

	finished = 0;
	}

Connection::~Connection()
	{
	if ( ! finished )
		internal_error("Done() not called before destruction of Connection");

	if ( conn_val )
		{
		conn_val->SetOrigin(0);
		Unref(conn_val);
		}

	delete key;

	delete orig_match_state;
	delete resp_match_state;
	}

void Connection::Done()
	{
	finished = 1;
	}

void Connection::SetContentsFile(unsigned int /* direction */, BroFile* /* f */)
	{
	RunTime("connection type does not support writing to a contents file");
	}

void Connection::SetLifetime(double lifetime)
	{
	ADD_TIMER(&Connection::DeleteTimer, network_time + lifetime, 0);
	}

void Connection::DeleteTimer(double /* t */)
	{
	if ( is_active )
		Event(connection_timeout);

	sessions->Remove(this, 1);
	}

void Connection::InactivityTimer(double t)
	{
	// If the inactivity_timeout is zero, there has been an active
	// timeout once, but it's disabled now. We do nothing then.
	if ( inactivity_timeout )
		{
		if ( last_time + inactivity_timeout < t )
			{
			Event(connection_timeout);
			sessions->Remove(this, 1);
			}
		else
			ADD_TIMER(&Connection::InactivityTimer,
					last_time + inactivity_timeout, 0);
		}
	}

void Connection::SetInactivityTimeout(double timeout)
	{
	// Check if we are inactive for the given amount of time already.
	if ( timeout && last_time + timeout < network_time )
		{
		Event(connection_timeout);
		sessions->Remove(this, 0);
		}

	// If there isn't any inactivity timer running, we add one.
	// If there already is one, we don't modify it, but activate the
	// new timeout when it expires.
	if ( timeout && ! inactivity_timeout )
		ADD_TIMER(&Connection::InactivityTimer,
				last_time + timeout, 0);

	inactivity_timeout = timeout;
	}

void Connection::EnableStatusUpdateTimer()
	{
	if ( connection_status_update && connection_status_update_interval )
		{
		ADD_TIMER(&Connection::StatusUpdateTimer,
			network_time + connection_status_update_interval, 0);
		installed_status_timer = 1;
		}
	}

void Connection::StatusUpdateTimer(double t)
	{
	val_list* vl = new val_list;
	vl->append(BuildConnVal());
	ConnectionEvent(connection_status_update, vl);
	ADD_TIMER(&Connection::StatusUpdateTimer,
			network_time + connection_status_update_interval, 0);
	}

RecordVal* Connection::BuildConnVal()
	{
	if ( ! conn_val )
		{
		conn_val = new RecordVal(connection_type);

		int is_tcp = ConnType() == CONN_TCP;

		RecordVal* id_val = new RecordVal(conn_id);
		id_val->Assign(0, new AddrVal(orig_addr));
		id_val->Assign(1, new PortVal(ntohs(orig_port), is_tcp));
		id_val->Assign(2, new AddrVal(resp_addr));
		id_val->Assign(3, new PortVal(ntohs(resp_port), is_tcp));
		conn_val->Assign(0, id_val);

		orig_endp = new RecordVal(endpoint);
		orig_endp->Assign(0, new Val(0, TYPE_COUNT));
		orig_endp->Assign(1, new Val(0, TYPE_COUNT));
		conn_val->Assign(1, orig_endp);

		resp_endp = new RecordVal(endpoint);
		resp_endp->Assign(0, new Val(0, TYPE_COUNT));
		resp_endp->Assign(1, new Val(0, TYPE_COUNT));
		conn_val->Assign(2, resp_endp);

		// conn_val->Assign(3, new Val(start_time, TYPE_TIME));	// ###
		conn_val->Assign(5, new StringVal(""));	// service
		conn_val->Assign(6, new StringVal(""));	// addl
		conn_val->Assign(7, new Val(0, TYPE_COUNT));	// hot
		}

	UpdateEndpointVal(orig_endp, 1);
	UpdateEndpointVal(resp_endp, 0);

	conn_val->Assign(3, new Val(start_time, TYPE_TIME));	// ###
	conn_val->Assign(4, new Val(last_time - start_time, TYPE_INTERVAL));

	conn_val->SetOrigin(this);

	Ref(conn_val);

	return conn_val;
	}

// Returns true if the character at s separates a version number.
static inline bool is_version_sep(const char* s, const char* end)
	{
	return
		// foo-1.2.3
			(s < end - 1 && ispunct(s[0]) && isdigit(s[1])) ||
		// foo-v1.2.3
			(s < end - 2 && ispunct(s[0]) &&
			 tolower(s[1]) == 'v' && isdigit(s[2])) ||
		// foo 1.2.3
			isspace(s[0]);
	}

Val* Connection::BuildVersionVal(const char* s, int len)
	{
	Val* name = 0;
	Val* major = 0;
	Val* minor = 0;
	Val* minor2 = 0;
	Val* addl = 0;

	const char* last = s + len;
	const char* e = s;

	// This is all just a guess...

	// Eat non-alpha-numerical chars.
	for ( ; s < last && ! isalnum(*s); ++s )
		;

	// Leading characters are the program name.
	// (first character must not be a digit)
	if ( isalpha(*s) )
		{
		for ( e = s; e < last && ! is_version_sep(e, last); ++e )
			;

		if ( s != e )
			name = new StringVal(e - s, s);
		}

	// Find first number - that's the major version.
	for ( s = e; s < last && ! isdigit(*s); ++s )
		;
	for ( e = s; e < last && isdigit(*e); ++e )
		;

	if ( s != e )
		major = new Val(atoi(s), TYPE_INT);

	// Find second number seperated only by punctuation chars -
	// that's the minor version.
	for ( s = e; s < last && ispunct(*s); ++s )
		;
	for ( e = s; e < last && isdigit(*e); ++e )
		;

	if ( s != e )
		minor = new Val(atoi(s), TYPE_INT);

	// Find second number seperated only by punctuation chars; -
	// that's the minor version.
	for ( s = e; s < last && ispunct(*s); ++s )
		;
	for ( e = s; e < last && isdigit(*e); ++e )
		;

	if ( s != e )
		minor2 = new Val(atoi(s), TYPE_INT);

	// Anything after following punctuation and until next white space is
	// an additional version string.
	for ( s = e; s < last && ispunct(*s); ++s )
		;
	for ( e = s; e < last && ! isspace(*e); ++e )
		;

	if ( s != e )
		addl = new StringVal(e - s, s);

	// If we do not have a name yet, the next alphanumerical string is it.
	if ( ! name )
		{ // eat non-alpha-numerical characters
		for ( s = e; s < last && ! isalpha(*s); ++s )
			;

		// Get name.
		for ( e = s; e < last && (isalnum(*e) || *e == '_'); ++e )
			;

		if ( s != e )
			name = new StringVal(e - s, s);
		}

	// We need at least a name.
	if ( ! name )
		return 0;

	RecordVal* version = new RecordVal(software_version);
	version->Assign(0, major ? major : new Val(-1, TYPE_INT));
	version->Assign(1, minor ? minor : new Val(-1, TYPE_INT));
	version->Assign(2, minor2 ? minor2 : new Val(-1, TYPE_INT));
	version->Assign(3, addl ? addl : new StringVal(""));

	RecordVal* sw = new RecordVal(software);
	sw->Assign(0, name);
	sw->Assign(1, version);

	return sw;
	}

int Connection::VersionFoundEvent(const uint32* addr, const char* s, int len)
	{
	if ( ! software_version_found )
		return 1;

	Val* val = BuildVersionVal(s, len);
	if ( ! val )
		{
		val_list* vl = new val_list;
		vl->append(new AddrVal(addr));
		vl->append(new StringVal(len, s));
		ConnectionEvent(software_parse_error, vl);
		return 0;
		}

	val_list* vl = new val_list;
	vl->append(new AddrVal(addr));
	vl->append(val);
	vl->append(new StringVal(len, s));
	ConnectionEvent(software_version_found, vl);

	return 1;
	}

void Connection::Event(EventHandlerPtr f, const char* name)
	{
	if ( ! f )
		return;

	val_list* vl = new val_list;
	if ( name )
		vl->append(new StringVal(name));
	vl->append(BuildConnVal());

	ConnectionEvent(f, vl);
	}

void Connection::Event(EventHandlerPtr f, Val* v1, Val* v2)
	{
	if ( ! f )
		{
		Unref(v1);
		Unref(v2);
		return;
		}

	val_list* vl = new val_list;
	vl->append(BuildConnVal());
	vl->append(v1);

	if ( v2 )
		vl->append(v2);

	ConnectionEvent(f, vl);
	}

void Connection::ConnectionEvent(EventHandlerPtr f, val_list* vl)
	{
	if ( ! f )
		internal_error("bad event in ConnectionEvent");

	// "this" is passed as a cookie for the event
	mgr.QueueEvent(f, vl, this);
	}

void Connection::Weird(const char* name)
	{
	++weird;
	if ( conn_weird )
		Event(conn_weird, name);
	else
		fprintf(stderr, "%.06f weird: %s\n", network_time, name);
	}

void Connection::Weird(const char* name, const char* addl)
	{
	++weird;
	if ( conn_weird_addl )
		{
		val_list* vl = new val_list;

		vl->append(new StringVal(name));
		vl->append(BuildConnVal());
		vl->append(new StringVal(addl));

		ConnectionEvent(conn_weird_addl, vl);
		}

	else
		fprintf(stderr, "%.06f weird: %s (%s)\n", network_time, name, addl);
	}

void Connection::Weird(const char* name, int addl_len, const char* addl)
	{
	++weird;
	if ( conn_weird_addl )
		{
		val_list* vl = new val_list;

		vl->append(new StringVal(name));
		vl->append(BuildConnVal());
		vl->append(new StringVal(addl_len, addl));

		ConnectionEvent(conn_weird_addl, vl);
		}

	else
		{
		fprintf(stderr, "%.06f weird: %s (", network_time, name);
		for ( int i = 0; i < addl_len; ++i )
			fputc(addl[i], stderr);
		fprintf(stderr, ")\n");
		}
	}

void Connection::AddTimer(timer_func timer, double delta_t, int do_expire)
	{
	Timer* conn_timer = new ConnectionTimer(this, timer, delta_t, do_expire);
	timer_mgr->Add(conn_timer);
	timers.append(conn_timer);
	}

void Connection::RemoveTimer(Timer* t)
	{
	timers.remove(t);

	// Set an inactivity timer if there are no other timers (other than
	// connection_status_updated) pending (and we haven't canceled
	// all timers).
	if ( (unsigned int) timers.length() == installed_status_timer &&
	     ! timers_canceled )
		SetInactivityTimeout(default_inactivity_timeout);
	}


void Connection::CancelTimers(int callee_is_timer)
	{
	if ( callee_is_timer )
		// Protect ourselves from being completely Unref()'d.
		// The ConnectionTimer will Unref() us after dispatching.
		Ref(this);

	loop_over_list(timers, i)
		{
		timers[i]->Cancel();

		// Unref ourselves for this timer, since it won't do it
		// for us now.
		Unref(this);
		}

	timers_canceled = 1;
	timers.clear();
	}

void Connection::InitEndpointMatcher(const IP_Hdr* ip, int caplen, int from_orig)
	{
	if ( ! rule_matcher )
		return;

	if ( from_orig )
		{
		if ( orig_match_state )
			{
			rule_matcher->FinishEndpoint(orig_match_state);
			delete orig_match_state;
			}

		orig_match_state = rule_matcher->InitEndpoint(this, ip, caplen, resp_match_state);
		}

	else
		{
		if ( resp_match_state )
			{
			rule_matcher->FinishEndpoint( resp_match_state );
			delete resp_match_state;
			}

		resp_match_state = rule_matcher->InitEndpoint(this, ip, caplen, orig_match_state);
		}
	}

void Connection::FinishEndpointMatcher()
	{
	if ( ! rule_matcher )
		return;

	if ( orig_match_state )
		{
		rule_matcher->FinishEndpoint(orig_match_state);
		delete orig_match_state;
		}

	if ( resp_match_state )
		{
		rule_matcher->FinishEndpoint(resp_match_state);
		delete resp_match_state;
		}

	orig_match_state = resp_match_state = 0;
	}
