#!/usr/bin/perl

# BasicSorters.pm, distributed as part of Snortsnarf v052301.1
# Author: James Hoagland, Silicon Defense (hoagland@SiliconDefense.com)
# copyright (c) 2001 by Silicon Defense (http://www.silicondefense.com/)
# Released under GNU General Public License, see the COPYING file included
# with the distribution or http://www.silicondefense.com/software/snortsnarf/
# for details.

# this file contains a set of basic implementations of the SnortSnarf
# Filter API

# Please send complaints, kudos, and especially improvements and bugfixes to
# hoagland@SiliconDefense.com.  As described in GNU General Public License, no
# warranty is expressed for this program.


use Filtering;
use KnownEquiv;

##########################################################

package TrueFilter;

# the all-inclusive filter that rejects nothing

@ISA= (qw(ScalarKE));

sub new {
	my $true= 'True';
	return bless \$true,$_[0]; # no need for instance storage
}

sub as_str {
	return '1';
}

sub test {
	return 1;
}

##########################################################

package FalseFilter;

# the all-hating filter that rejects everything

@ISA= (qw(ScalarKE));

sub new {
	my $false= 'False';
	return bless \$false,$_[0]; # no need for instance storage
}

sub as_str {
	return '0';
}

sub test {
	return 0;
}

##########################################################

package NotFilter;

# a filter class that returns opposite of another alert filter

@ISA= (qw(ScalarKE));

sub new {
	my($filter);
	my($class)= shift;
	if (ref($_[0])) {
		$filter= $_[0];
	} else {
		my($strspec)= shift;
		$filter= Filter::from_str($strspec);
	}
	return bless \$filter,$class; 
}

sub as_str {
	return Filter::as_str(${$_[0]});
}

sub test {
	my($self,$alert)= @_;
	return !${$self}->test($alert);
}

##########################################################

package AndFilter;

# a filter class that returns true iff all the enclosed filters evalute to true

@ISA= (qw(ArrayKE));

sub new {
	my($class)= shift;
	my(@filters);
	if (@_ > 1 || @_ == 1 && ref($_[0])) {
		@filters= @_;
	} else {
		my($strspec)= shift;
		my @filterstrs= Filter::unjoin_strs($strspec);
		@filters= map(Filter::from_str($_),@filterstrs);
	}
	return bless \@filters,$class; 
}

sub as_str {
	my(@filterstrs)= map(Filter::as_str($_),@{$_[0]});
	return Filter::join_strs(@filterstrs);
}

sub test {
	my($self,$alert)= @_;
	my $filter;
	foreach $filter (@{$self}) {
		return 0 unless $filter->test($alert);
	}
	return 1;
}

##########################################################

package OrFilter;

# a filter class that returns false iff all the enclosed filters evalute to false

@ISA= (qw(ArrayKE));

sub new {
	my(@filters);
	my($class)= shift;
	if (@_ > 1 || @_ == 1 && ref($_[0])) {
		@filters= @_;
	} else {
		my($strspec)= shift;
		my @filterstrs= Filter::unjoin_strs($strspec);
		@filters= map(Filter::from_str($_),@filterstrs);
	}
	return bless \@filters,$class; 
}

sub as_str {
	my(@filterstrs)= map(Filter::as_str($_),@{$_[0]});
	return Filter::join_strs(@filterstrs);
}

sub test {
	my($self,$alert)= @_;
	my $filter;
	foreach $filter (@{$self}) {
		return 1 if $filter->test($alert);
	}
	return 0;
}

##########################################################

package AlertFieldDef;

# a filter class that matches iff a certain alert field is defined (or undefined)

@ISA= (qw(ArrayKE));

sub new {
	my($class)= shift;
	my($strspec)= shift;
	my $tense = ($strspec =~ s/^!//) ? 0 : 1;
	$strspec =~ s/\s+$//;
	return bless [$tense,$strspec],$class; 
}

sub as_str {
	my($tense,$field)= @{$_[0]};
	return ($tense?'':'!').$field;
}

sub test {
	my($self,$alert)= @_;
	my $meth=$self->[1];
	my $def= defined($alert->$meth());
	return $self->[0] ? $def : !$def;
}

##########################################################

package AlertFieldEq;

# a filter class that matches iff a certain alert field has a certain value (stringwise comparison)

@ISA= (qw(ArrayKE));

sub new {
	my($class)= shift;
	my($strspec)= shift;
	my($field,$val)= split(/\s*=\s*/,$strspec,2);
	$val =~ s/\s+$//;
	return bless [$field,$val],$class; 
}

sub as_str {
	my($field,$val)= @{$_[0]};
	return "$field=$val";
}

sub test {
	my($self,$alert)= @_;
	my $meth=$self->[0];
	return $alert->$meth() eq $self->[1];
}


##########################################################

package AlertFieldCompare;

# a filter class that matches if a certain string comparison operation with given alert field on the right and a certain string on the left evaluates to true

@ISA= (qw(ArrayKE));

sub new {
	my($class)= shift;
	my($strspec)= shift;
	my($field,$op,$val)= split(/\s*$Filtering::strcmpops_regex\s*/,$strspec,2);
	$val =~ s/\s+$//;
	return bless [$op,$field,$val],$class; 
}

sub as_str {
	my($op,$field,$val)= @{$_[0]};
	return "$field $op $val";
}

sub test {
	my($self,$alert)= @_;
	my($op,$field,$val)= @{$self};
	return &Filtering::strcmpops($op,$alert->$field(),$val);
}


##########################################################

package AlertFieldNumCompare;

# a filter class that matches if a certain numeric comparison operation with given alert field on the right and a certain number on the left evaluates to true

@ISA= (qw(ArrayKE));

sub new {
	my($class)= shift;
	my($strspec)= shift;
	my($field,$op,$val)= split(/\s*$Filtering::numcmpops_regex\s*/,$strspec,2);
	$val =~ s/\s+$//;
	return bless [$op,$field,$val],$class; 
}

sub as_str {
	my($op,$field,$val)= @{$_[0]};
	return "$field $op $val";
}

sub test {
	my($self,$alert)= @_;
	my($op,$field,$val)= @{$self};
	return &Filtering::numcmpops($op,$alert->$field(),$val);
}

##########################################################

package AnyPacketFilter;

# a filter class that matches iff the provided packeet filter matches any packet in the given alert

@ISA= (qw(ScalarKE));

sub new {
	my($class)= shift;
	my($pktfilter)= shift;
	unless (ref($pktfilter)) {
		$pktfilter= Filter::from_str($pktfilter);
	}
	return bless \$pktfilter,$class; 
}

sub as_str {
	return Filter::as_str(${$_[0]});
}

sub test {
	my($self,$alert)= @_;
	foreach ($alert->packets()) {
		return 1 if ${$self}->test($_);
	}
	return 0;
}

##########################################################

package AllPacketFilter;

# a filter class that matches iff the provided packeet filter matches all packets in the given alert

@ISA= (qw(ScalarKE));

sub new {
	my($class)= shift;
	my($pktfilter)= shift;
	unless (ref($pktfilter)) {
		$pktfilter= Filter::from_str($pktfilter);
	}
	return bless \$pktfilter,$class; 
}

sub as_str {
	return Filter::as_str(${$_[0]});
}

sub test {
	my($self,$alert)= @_;
	foreach ($alert->packets()) {
		return 0 if !${$self}->test($_);
	}
	return 1;
}

##########################################################

package FieldDefPktFilter;

# a packet filter class that matches if a certain string comparison operation with given packet field on the right and a certain string on the left evaluates to true

@ISA= (qw(ArrayKE));

sub new {
	my($class)= shift;
	my($strspec)= shift;
	my $tense = ($strspec =~ s/^!//) ? 0 : 1;
	$strspec =~ s/\s+$//;
	return bless [$tense,$strspec],$class; 
}

sub as_str {
	my($tense,$field)= @{$_[0]};
	return ($tense?'':'!').$field;
}

sub test {
	my($self,$packet)= @_;
	my $meth=$self->[1];
	my $def= defined($packet->$meth());
	return $self->[0] ? $def : !$def;
}

##########################################################

package FieldComparePktFilter;

# a packet filter class that matches if a certain string comparison operation with given packet field on the right and a certain string on the left evaluates to true

@ISA= (qw(ArrayKE));

sub new {
	my($class)= shift;
	my($strspec)= shift;
	my($field,$op,$val)= split(/\s*$Filtering::strcmpops_regex\s*/,$strspec,2);
	$val =~ s/\s+$//;
	return bless [$op,$field,$val],$class; 
}

sub as_str {
	my($op,$field,$val)= @{$_[0]};
	return "$field $op $val";
}

sub test {
	my($self,$packet)= @_;
	my($op,$field,$val)= @{$self};
	return &Filtering::strcmpops($op,$packet->$field(),$val);
}


##########################################################

package FieldNumComparePktFilter;

# a packet filter class that matches if a certain numeric comparison operation with given packet field on the right and a certain number on the left evaluates to true

@ISA= (qw(ArrayKE));

sub new {
	my($class)= shift;
	my($strspec)= shift;
	my($field,$op,$val)= split(/\s*$Filtering::numcmpops_regex\s*/,$strspec,2);
	$val =~ s/\s+$//;
	return bless [$op,$field,$val],$class; 
}

sub as_str {
	my($op,$field,$val)= @{$_[0]};
	return "$field $op $val";
}

sub test {
	my($self,$packet)= @_;
	my($op,$field,$val)= @{$self};
	return &Filtering::numcmpops($op,$packet->$field(),$val);
}

1;
