// $Id: Discard.cc,v 1.5 2002/10/11 02:04:40 leres Exp $
//
// Copyright (c) 2000, 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 "Net.h"
#include "Var.h"
#include "Discard.h"

Discarder::Discarder()
	{
	ip_hdr = internal_type("ip_hdr")->AsRecordType();
	tcp_hdr = internal_type("tcp_hdr")->AsRecordType();
	udp_hdr = internal_type("udp_hdr")->AsRecordType();
	icmp_hdr = internal_type("icmp_hdr")->AsRecordType();

	check_ip = internal_func("discarder_check_ip");
	check_tcp = internal_func("discarder_check_tcp");
	check_udp = internal_func("discarder_check_udp");
	check_icmp = internal_func("discarder_check_icmp");

	discarder_maxlen = opt_internal_int("discarder_maxlen");
	}

Discarder::~Discarder()
	{
	}

int Discarder::IsActive()
	{
	return check_ip || check_tcp || check_udp || check_icmp;
	}

int Discarder::NextPacket(const IP_Hdr* ip, int len, int caplen)
	{
	int discard_packet = 0;

	const struct ip* ip4 = ip->IP4_Hdr();

	if ( check_ip )
		{
		val_list* args = new val_list;
		args->append(BuildHeader(ip4));
		discard_packet = check_ip->Call(args)->AsBool();
		delete args;
		return discard_packet;
		}

	int proto = ip4->ip_p;
	if ( proto != IPPROTO_TCP && proto != IPPROTO_UDP &&
	     proto != IPPROTO_ICMP )
		// This is not a protocol we understand.
		return 0;

	// XXX shall we only check the first packet???
	uint32 frag_field = ntohs(ip4->ip_off);
	if ( (frag_field & 0x3fff) != 0 )
		// Never check any fragment.
		return 0;

	int ip_hdr_len = ip4->ip_hl * 4;
	len -= ip_hdr_len;	// remove IP header
	caplen -= ip_hdr_len;

	int is_tcp = (proto == IPPROTO_TCP);
	int is_udp = (proto == IPPROTO_UDP);
	int min_hdr_len = is_tcp ?
		sizeof(struct tcphdr) :
		(is_udp ? sizeof(struct udphdr) : sizeof(struct icmp));

	if ( len < min_hdr_len || caplen < min_hdr_len )
		// we don't have a complete protocol header
		return 0;

	// Where the data starts - if this is a protocol we know about,
	// this gets advanced past the transport header.
	const u_char* data = ((u_char*) ip4 + ip_hdr_len);

	if ( is_tcp )
		{
		if ( check_tcp )
			{
			const struct tcphdr* tp = (const struct tcphdr*) data;
			int th_len = tp->th_off * 4;

			val_list* args = new val_list;
			args->append(BuildHeader(ip4));
			args->append(BuildHeader(tp, len));
			args->append(BuildData(data, th_len, len, caplen));
			discard_packet = check_tcp->Call(args)->AsBool();
			delete args;
			}
		}

	else if ( is_udp )
		{
		if ( check_udp )
			{
			const struct udphdr* up = (const struct udphdr*) data;
			int uh_len = sizeof (struct udphdr);

			val_list* args = new val_list;
			args->append(BuildHeader(ip4));
			args->append(BuildHeader(up));
			args->append(BuildData(data, uh_len, len, caplen));
			discard_packet = check_udp->Call(args)->AsBool();
			delete args;
			}
		}

	else
		{
		if ( check_icmp )
			{
			const struct icmp* ih = (const struct icmp*) data;

			val_list* args = new val_list;
			args->append(BuildHeader(ip4));
			args->append(BuildHeader(ih));
			discard_packet = check_icmp->Call(args)->AsBool();
			delete args;
			}
		}

	return discard_packet;
	}

Val* Discarder::BuildHeader(const struct ip* ip)
	{
	RecordVal* hdr = new RecordVal(ip_hdr);

	hdr->Assign(0, new Val(ip->ip_hl * 4, TYPE_COUNT));
	hdr->Assign(1, new Val(ip->ip_tos, TYPE_COUNT));
	hdr->Assign(2, new Val(ntohs(ip->ip_len), TYPE_COUNT));
	hdr->Assign(3, new Val(ntohs(ip->ip_id), TYPE_COUNT));
	hdr->Assign(4, new Val(ip->ip_ttl, TYPE_COUNT));
	hdr->Assign(5, new Val(ip->ip_p, TYPE_COUNT));
	hdr->Assign(6, new AddrVal(ip->ip_src.s_addr));
	hdr->Assign(7, new AddrVal(ip->ip_dst.s_addr));

	return hdr;
	}

Val* Discarder::BuildHeader(const struct tcphdr* tp, int tcp_len)
	{
	RecordVal* hdr = new RecordVal(tcp_hdr);

	hdr->Assign(0, new PortVal(ntohs(tp->th_sport), 1));
	hdr->Assign(1, new PortVal(ntohs(tp->th_dport), 1));
	hdr->Assign(2, new Val((uint32)ntohl(tp->th_seq), TYPE_COUNT));
	hdr->Assign(3, new Val((uint32)ntohl(tp->th_ack), TYPE_COUNT));

	int tcp_hdr_len = tp->th_off * 4;

	hdr->Assign(4, new Val(tcp_hdr_len, TYPE_COUNT));
	hdr->Assign(5, new Val(tcp_len - tcp_hdr_len, TYPE_COUNT));

	hdr->Assign(6, new Val(tp->th_flags, TYPE_COUNT));
	hdr->Assign(7, new Val(ntohs(tp->th_win), TYPE_COUNT));

	return hdr;
	}

Val* Discarder::BuildHeader(const struct udphdr* up)
	{
	RecordVal* hdr = new RecordVal(udp_hdr);

	hdr->Assign(0, new PortVal(ntohs(up->uh_sport), 0));
	hdr->Assign(1, new PortVal(ntohs(up->uh_dport), 0));
	hdr->Assign(2, new Val(ntohs(up->uh_ulen), TYPE_COUNT));

	return hdr;
	}

Val* Discarder::BuildHeader(const struct icmp* icmp)
	{
	RecordVal* hdr = new RecordVal(icmp_hdr);

	hdr->Assign(0, new Val(icmp->icmp_type, TYPE_COUNT));

	return hdr;
	}

Val* Discarder::BuildData(const u_char* data, int hdrlen, int len, int caplen)
	{
	len -= hdrlen;
	caplen -= hdrlen;
	data += hdrlen;

	len = max(min(min(len, caplen), discarder_maxlen), 0);

	return new StringVal(new BroString(data, len, 1));
	}
