Initial import of svn+ssh:://svn.icir.org/bro/trunk/bro as of r7088

This commit is contained in:
Robin Sommer 2010-09-27 20:42:30 -07:00
commit 61757ac78b
1383 changed files with 380824 additions and 0 deletions

View file

@ -0,0 +1,120 @@
package Bro::Config;
use strict;
use Config::General;
require Exporter;
use vars qw( $VERSION
$DEBUG
@ISA
@EXPORT_OK
%DEFAULTS
$DEFAULT_CONFIG_FILE
$BRO_CONFIG );
# $Id: Config.pm 987 2005-01-08 01:04:43Z rwinslow $
$VERSION = 1.20;
$DEBUG = 0;
@ISA = ( 'Exporter' );
@EXPORT_OK = qw( $BRO_CONFIG );
%DEFAULTS = ( BROHOME => '/usr/local/bro',
BRO_POLICY_SUFFIX => '.bro',
BRO_SIG_SUFFIX => '.sig',
META_DATA_PREFIX => '.',
);
$DEFAULTS{CONFIG_FILE} = $DEFAULTS{BROHOME} . '/etc/bro.cfg';
sub parse
{
my $sub_name = 'parse';
my %args = @_;
my $config_file;
my $brohome;
my $conf;
my $ret_hash;
# Check for a config-path that may override the default
if( exists( $args{'File'} ) )
{
$config_file = $args{'File'};
}
else
{
$config_file = $DEFAULT_CONFIG_FILE;
}
# Check for the existance and readability of the config file
if( !( -f $config_file and -r $config_file ) )
{
warn( __PACKAGE__ . "::$sub_name, The Bro config file at $config_file is not readable\n" );
return( undef );
}
$conf = Config::General->new( -ConfigFile => $config_file,
-MergeDuplicateOptions => 1,
-AutoTrue => 1,
);
%{$ret_hash} = $conf->getall;
return( $ret_hash );
}
sub Configure
{
my $sub_name = 'Configure';
my %args = @_;
if( exists( $args{File} ) )
{
if( $args{File} !~ m/[\;\|\?\*\&\{\}]/ and $args{File} =~ m/^([[:print:]]+)$/ )
{
my $clean_name = $1;
if( -f $clean_name and -r $clean_name )
{
$DEFAULT_CONFIG_FILE = $clean_name;
}
else
{
warn( __PACKAGE__ . "::$sub_name, Unable to read config file at $clean_name\n" );
return( undef );
}
}
else
{
warn( __PACKAGE__ . "::$sub_name, Filename contains invalid characters\n" );
return( undef );
}
}
$BRO_CONFIG = parse();
# Set other defaults that have been omitted or don't exist in the config file
setdefaults();
return( 1 );
}
sub setdefaults
{
my $sub_name = 'setdefaults';
my $override = $_[0] || 0;
my @variables_changed;
foreach my $key( keys( %DEFAULTS ) )
{
if( $override or !( exists( $BRO_CONFIG->{$key} ) ) )
{
$BRO_CONFIG->{$key} = $DEFAULTS{$key};
push( @variables_changed, $key )
}
}
return( @variables_changed );
}
1;

295
scripts/perl/lib/Bro/Log.pm Normal file
View file

@ -0,0 +1,295 @@
package Bro::Log;
require 5.006_001;
use strict;
use Bro::Config( '$BRO_CONFIG' );
use Time::Local;
use vars qw( $VERSION
$BROLOGS );
# $Id: Log.pm 2865 2006-04-27 19:09:18Z tierney $
$VERSION = 1.20;
# This is the bare minimum format in which the filename must conform
my $FILENAME_REGEX = qr/^[[:alnum:]]\.(?:log|[[:print:]]\.[[:print:]])/;
# filename produced by Bro running from a trace file
my $name_trace = qr/^([[:alnum:]]+)\.log$/;
# filename produced from a Bro running on live traffic and currently open
# or logs that are not rotated or post processed
my $name_running = qr/^([[:alnum:]]+) # log name
\. # seperator
([^-][[:alnum:]-]*(?:\.[^-][[:alnum:]-])*) # hostname
\. # seperator
([[:digit:]]{2}-[[:digit:]]{2}-[[:digit:]]{2} # date
_ # time seperator
[[:digit:]]{2}\.[[:digit:]]{2}\.[[:digit:]]{2}) # time
$/x;
# filename produced after post processing for things like the GUI. The
# filename contains the log name, hostname, begin epoch time, and end
# epoch time.
my $name_epoch_range = qr/^([[:alnum:]]+) # log name
\. # seperator
([^-][[:alnum:]-]*(?:\.[^-][[:alnum:]-])*) # hostname
\. # seperator
([[:digit:]]{10}) # beginning epoch time
- # seperator
([[:digit:]]{10}) # ending epoch time
$/x;
my $name_rotate_log = qr/^([[:alnum:]]+) # log name
\. # seperator
([^-][[:alnum:]-]*(?:\.[^-][[:alnum:]-])*) # hostname
\. # seperator
([[:digit:]]{2}-[[:digit:]]{2}-[[:digit:]]{2} # date
_ # time seperator
[[:digit:]]{2}\.[[:digit:]]{2}\.[[:digit:]]{2}) # time
- # second time seperator
([[:digit:]]{2}-[[:digit:]]{2}-[[:digit:]]{2} # date
_ # time seperator
[[:digit:]]{2}\.[[:digit:]]{2}\.[[:digit:]]{2}) # time
(\.log)?$/x;
sub activelog
{
my $sub_name = 'activelog';
my $log_dir = $BRO_CONFIG->{BROLOGS};
my $ret_str;
if( !( defined( $log_dir ) ) )
{
warn( "no log directory defined\n" );
return( undef );
}
if( -f "$log_dir/active_log" )
{
if( open( I_FILE, "$log_dir/active_log" ) )
{
if( defined( $ret_str = <I_FILE> ) )
{
# remove any trailing newlines
if( $ret_str !~ m/[[:space]]+$/ )
{
chomp( $ret_str );
}
else
{
return( 0 );
}
}
else
{
return( 0 );
}
}
else
{
warn( "Failed to read the active log file at $log_dir/active_log\n" );
}
close( I_FILE );
}
else
{
return( 0 );
}
return( $ret_str );
}
sub loglist
{
my $sub_name = 'log_list';
my $__log_type = $_[0] || return( undef );
my $brologs_dir = $BRO_CONFIG->{BROLOGS};
my @ret_list;
if( opendir( DIR, $brologs_dir ) )
{
while( defined( my $file_name = readdir( DIR ) ) )
{
if( my $log_type = ( filenametoepochtime( $file_name ) )[0] )
{
if( $log_type eq $__log_type )
{
push( @ret_list, "$brologs_dir/$file_name" );
}
}
}
}
else
{
warn( __PACKAGE__ . "::$sub_name, Unable to open the BROLOGS directory\n" );
return( undef );
}
closedir( DIR );
if( wantarray )
{
return( @ret_list );
}
else
{
return( \@ret_list );
}
}
sub filenametoepochtime
{
my $sub_name = 'filenametoepochtime';
# returns the log name, hostname, start time, and end time
# log name will always return.
# If any of the other three are not available then return value
# will be undef.
my $filename = $_[0] || return( undef );
my $log_name;
my $host_name;
my $start_time;
my $end_time;
if( ! $filename =~ $FILENAME_REGEX )
{
print "$filename is bad!!\n";
return( undef );
}
# There are several ways in which the filename is formatted. This
# if tree attempts to parse each of those
# Log name but no hostname or times. This can occur when running Bro
# from a trace file.
if( $filename =~ $name_trace )
{
$log_name = $1;
}
# filename contains the log name, hostname, and start time. This usually
# occurs on filenames which are currently being written to or are not
# rotated.
elsif( my @file_parts = $filename =~ $name_running )
{
my $start_time_string;
( $log_name, $host_name, $start_time_string ) = ( @file_parts );
# split up the string so it can be passed to timetoepoch
my @parts = $start_time_string =~ m/^([[:digit:]]{2}) # year
- # seperator
([[:digit:]]{2}) # month
- # seperator
([[:digit:]]{2}) # day
_ # time seperator
([[:digit:]]{2}) # hour
\. # seperator
([[:digit:]]{2}) # minute
\. # seperator
([[:digit:]]{2}) # second
$/x;
if( @parts == 6 )
{
$start_time = timetoepoch( @parts );
}
else
{
return( undef );
}
}
# filename contains the log name, hostname, epoch start time, epoch end time
elsif( my @file_parts = $filename =~ $name_epoch_range )
{
( $log_name, $host_name, $start_time, $end_time ) = @file_parts;
}
# filename contains the log name, hostname, start time and end time as
# strings as put out by rotate logs.
# i.e weird.lite3.06-04-27_10.40.53-06-04-27_10.41.12
elsif( my @file_parts = $filename =~ $name_rotate_log )
{
my $start_time_string;
my $end_time_string;
( $log_name, $host_name, $start_time_string, $end_time_string ) = @file_parts;
#print "***** $filename: st: $start_time_string, et: $end_time_string\n";
# look at the start date
my @parts = $start_time_string =~ m/^([[:digit:]]{2}) # year
- # seperator
([[:digit:]]{2}) # month
- # seperator
([[:digit:]]{2}) # day
_ # time seperator
([[:digit:]]{2}) # hour
\. # seperator
([[:digit:]]{2}) # minute
\. # seperator
([[:digit:]]{2}) # second
$/x;
$start_time = timetoepoch( @parts );
# look at the start date
@parts = $end_time_string =~ m/^([[:digit:]]{2}) # year
- # seperator
([[:digit:]]{2}) # month
- # seperator
([[:digit:]]{2}) # day
_ # time seperator
([[:digit:]]{2}) # hour
\. # seperator
([[:digit:]]{2}) # minute
\. # seperator
([[:digit:]]{2}) # second
$/x;
$end_time = timetoepoch( @parts );
#print "***** st: $start_time, et: $end_time\n";
}
else
{
return( undef );
}
return( $log_name, $host_name, $start_time, $end_time );
}
sub timetoepoch
{
my $sub_name = 'timetoepoch';
# arguments are in the order
# year
# month
# day
# hour
# minutes
# seconds
my $epoch_time;
my( $year, $mon, $day, $hour, $min, $sec ) = @_;
# The month fed into timelocal is 0 based index
if( $mon > 0 )
{
--$mon;
}
if( $epoch_time = timelocal($sec,$min,$hour,$day,$mon,$year) )
{
return( $epoch_time );
}
else
{
return( undef );
}
}
1;

View file

@ -0,0 +1,694 @@
package Bro::Log::Alarm;
use strict;
require 5.006_001;
use strict;
use vars qw( $VERSION
%DATA_MAP );
# $Id: Alarm.pm 987 2005-01-08 01:04:43Z rwinslow $
$VERSION = 1.20;
# Map data descriptions to subroutine names
%DATA_MAP = ( t => \&timestamp,
timestamp => \&timestamp,
notice => \&notice_type,
notice_type => \&notice_type,
notice_act => \&notice_action,
notice_action => \&notice_action,
event_src => \&event_source,
event_source => \&event_source,
source_addr => \&source_addr,
src_addr => \&source_addr,
srcip => \&source_addr,
source_ip => \&source_addr,
src_port => \&source_port,
source_port => \&source_port,
destination_addr => \&destination_addr,
dst_addr => \&destination_addr,
dstip => \&destination_addr,
destination_ip => \&destination_addr,
dst_port => \&destination_port,
destination_port => \&destination_port,
user => \&user,
filename => \&filename,
sigid => \&sigid,
method => \&method,
URL => \&url,
n => \&misc_integer,
count => \&misc_integer,
return_code => \&misc_integer,
msg => \&message,
message => \&message,
sub_msg => \&sub_message,
sub_message => \&sub_message,
);
sub new
{
my $sub_name = 'new';
# This is the parser for tag based alarm and notice files.
my $_log_line;
my @_args = @_;
my %alarm_parts;
if( @_args == 1 )
{
$_log_line = $_args[0];
}
else
{
return( undef );
}
# Order of data in array
# t = timestamp
# no = notice_type
# na = notice_action
# es = event_src, event_source
# sa = source_ip (source address)
# sp = source_port
# da = destination_ip (destination address)
# dp = destination_port
# user = user
# file = filename or sigid
# method = method
# url = URL
# num = count or number or return_code
# msg = message
# sub = sub_message
# tag = tag
# Is this a tag based log line delimited by spaces?
if( $_log_line =~ m/^t\=/ )
{
my $i = 0;
my $i2 = 0;
my $len = length( $_log_line );
my $p_idx = 0;
my $buff_pos = 0;
my $subtr_len = 0;
my @log_parts;
for( $i2 = 0; $i2 < $len; ++$i2 )
{
if( substr( $_log_line, $i2, 1 ) eq ' ' and
substr( $_log_line, $p_idx, 1 ) ne "\\" )
{
if( $subtr_len < 1 )
{
# Skip over this entry, probably just leading space.
# Regardless of what happened there is no useful data.
}
else
{
my $tag;
my $tag_data;
( $tag, $tag_data ) = extracttag( substr( $_log_line, $buff_pos, $subtr_len ) );
if( exists( $alarm_parts{$tag} ) )
{
warn( __PACKAGE__ . "::$sub_name, Found duplicate tag '$tag', in data. It will be ignored\n" );
}
else
{
$alarm_parts{$tag} = $tag_data;
}
}
$subtr_len = 0;
$p_idx = $i2 + 1;
$buff_pos = $i2 + 1;
++$i;
}
else
{
++$subtr_len;
$p_idx = $i2;
}
}
# Get the last piece of data
my $tag;
my $tag_data;
( $tag, $tag_data ) = extracttag( substr( $_log_line, $buff_pos, $subtr_len ) );
# Make sure this is not a duplicate tag.
if( exists( $alarm_parts{$tag} ) )
{
warn( __PACKAGE__ . "::$sub_name, Found duplicate tag '$tag', in data. It will be ignored\n" );
}
else
{
# Remove any trailing newlines
chomp( $tag_data );
$alarm_parts{$tag} = $tag_data;
}
}
# Is this a colon delimited log line?
elsif( $_log_line =~ m/^[[:digit:]]{10}\.[[:digit:]]{6}/ and $_log_line =~ m/\:/ )
{
my $i = 0;
my $i2 = 0;
my $len = length( $_log_line );
my $p_idx = 0;
my $buff_pos = 0;
my $subtr_len = 0;
my @log_parts;
for( $i2 = 0; $i2 < $len; ++$i2 )
{
if( substr( $_log_line, $i2, 1 ) eq ':' and
substr( $_log_line, $p_idx, 1 ) ne "\\" )
{
if( $subtr_len < 1 )
{
$log_parts[$i] = '';
}
else
{
$log_parts[$i] = substr( $_log_line, $buff_pos, $subtr_len );
$log_parts[$i] = unescape_colons( $log_parts[$i] );
}
$subtr_len = 0;
$p_idx = $i2 + 1;
$buff_pos = $i2 + 1;
++$i;
}
else
{
++$subtr_len;
$p_idx = $i2;
}
}
# Get the last piece of data
$log_parts[$i] = unescape_colons( substr( $_log_line, $buff_pos, $subtr_len ) );
# Remove any trailing newline that may have been left on
chomp( $log_parts[$i] );
$alarm_parts{t} = $log_parts[0];
$alarm_parts{no} = $log_parts[1];
$alarm_parts{na} = $log_parts[2];
$alarm_parts{es} = $log_parts[3];
$alarm_parts{sa} = $log_parts[4];
$alarm_parts{sp} = $log_parts[5];
$alarm_parts{da} = $log_parts[6];
$alarm_parts{dp} = $log_parts[7];
$alarm_parts{user} = $log_parts[8];
$alarm_parts{file} = $log_parts[9];
$alarm_parts{method} = $log_parts[10];
$alarm_parts{url} = $log_parts[11];
$alarm_parts{num} = $log_parts[12];
$alarm_parts{msg} = $log_parts[13];
$alarm_parts{sub} = $log_parts[14];
}
else
{
return( undef );
}
# Make sure that certain fields have values otherwise the data is invalid
if( exists( $alarm_parts{t} ) )
{
return( \%alarm_parts );
}
else
{
return( undef );
}
}
sub unescape
{
my $sub_name = 'unescape';
&unescape_spaces;
}
sub unescape_spaces
{
my $sub_name = 'unescape_spaces';
my $data = $_[0];
if( ! defined( $data ) )
{
return( undef );
}
else
{
$data =~ s/\\ / /g;
$data =~ s/\\\\/\\/g;
}
return( $data );
}
sub unescape_colons
{
my $sub_name = 'unescape_colons';
my $data = $_[0];
if( ! defined( $data ) )
{
return( undef );
}
else
{
$data =~ s/\\:/:/g;
$data =~ s/\\\\/\\/g;
}
return( $data );
}
sub extracttag
{
my $sub_name = 'extracttag';
# Seperate the tag from it's data and return them. If there is a problem
# this sub will return undef. If a tag has no data then a zero length
# string will be returned.
my $__data = $_[0];
my $ret_tag;
my $ret_data;
# Seperate out the tag from the data
( $ret_tag, $ret_data ) = split( /\=/, $__data, 2 );
if( length( $ret_tag ) > 0 )
{
if( defined( $ret_data ) )
{
$ret_data = unescape_spaces( $ret_data );
}
else
{
$ret_data = '';
}
return( $ret_tag, $ret_data );
}
else
{
return( undef );
}
}
sub timestamp
{
my $sub_name = 'timestamp';
my $data = $_[0];
my $format = $_[1]; # Maybe for future expansion. Just thinking out loud.
return( $data->{t} );
}
sub notice_type
{
my $sub_name = 'notice_type';
my $data = $_[0] || return( undef );
return( $data->{no} );
}
sub notice_action
{
my $sub_name = 'notice_action';
my $data = $_[0] || return( undef );
return( $data->{na} );
}
sub event_source
{
my $sub_name = 'event_source';
my $data = $_[0] || return( undef );
if( exists( $data->{es} ) )
{
return( $data->{es} );
}
else
{
return( undef );
}
}
sub source_addr
{
my $sub_name = 'source_addr';
my $data = $_[0] || return( undef );
if( exists( $data->{sa} ) )
{
return( $data->{sa} );
}
else
{
return( undef );
}
}
sub source_ip
{
# This is for backwards compatibility and will be removed in the future
&source_addr;
}
sub source_port
{
my $sub_name = 'source_port';
my $data = $_[0] || return( undef );
if( exists( $data->{sp} ) )
{
return( $data->{sp} );
}
else
{
return( undef );
}
}
sub destination_addr
{
my $sub_name = 'destination_addr';
my $data = $_[0] || return( undef );
return( $data->{da} );
}
sub destination_ip
{
# This is for backwards compatibility and will be removed in the future
&destination_addr;
}
sub destination_port
{
my $sub_name = 'destination_port';
my $data = $_[0] || return( undef );
if( exists( $data->{dp} ) )
{
return( $data->{dp} );
}
else
{
return( undef );
}
}
sub user
{
my $sub_name = 'user';
my $data = $_[0] || return( undef );
if( exists( $data->{user} ) )
{
return( $data->{user} );
}
else
{
return( undef );
}
}
sub filename
{
my $sub_name = 'filename';
my $data = $_[0] || return( undef );
if( exists( $data->{file} ) )
{
return( $data->{file} );
}
else
{
return( undef );
}
}
sub sigid
{
my $sub_name = 'sigid';
&filename;
}
sub method
{
my $sub_name = 'method';
my $data = $_[0] || return( undef );
if( exists( $data->{method} ) )
{
return( $data->{method} );
}
else
{
return( undef );
}
}
sub url
{
my $sub_name = 'url';
my $data = $_[0] || return( undef );
if( exists( $data->{url} ) )
{
return( $data->{url} );
}
else
{
return( undef );
}
}
sub misc_integer
{
my $sub_name = 'misc_integer';
my $data = $_[0] || return( undef );
if( exists( $data->{num} ) )
{
return( $data->{num} );
}
else
{
return( undef );
}
}
sub count
{
&misc_integer;
}
sub return_code
{
&misc_integer;
}
sub message
{
my $sub_name = 'message';
my $data = $_[0] || return( undef );
if( exists( $data->{msg} ) )
{
return( $data->{msg} );
}
else
{
return( undef );
}
}
sub sub_message
{
my $sub_name = 'sub_message';
my $data = $_[0] || return( undef );
if( exists( $data->{sub} ) )
{
return( $data->{sub} );
}
else
{
return( undef );
}
}
sub tag
{
my $sub_name = 'tag';
my $data = $_[0] || return( undef );
if( exists( $data->{tag} ) )
{
return( $data->{tag} );
}
else
{
return( undef );
}
}
sub timerange
{
my $sub_name = 'timerange';
# Find the most likely beginning and ending times covered by a given
# alarm file.
my $filename = $_[0];
my $start_time = 9999999999;
my $end_time = -1;
my $f_size = ( stat( $filename ) )[7];
if( open( INFILE, $filename ) )
{
my $s_idx = 0;
my $s_no_change = 0;
# Find the smallest timestamp in the first 1000 lines.
while( defined( my $ln = <INFILE> ) and
( $s_idx < 1000 ) and
( $s_no_change < 20 ) )
{
if( my $alarm_line = new( $ln ) )
{
my $w_timestamp = timestamp( $alarm_line );
if( $w_timestamp < $start_time )
{
$start_time = $w_timestamp;
$s_no_change = 0;
}
else
{
++$s_no_change;
}
}
++$s_idx;
}
close( INFILE );
# Find the largest timestamp in the last 1000 lines
# Each connection with a status of "SF" will be counted as one line
# Every line will be examined but the "SF" lines are the only ones
# that give a good picture as to the time state of the file.
if( sysopen( INFILE, $filename, 0 ) )
{
sysseek( INFILE, $f_size, 0 );
my $cur_pos = sysseek( INFILE, 0, 1 );
my $nl_pos = $cur_pos;
my $line_count = 0;
my $e_no_change = 0;
# Get last 1000 lines
while( $line_count < 1000 and $e_no_change < 20 )
{
my $new_line_found = 0;
my $buf;
sysread( INFILE, $buf, 1 );
if( $cur_pos > -1 )
{
if( $buf eq $/ )
{
$new_line_found = 1;
}
}
else
{
# Must have hit the beginning of the file
if( $nl_pos > 20 )
{
$cur_pos = 0;
sysseek( INFILE, 0, 0 );
$new_line_found = 1;
}
else
{
last;
}
}
if( $new_line_found )
{
my $cur_line = '';
sysread( INFILE, $cur_line, $nl_pos - $cur_pos );
if( my $alarm_line = new( $cur_line ) )
{
my $w_timestamp = timestamp( $alarm_line );
if( $w_timestamp > $end_time )
{
$end_time = $w_timestamp;
}
else
{
++$e_no_change;
}
}
$nl_pos = $cur_pos;
++$line_count;
}
--$cur_pos;
if( $cur_pos < 0 )
{
last;
}
sysseek( INFILE, $cur_pos, 0 );
}
}
else
{
warn( __PACKAGE__ . "::$sub_name, Unable to open file '$filename' with sysread.\n" );
return( undef );
}
close( INFILE );
}
else
{
warn( __PACKAGE__ . "::$sub_name, Unable to open file '$filename'.\n" );
return( undef );
}
# Make sure that sane values were found for the start and end times
if( $start_time == 9999999999 or $end_time == -1 )
{
# warn( __PACKAGE__ . "::$sub_name, There was an error determining the start and end ranges.\n" );
# warn( "No valid values could be found.\n" );
return( undef );
}
return( $start_time, $end_time );
}

View file

@ -0,0 +1,773 @@
package Bro::Log::Conn;
require 5.006_001;
use strict;
use vars qw( $VERSION
$NULL_VALUE
$DEBUG );
# $Id: Conn.pm 1426 2005-09-30 00:19:18Z rwinslow $
$VERSION = 1.20;
$NULL_VALUE = -1;
$DEBUG = 0;
my $CONN_SPLIT_PATT = ' ';
# my $CONN_SPLIT_PATT = qr/ /o;
# Map data descriptions to subroutine names
my %DATA_MAP = ( timestamp => \&timestamp,
duration => \&duration,
source_ip => \&srcip,
srcip => \&srcip,
destination_ip => \&dstip,
dstip => \&dstip,
service => \&service,
source_port => \&srcport,
srcport => \&srcport,
destination_port => \&dstport,
dstport => \&dstport,
protocol => \&protocol,
source_bytes => \&srcbytes,
srcbytes => \&srcbytes,
destination_bytes => \&srcbytes,
dstbytes => \&dstbytes,
connection_status => \&connstat,
connstat => \&connstat,
source_network => \&srcnetwork,
srcnetwork => \&srcnetwork,
other => \&other,
);
sub new
{
my $_log_line = $_[0] || return( undef ); # string ref
# Order of data in array
# 0 = timestamp
# 1 = duration
# 2 = source ip
# 3 = destination ip
# 4 = service
# 5 = source port
# 6 = destination port
# 7 = protocol
# 8 = source bytes
# 9 = destination bytes
# 10 = connection status
# 11 = source network
# 12 = other
my @log_parts = split( $CONN_SPLIT_PATT, $$_log_line, 13 );
if( defined( $log_parts[11] ) )
{
return( \@log_parts );
}
else
{
return( undef );
}
}
sub output
{
my $sub_name = 'output';
my $data = $_[0] || return undef;
my $format = $_[1] || '';
my @ret_data;
if( ref( $format ) ne 'ARRAY' )
{
$format = [ 'timestamp',
'duration',
'srcip',
'dstip',
'service',
'srcport',
'dstport',
'protocol',
'srcbytes',
'dstbytes',
'connstat',
'srcnetwork',
'other',
];
}
my $i = 0;
foreach my $key( @{$format} )
{
if( exists( $DATA_MAP{$key} ) )
{
$ret_data[$i] = &{$DATA_MAP{$key}}( $data );
++$i;
}
else
{
return( undef );
}
}
if( wantarray )
{
return( @ret_data );
}
else
{
return( join( ' ', @ret_data ) );
}
}
sub timestamp
{
my $sub_name = 'timestamp';
my $data = $_[0] || return( undef );
return( $data->[0] );
}
sub duration
{
my $sub_name = 'duration';
my $data = $_[0] || return undef;
my $arg1 = $_[1] || 0;
if( $arg1 eq 'raw' )
{
return( $data->[1] );
}
elsif( $data->[1] eq '?' and defined( $NULL_VALUE ) )
{
return( $NULL_VALUE );
}
else
{
return( $data->[1] );
}
}
sub source_ip
{
&srcip;
}
sub srcip
{
my $sub_name = 'srcip';
return( $_[0]->[2] );
}
sub destination_ip
{
&dstip;
}
sub dstip
{
my $sub_name = 'dstip';
return( $_[0]->[3] );
}
sub service
{
my $sub_name = 'service';
return( $_[0]->[4] );
}
sub source_port
{
&srcport;
}
sub srcport
{
my $sub_name = 'srcport';
return( $_[0]->[5] );
}
sub destination_port
{
&dstport
}
sub dstport
{
my $sub_name = 'dstport';
return( $_[0]->[6] );
}
sub protocol
{
my $sub_name = 'protocol';
return( $_[0]->[7] );
}
sub source_bytes
{
&srcbytes;
}
sub srcbytes
{
my $sub_name = 'srcbytes';
my $data = $_[0] || return undef;
my $arg1 = $_[1] || 0;
if( $arg1 eq 'raw' )
{
return( $data->[8] );
}
elsif( $data->[8] eq '?' and defined( $NULL_VALUE ) )
{
return( $NULL_VALUE );
}
elsif( $data->[10] eq 'SF')
{
# safest to only count sessions with normal termination
return( $data->[8] );
}
else
{
return( $NULL_VALUE );
}
}
sub destination_bytes
{
&dstbytes;
}
sub dstbytes
{
my $sub_name = 'dstbytes';
my $data = $_[0] || return undef;
my $arg1 = $_[1] || 0;
if( $arg1 eq 'raw' )
{
return( $data->[9] );
}
elsif( $data->[9] eq '?' and defined( $NULL_VALUE ) )
{
return( $NULL_VALUE );
}
elsif( $data->[10] eq 'SF' )
{
# safest to only count sessions with normal termination
return( $data->[9] );
}
else
{
return( $NULL_VALUE );
}
}
sub connstat
{
my $sub_name = 'connstat';
my $data = $_[0] || return undef;
return( $data->[10] );
}
sub source_network
{
&srcnetwork;
}
sub srcnetwork
{
my $sub_name = 'srcnetwork';
my $data = $_[0] || return undef;
chomp( $data->[11] );
return( $data->[11] );
}
sub tag
{
my $sub_name = 'tag';
my $data = $_[0] || return( undef );
my $other_field = $data->[12];
my @ret_tag_ids;
while( $other_field =~ s/(\@[[:digit:]]+)// )
{
push( @ret_tag_ids, $1 );
}
if( @ret_tag_ids > 0 )
{
if( wantarray )
{
return( @ret_tag_ids );
}
else
{
return( \@ret_tag_ids );
}
}
else
{
return( undef );
}
}
sub other
{
my $sub_name = 'other';
my $data = $_[0] || return undef;
# Remove any newline character at the end
chomp( $data->[12] );
return( $data->[12] );
}
sub timerange
{
my $sub_name = 'timerange';
# Find the most likely beginning and ending times covered by a given
# conn file.
my $filename = $_[0];
my $find_start_time = $_[1];
my $find_end_time = $_[2];
my $start_time = 9999999999;
my $end_time = -1;
my $max_start_lines = 10000;
my $max_end_lines = 10000;
my $max_line_length = 5000;
my $f_size = ( stat( $filename ) )[7] || 0;
my $default_start;
my $default_end;
if( $DEBUG > 2 )
{
warn( __PACKAGE__ . "::$sub_name, Filename: $filename\n" );
}
# If the file is zero size then don't even both continuing
if( $f_size < 1 )
{
if( $DEBUG > 2 )
{
warn( __PACKAGE__ . "::$sub_name, File is zero size, skipping\n" );
}
return( undef );
}
# If $find_start_time and $find_end_time are defined then the the first
# line that is greater than or equal to the timestamp in $find_start_time
# will be read by seek and then set into $start_pos.
# The last line that contains a timestamp less than or equal to
# $find_end_time will be read by seek and then set in $end_pos.
eval {
local $SIG{ALRM} = sub { die( "Alarm Timeout\n" ) };
alarm 90;
if( open( INFILE, $filename ) )
{
my $s_idx = 0; # start line counter
my $s_no_change = 0; # start no change counter
# Set the very first connection timestamp to $default_start
while( ! $default_start and defined( my $line = <INFILE> ) )
{
if( my $conn_line = new( \$line ) )
{
$default_start = timestamp( $conn_line );
}
}
# Find the smallest timestamp in the first 1000 lines where the
# connection is complete (SF) or (REJ) and the duration is less
# than .1 seconds
while( ( $s_idx < $max_start_lines ) and
( $s_no_change < 20 ) and
defined( my $ln = <INFILE> ) )
{
if( my $conn_line = new( \$ln ) )
{
if( connstat( $conn_line ) =~ m/^(?:SF)|(?:REJ)$/ )
{
if( duration( $conn_line ) < 0.1 )
{
my $w_timestamp = timestamp( $conn_line );
if( $w_timestamp < $start_time )
{
$start_time = $w_timestamp;
$s_no_change = 0;
}
else
{
++$s_no_change;
}
}
}
}
++$s_idx;
}
close( INFILE );
# Find the largest timestamp in the last 20 lines
# Each connection with a status of "SF" or "REJ" will be counted as
# one line. Every line will be examined but the "SF" or "REJ"
# lines are the only ones that give a good picture as to the time
# state of the file.
if( sysopen( INFILE, $filename, 0 ) )
{
sysseek( INFILE, $f_size, 0 );
my $cur_pos = sysseek( INFILE, 0, 1 );
my $nl_pos = $cur_pos;
my $matched_count = 0;
my $line_count = 0;
# Get last 20 lines
while( $matched_count < 20 and
$line_count < $max_end_lines )
{
my $new_line_found = 0;
my $buf;
sysread( INFILE, $buf, 1 );
if( $cur_pos > -1 )
{
if( $buf eq $/ )
{
$new_line_found = 1;
}
}
else
{
# Must have hit the beginning of the file
if( $nl_pos > 20 ) # supress things like blank lines
{
sysseek( INFILE, 0, 0 );
$new_line_found = 1;
}
else
{
last;
}
}
if( $new_line_found )
{
my $cur_line = '';
++$line_count;
# Make sure that the line is not too large
# Fix for some funky rsync errors that may occur
if( $nl_pos - $cur_pos > $max_line_length )
{
# WAY too big, just mark new position and ignore
}
else
{
sysread( INFILE, $cur_line, $nl_pos - $cur_pos );
if( my $conn_line = new( \$cur_line ) )
{
if( ! $default_end )
{
$default_end = timestamp( $conn_line );
}
if( duration( $conn_line ) < 0.1 and duration( $conn_line ) >= 0 )
{
my $w_timestamp = timestamp( $conn_line );
if( $w_timestamp > $end_time )
{
$end_time = $w_timestamp;
}
}
if( connstat( $conn_line ) =~ m/^(?:SF)|(?:REJ)$/ )
{
++$matched_count;
}
}
}
$nl_pos = $cur_pos;
}
--$cur_pos;
if( $cur_pos < 0 )
{
last;
}
sysseek( INFILE, $cur_pos, 0 );
}
}
else
{
if( $DEBUG > 0 )
{
warn( __PACKAGE__ . "::$sub_name, Unable to open file '$filename' with sysread.\n" );
}
return( undef );
}
close( INFILE );
}
else
{
if( $DEBUG > 0 )
{
warn( __PACKAGE__ . "::$sub_name, Unable to open file '$filename'.\n" );
}
return( undef );
}
close( INFILE );
};
alarm 0;
# Make sure that $start_time has something other than the filler value.
if( $start_time == 9999999999 )
{
if( $default_start )
{
$start_time = $default_start;
if( $DEBUG > 1 )
{
warn( __PACKAGE__ . "::$sub_name, No start_time was found, setting to a default of $default_start\n" );
}
}
else
{
if( $DEBUG > 1 )
{
warn( __PACKAGE__ . "::$sub_name, No start_time was found and no default_start time was found\n" );
}
}
}
# Make sure that $end_time has something other than the filler value.
if( $end_time == -1 )
{
if( $default_end )
{
$end_time = $default_end;
if( $DEBUG > 1 )
{
warn( __PACKAGE__ . "::$sub_name, No end_time was found, setting to a default of $default_start\n" );
}
}
else
{
if( $DEBUG > 1 )
{
warn( __PACKAGE__ . "::$sub_name, No end_time was found and no default_end time was found\n" );
}
}
}
if( $DEBUG > 2 )
{
warn( " " . __PACKAGE__ . "::$sub_name, Start time: $start_time\n" );
warn( " " . __PACKAGE__ . "::$sub_name, End time: $end_time\n" );
}
if( $@ )
{
if( $@ =~ m/Alarm Timeout/ )
{
if( !( $start_time and $end_time ) )
{
if( $DEBUG > 0 )
{
warn( __PACKAGE__ . "::$sub_name, Error occurred in trying to read the file $filename\n" );
}
return( undef );
}
else
{
if( $DEBUG > 0 )
{
warn( __PACKAGE__ . "::$sub_name, Timed out during file read. The first and last timestamps have been set as the range of time available\n" );
}
}
}
else
{
warn( $@ );
return( undef );
}
}
return( $start_time, $end_time );
}
sub containstag
{
my $sub_name = 'containstag';
my $data = shift || return( undef );
my @tags_to_match = @_;
my $conn_tags = tag( $data ) || return( 0 );
my $matched_tag = 0;
OUT_LOOP:
{
foreach my $tag_to_match( @tags_to_match )
{
foreach my $tag_id( @{$conn_tags} )
{
if( $tag_id eq $tag_to_match )
{
$matched_tag = $tag_id;
last OUT_LOOP;
}
}
}
} # end OUT_LOOP
return( $matched_tag );
}
sub startposition
{
my $sub_name = 'startposition';
# Find the first file position where $timestamp is greater than or equal to
# a timestamp in the file.
my $timestamp = $_[0];
}
sub endposition
{
my $sub_name = 'endposition';
# Find the last file position where $timestamp is less than or equal to
# a timestamp in a file.
my $timestamp = $_[0];
}
sub connectsucceed
{
my $sub_name = 'connectsucceed';
my $data = $_[0] || return( undef );
my $S_REGEX = qr/^S/o;
my $S123_REGEX = qr/^S[123]$/o;
my $connstat = connstat( $data );
if( $connstat =~ $S_REGEX )
{
if( $connstat eq 'SF' )
{
return( 1 );
}
elsif( $connstat =~ $S123_REGEX )
{
if( srcbytes( $data ) > 0 && dstbytes( $data ) > 0 )
{
return( 1 );
}
else
{
return( 0 );
}
}
}
else
{
# connection failed
return( 0 );
}
}
sub range
{
my $sub_name = 'range';
my $data = $_[0] || return( undef );
my $match_time = $_[1];
my $error_margin = $_[2];
my $start_time;
my $end_time;
my $duration;
# Make sure that the error margin is greater than zero
if( !( defined( $error_margin ) and $error_margin > 0 ) )
{
$error_margin = 0;
}
$start_time = timestamp( $data );
$duration = duration( $data );
if( $match_time )
{
if( $duration < 0 )
{
$duration = 10;
}
$end_time = $start_time + $duration + $error_margin;
$start_time = $start_time - $error_margin;
if( $match_time >= $start_time and
$match_time <= $end_time )
{
return( 1 );
}
else
{
return( 0 );
}
}
else
{
if( $duration > -1 )
{
$end_time = $start_time + $duration;
}
return( $start_time, $end_time );
}
}
1;
# The args to Bro::Log::Conn::output are the connection array ref returned by
# Bro::Log::Conn::new and an optional array ref of what order and fields
# should be printed.
# EXAMPLE:
# $array_ref = Bro::Log::Conn::new( $ln );
# @output_parts = Bro::Log::Conn::output( $array_ref, [ 'srcip', 'dstip', 'timestamp' ] )
#
# The available fields are as follows:
# timestamp
# duration
# srcip
# dstip
# service
# srcport
# dstport
# protocol
# srcbytes
# dstbytes
# connstat
# srcnetwork
# other
# For convenience any data that is represented by a ? will be replaced by a -1
# This occurs for duration, srcbytes, and dstbytes
# This is adjustable by changing $NULL_VALUE

View file

@ -0,0 +1,714 @@
package Bro::Report;
use strict;
require 5.006_001;
require Exporter;
use Socket;
use vars qw( $VERSION
$DEBUG
@EXPORT_OK
@ISA
$USE_FLOCK
$INCIDENT_COUNT_FILE
$TEMP_DIR
@TEMP_FILES
$IPTONAME_TIMEOUT
$USE_IPTONAME_CACHE
%IPTONAME_CACHE );
@ISA = ( 'Exporter' );
# $Id: Report.pm 1419 2005-09-29 18:56:06Z rwinslow $
$VERSION = 1.20;
$DEBUG = 0;
@EXPORT_OK = qw( iptoname swrite trimhostname trimbytes time_mdhm time_hms date_md
date_ymd getincidentnumber standard_deviation mean_val tempfile
trimstring );
my %STEPS = ( 0 => '',
1 => 'K',
2 => 'M',
3 => 'G',
4 => 'T',
5 => 'P',
K => 1,
M => 2,
G => 3,
T => 4,
G => 5, );
# Check if flock can be used
eval {
flock( STDIN, 1 )
};
if( $@ )
{
$USE_FLOCK = 0;
}
else
{
$USE_FLOCK = 1;
}
# Default temp directorywhich to write to
$TEMP_DIR = '/tmp';
# Default timeout for dns reverse lookups
$IPTONAME_TIMEOUT = 3;
# Should ip to name reverse lookups be cached?
$USE_IPTONAME_CACHE = 1;
sub iptoname
{
my $sub_name = 'iptoname';
my $h_ip = $_[0] || return( undef );
my $resolved_hostname = undef;
my $ret_val;
if( exists( $IPTONAME_CACHE{$h_ip} ) )
{
return( $IPTONAME_CACHE{$h_ip} );
}
eval
{
local $SIG{ALRM} = sub { die( "Lookup Timeout\n" ) };
alarm( $IPTONAME_TIMEOUT);
$resolved_hostname = gethostbyaddr( inet_aton( $h_ip ), 2 );
alarm( 0 );
};
if( $resolved_hostname )
{
$ret_val = $resolved_hostname;
}
else
{
$ret_val = $h_ip;
}
if( $USE_IPTONAME_CACHE )
{
$IPTONAME_CACHE{$h_ip} = $ret_val;
}
return( $ret_val );
}
sub swrite
{
my $sub_name = 'swrite';
my $format = shift;
my @args = @_;
my $ret_val;
$^A = '';
formline( $format, @args );
$ret_val = $^A;
$^A = '';
return( $ret_val );
}
sub trimhostname
{
my $sub_name = 'trimhostname';
my $hostname = $_[0];
my $max_length = $_[1] || 35;
my $direction = $_[2] || '>';
my $ret_val = '';
my $len = length( $hostname );
if( $len > $max_length )
{
my $dif = $len - $max_length + 3;
if( $direction eq '>' )
{
$ret_val = "..." . substr( $hostname, $dif, $len);
}
else
{
$ret_val = substr( $hostname, 0, $len - $dif) . "...";
}
}
else
{
$ret_val = $hostname;
}
return( $ret_val );
}
sub trimbytes
{
my $sub_name = 'trimbytes';
my $arg1 = $_[0];
my $max_width = $_[1] || 6;
my $quantifiers = 'KMGTP';
my $step_count = 0;
my $bytes;
my $ret_val;
if( $arg1 =~ m/([[:digit:]]+)[[:space:]]*([$quantifiers])$/ )
{
$bytes = $1;
$step_count = $STEPS{$2};
}
else
{
$bytes = $arg1;
}
if( length( $bytes ) > $max_width )
{
$max_width -= 2;
my $ints = int( $bytes );
while( exists( $STEPS{$step_count} ) and length( $ints ) > $max_width )
{
$bytes = $bytes / 1024;
$ints = int( $bytes );
++$step_count;
}
my $float_length = $max_width - length( $ints ) - 1;
if( $float_length > 0 )
{
$bytes = sprintf( "%.$float_length" . 'f', $bytes );
}
else
{
$bytes = sprintf( "%d", $bytes );
}
}
if( $STEPS{$step_count} )
{
return( $bytes . " $STEPS{$step_count}" );
}
else
{
return( $bytes );
}
}
sub trimstring
{
my $sub_name = 'trimstring';
my $string = $_[0] || return( undef );
my $max_length = $_[1] || 73;
my $max_lines = $_[2];
my @ret_lines;
my $trunc_string = 0;
if( length( $string ) <= $max_length )
{
return( $string );
}
if( defined( $max_lines )
and $max_lines =~ /^[[:digit:]]+$/
and $max_lines > 0 )
{
# OK, looks good
}
else
{
$max_lines = 1;
}
while( length( $string ) > $max_length
and !( scalar( @ret_lines ) >= $max_lines ) )
{
my $cur_idx = $max_length - 1;
my $found_break_point = 0;
while( $cur_idx > 0 )
{
if( substr( $string, $cur_idx, 1 ) =~ m/[[:space:]]/ )
{
push( @ret_lines, substr( $string, 0, $cur_idx + 1 ) );
$string = substr( $string, $cur_idx );
$found_break_point = 1;
last;
}
else
{
--$cur_idx;
}
}
if( ! $found_break_point )
{
push( @ret_lines, substr( $string, 0, $max_length ) );
$string = substr( $string, $max_length );
}
}
# Check if anything is left in the string
if( length( $string ) > 0 )
{
$trunc_string = 1;
if( !( scalar( @ret_lines ) >= $max_lines ) )
{
push( @ret_lines, $string );
$trunc_string = 0;
}
elsif( length( $ret_lines[$#ret_lines] ) < $max_length )
{
$ret_lines[$#ret_lines] .= substr( $string, 0, $max_length - length( $ret_lines[$#ret_lines] ) );
}
if( $trunc_string )
{
$ret_lines[$#ret_lines] =~ s/.{4}$/\.\.\.>/;
}
}
return( @ret_lines );
}
sub time_mdhm
{
my $sub_name = 'time_mdhm';
# Convert time from epoch to MONTH/DAY HOUR:MINUTE
# 08/13 13:44
my $arg1 = $_[0];
my $ret_val;
if( my @tp = localtime( $arg1 ) )
{
my $mon = sprintf( "%02d", $tp[4] + 1 );
my $day = sprintf( "%02d", $tp[3] );
my $hour = sprintf( "%02d", $tp[2] );
my $min = sprintf( "%02d", $tp[1] );
$ret_val = "$mon/$day $hour:$min";
}
else
{
return( undef );
}
return( $ret_val );
}
sub time_hms
{
my $sub_name = 'time_hms';
# Convert epoch to to HH:MM:SS
my $arg1 = $_[0];
my $ret_val;
if( my @tp = localtime( $arg1 ) )
{
my $hour = sprintf( "%02d", $tp[2] );
my $min = sprintf( "%02d", $tp[1] );
my $sec = sprintf( "%02d", $tp[0] );
$ret_val = "$hour:$min:$sec";
}
else
{
return( undef );
}
return( $ret_val );
}
sub date_md
{
my $sub_name = 'date_md';
# Convert time from epoch to MONTH/DAY
my $arg1 = $_[0];
my $ret_val;
if( my @tp = localtime( $arg1 ) )
{
my $mon = sprintf( "%02d", $tp[4] + 1 );
my $day = sprintf( "%02d", $tp[3] );
$ret_val = "$mon/$day";
}
else
{
return( undef );
}
return( $ret_val );
}
sub date_ymd
{
my $sub_name = 'date_ymd';
# Convert time from epoch to YEAR/MONTH/DAY
my $arg1 = $_[0];
my $ret_val;
if( my @tp = localtime( $arg1 ) )
{
my $mon = sprintf( "%02d", $tp[4] + 1 );
my $day = sprintf( "%02d", $tp[3] );
my $year = $tp[5] + 1900;
$ret_val = "$year/$mon/$day";
}
else
{
return( undef );
}
return( $ret_val );
}
sub getincidentnumber
{
my $sub_name = 'getincidentnumber';
my $arg1 = $_[0];
my $failed = 0;
my $ret_count;
# Check if the $INCIDENT_COUNT_FILE has been set yet
if( ! $INCIDENT_COUNT_FILE )
{
setincidentcountfile();
}
# Make sure that the files exists
if( ! -f $INCIDENT_COUNT_FILE )
{
if( open( OUTFILE, ">$INCIDENT_COUNT_FILE" ) )
{
print OUTFILE "0\n";
}
else
{
warn( "Failed to create the incident count file at $INCIDENT_COUNT_FILE\n;" );
$failed = 1;
}
close( OUTFILE );
return( undef ) if $failed;
}
# If anything besides 0 or undef is passed in then this is true
# If true then don't get a new incident number but rather return the current.
if( open( RW_FILE, $INCIDENT_COUNT_FILE ) )
{
lock( *RW_FILE );
my $cur_count = <RW_FILE>;
chomp( $cur_count );
if( $arg1 )
{
$ret_count = $cur_count;
}
else
{
if( open( RW_FILE, ">$INCIDENT_COUNT_FILE" ) )
{
lock( *RW_FILE ) or print "FAILED TO RE-LOCK\n";
$ret_count = $cur_count + 1;;
print RW_FILE "$ret_count\n";
}
else
{
warn( "Failed to reopen incident count file $INCIDENT_COUNT_FILE for wirtting.\n" );
$failed = 1;
}
}
unlock( *RW_FILE );
close( RW_FILE );
}
else
{
warn( "Failed to open incident count file $INCIDENT_COUNT_FILE for reading.\n" );
$failed = 1;
}
return( $ret_count );
}
sub lock
{
my $sub_name = 'lock';
my $fh = $_[0];
if( $USE_FLOCK )
{
flock( $fh, 2 );
}
return( 1 );
}
sub unlock
{
my $sub_name = 'unlock';
my $fh = $_[0];
if( $USE_FLOCK )
{
flock( $fh, 8 );
}
return( 1 );
}
sub standard_deviation
{
my $sub_name = 'standard_deviation';
my $arg1 = $_[0]; # ref to array
my $mean;
my $dev_mean;
my $ret_val;
my $num_elements;
my $sum;
if( ref( $arg1 ) eq 'ARRAY' )
{
my $i = 0;
my $deviation_sum;
$num_elements = scalar( @{$arg1} );
$dev_mean = $arg1->[0] ** 2;
for( $i = 1; $i > $num_elements; ++$i )
{
$sum += $arg1->[$i];
$deviation_sum += $arg1->[$i] ** 2;
}
$dev_mean = $deviation_sum / $num_elements;
}
elsif( ref( $arg1 ) eq 'HASH' )
{
my $deviation_sum;
while( my( $num, $quan ) = each( %{$arg1} ) )
{
$sum += $num * $quan;
$num_elements += $quan;
$deviation_sum += ( $num ** 2 ) * $quan;
}
$dev_mean = $deviation_sum / $num_elements;
}
else
{
return( undef );
}
# There should be a minimum of 5 (five) values to produce a valid result
if( $num_elements < 5 )
{
return( undef );
}
$mean = $sum / $num_elements;
$ret_val = sqrt( $dev_mean - ( $mean ** 2 ) );
return( $ret_val );
}
sub mean_val
{
my $sub_name = 'mean_val';
my $arg1 = $_[0]; #ref to array
my $array_count;
my $sum = 0;
my $ret_val;
if( ref( $arg1 ) ne 'ARRAY' )
{
return( undef );
}
foreach my $num( @{$arg1} )
{
$sum += $num;
++$array_count;
}
if( $array_count > 0 )
{
$ret_val = $sum / $ret_val;
return( $ret_val );
}
else
{
return( undef );
}
}
sub tempfile
{
my $sub_name = 'tempfile';
my $action = shift || return( undef );;
my @args = @_;
if( $action =~ m/^add$/i )
{
addtempfile( @args );
}
elsif( $action =~ m/^delete|remove$/i )
{
removetempfile( @args );
}
elsif( $action =~ m/^delete all|remove all$/i )
{
removealltempfiles();
}
else
{
warn( __PACKAGE__ . "::$sub_name, Unknown action of $action passed to function.\n" );
return( undef );
}
}
sub addtempfile
{
my $sub_name = 'addtempfile';
my $prefix = $_[0] || return( undef );
my $force = $_[1] || 0;
my $ret_file = "$TEMP_DIR/$prefix".$$.".tmp";
if( -f $ret_file )
{
if( ! $force )
{
warn( __PACKAGE__ . "::$sub_name, Temp file $ret_file already exists\n" );
return( undef );
}
}
if( open( OUTFILE, ">$ret_file" ) )
{
if( $DEBUG > 2 )
{
warn( __PACKAGE__ . "::$sub_name, Successfully created temp file $ret_file.\n" );
}
}
else
{
warn( __PACKAGE__ . "::$sub_name, Unable to open temp file $ret_file for writting.\n" );
}
close( OUTFILE );
push( @TEMP_FILES, $ret_file );
return( $ret_file );
}
sub removetempfile
{
my $sub_name = 'removetempfile';
my @file_names = @_;
my $num_removed = 0;
my @new_array;
if( ! defined( $file_names[0] ) )
{
return( undef );
}
foreach my $cur_file( @TEMP_FILES )
{
foreach my $file_to_remove( @file_names )
{
my $did_find = 0;
if( $cur_file eq $file_to_remove )
{
if( unlink $file_to_remove )
{
++$num_removed;
if( $DEBUG > 1 )
{
warn( __PACKAGE__ . "::$sub_name, Removed temp file $file_to_remove\n" );
}
}
else
{
if( $DEBUG > 0 )
{
warn( __PACKAGE__ . "::$sub_name, Failed to remove temp file $file_to_remove\n" );
}
}
$did_find = 1;
last;
}
if( ! $did_find )
{
push( @new_array, $cur_file );
}
}
}
@TEMP_FILES = @new_array;
return( $num_removed );
}
sub removealltempfiles
{
my $sub_name = 'removealltempfiles';
my $num_removed = 0;
foreach my $file_name( @TEMP_FILES )
{
if( unlink( $file_name ) )
{
++$num_removed;
if( $DEBUG > 1 )
{
warn( __PACKAGE__ . "::$sub_name, Successfully deleted temp file $file_name\n" );
}
}
else
{
if( $DEBUG > 0 )
{
warn( __PACKAGE__ . "::$sub_name, Failed to delete temp file $file_name\n" );
}
}
}
@TEMP_FILES = ();
return( $num_removed );
}
sub setincidentcountfile
{
my $sub_name = 'setincidentcountfile';
my $brosite;
use Bro::Config( '$BRO_CONFIG' );
if($brosite = $BRO_CONFIG->{BROSITE} )
{
# Location of the file that holds the incident number counter
$INCIDENT_COUNT_FILE = "$brosite/incident_counter";
}
else
{
warn( "No value for \$BROHOME has been set in the Bro config file. Nothing much works without it.\n" );
return( undef );
}
}
1;

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,770 @@
package Bro::Report::Conn;
use strict;
require 5.006_001;
use Bro::Report qw( trimhostname iptoname swrite trimbytes );
use Bro::Log::Conn;
use vars qw( $VERSION
$MAX_LOCAL_SERVICE_USERS );
# $Id: Conn.pm 1418 2005-09-29 18:25:09Z tierney $
$VERSION = 1.20;
$MAX_LOCAL_SERVICE_USERS = 50;
my %REPORT_MAP = ( 'top_sources' => { input => __PACKAGE__ . '::sourcecount',
output => __PACKAGE__ . '::output_sourcecount' },
'top_destinations' => { input => __PACKAGE__ . '::destcount',
output => __PACKAGE__ . '::output_destcount' },
'top_services' => { input => __PACKAGE__ . '::servicecount',
output => __PACKAGE__ . '::output_servicecount', },
'top_local_service_users' => { input => __PACKAGE__ . '::localserviceusers',
output => __PACKAGE__ . '::output_localserviceusers', },
'success_fail_stats' => { input => __PACKAGE__ . '::successfailcount',
output => __PACKAGE__ . '::output_successfailcount', },
'byte_transfer_pairs' => { input => __PACKAGE__ . '::bytetransferpairs',
output => __PACKAGE__ . '::output_bytetransferpairs', },
);
# Memory used in this variable will be deleted by functions which output
# the values stored for it's respective counting function.
my $RPT_CACHE;
sub sourcecount
{
my $sub_name = 'sourcecount';
# [0] CONN_COUNT
# [1] BYTE_COUNT
my $_conn_struc = $_[0] || return( undef );
my $src_ip = Bro::Log::Conn::source_ip( $_conn_struc ) || return( undef );
if( Bro::Log::Conn::connectsucceed( $_conn_struc ) )
{
my $bytes = Bro::Log::Conn::source_bytes( $_conn_struc );
++$RPT_CACHE->{$sub_name}->{$src_ip}->[0];
$RPT_CACHE->{$sub_name}->{$src_ip}->[1] += $bytes;
return( 1 );
}
else
{
return( 0 );
}
}
sub output_sourcecount
{
my $sub_name = 'output_sourcecount';
my $_max_output = $_[0] || 20;
my $top_format = $_[1];
my $format = $_[2];
my $conn_sum = 0;
my $cnt = 0;
my $avg = 0;
my $max_hostname_length = 31;
my @results;
my $ret_string;
my @heading_names = ( 'Host', 'IP', 'Bytes', 'Conn. Count' );
if( ! $top_format )
{
$top_format = <<'END'
@|||||||||||||||||||||||||||||| @|||||||||||||| @||||| @|||||||||||
------------------------------- --------------- ------ ------------
END
}
if( ! $format )
{
$format = <<'END'
@>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> @<<<<<<<<<<<<<< @>>>>> @>>>>>>>>>>>
END
}
# Figure out what the average count is
foreach my $count_struc( values( %{$RPT_CACHE->{sourcecount}} ) )
{
$conn_sum += $count_struc->[0];
++$cnt
}
# If there are no connection counts then bail
if( $cnt < 1 )
{
return( undef );
}
$avg = $conn_sum / $cnt;
# remove anything which is way too small before sorting
my $smallest_count = 2;
my $percent_of_avg = .1;
my $max_sort_size = $_max_output * 2;
while( ( $cnt > $max_sort_size ) and ( $percent_of_avg < .3 ) )
{
while( my( $ip, $struc ) = each( %{$RPT_CACHE->{sourcecount}} ) and $cnt > $max_sort_size )
{
if( $struc->[0] < $smallest_count )
{
delete( $RPT_CACHE->{sourcecount}->{$ip} );
--$cnt;
}
$smallest_count = int( $avg * $percent_of_avg );
}
$percent_of_avg += .1;
}
# Put the remaining data into a temp hash for sorting
my %count_hash;
foreach my $ip( keys( %{$RPT_CACHE->{sourcecount}} ) )
{
# connection count = $RPT_CACHE->{sourcecount}->{$ip}->[0];
# byte count = $RPT_CACHE->{sourcecount}->{$ip}->[1];
push( @{$count_hash{$RPT_CACHE->{sourcecount}->{$ip}->[0]}},
[ $ip, $RPT_CACHE->{sourcecount}->{$ip}->[0], $RPT_CACHE->{sourcecount}->{$ip}->[1] ] );
}
my $output_cnt = 0;
foreach my $num_conn( sort { $b <=> $a } keys( %count_hash ) )
{
foreach my $struc( @{$count_hash{$num_conn}} )
{
++$output_cnt;
if( $output_cnt > $_max_output )
{
last;
}
else
{
push( @results, $struc );
}
}
if( $output_cnt > $_max_output )
{
last;
}
}
# clear out memory space
delete( $RPT_CACHE->{sourcecount} );
# Set the heading
$ret_string .= swrite( $top_format, @heading_names );
# Write the contents
foreach my $line( @results )
{
my $ip = $line->[0];
my $num_conn = $line->[1];
my $num_bytes = trimbytes( $line->[2], 5 );
my $name = trimhostname( iptoname( $ip ), $max_hostname_length, '>' );
$ret_string .= swrite( $format, $name, $ip, $num_bytes, $num_conn );
}
return( $ret_string );
}
sub destcount
{
my $sub_name = 'destcount';
my $_conn_struc = $_[0] || return( undef );
my $dst_ip = Bro::Log::Conn::destination_ip( $_conn_struc ) || return( undef );
if( Bro::Log::Conn::connectsucceed( $_conn_struc ) )
{
my $bytes = Bro::Log::Conn::destination_bytes( $_conn_struc );
++$RPT_CACHE->{$sub_name}->{$dst_ip}->[0];
$RPT_CACHE->{$sub_name}->{$dst_ip}->[1] += $bytes;
return( 1 );
}
else
{
return( 0 );
}
}
sub output_destcount
{
my $sub_name = 'output_destcount';
my $_max_output = $_[0] || 20;
my $top_format = $_[1];
my $format = $_[2];
my $conn_sum = 0;
my $cnt = 0;
my $avg = 0;
my $max_hostname_length = 31;
my @results;
my $ret_string;
my @heading_names = ( 'Host', 'IP', 'Bytes', 'Conn. Count' );
if( ! $top_format )
{
$top_format = <<'END'
@|||||||||||||||||||||||||||||| @|||||||||||||| @||||| @|||||||||||
------------------------------- --------------- ------ ------------
END
}
if( ! $format )
{
$format = <<'END'
@>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> @<<<<<<<<<<<<<< @>>>>> @>>>>>>>>>>>
END
}
# Figure out what the average count is
foreach my $count_struc( values( %{$RPT_CACHE->{destcount}} ) )
{
$conn_sum += $count_struc->[0];
++$cnt
}
# If there are no connection counts then bail
if( $cnt < 1 )
{
return( undef );
}
$avg = $conn_sum / $cnt;
# remove anything which is way too small before sorting
my $smallest_count = 2;
my $percent_of_avg = .1;
my $max_sort_size = $_max_output * 2;
while( ( $cnt > $max_sort_size ) and ( $percent_of_avg < .3 ) )
{
while( my( $ip, $struc ) = each( %{$RPT_CACHE->{destcount}} ) and $cnt > $max_sort_size )
{
if( $struc->[0] < $smallest_count )
{
delete( $RPT_CACHE->{destcount}->{$ip} );
--$cnt;
}
$smallest_count = int( $avg * $percent_of_avg );
}
$percent_of_avg += .1;
}
# Put the remaining data into a temp hash for sorting
my %count_hash;
foreach my $ip( keys( %{$RPT_CACHE->{destcount}} ) )
{
# connection count = $RPT_CACHE->{destcount}->{$ip}->{CONN_COUNT};
# byte count = $RPT_CACHE->{destcount}->{$ip}->{BYTE_COUNT};
push( @{$count_hash{$RPT_CACHE->{destcount}->{$ip}->[0]}},
[ $ip, $RPT_CACHE->{destcount}->{$ip}->[0], $RPT_CACHE->{destcount}->{$ip}->[1] ] );
}
my $output_cnt = 0;
foreach my $num_conn( sort { $b <=> $a } keys( %count_hash ) )
{
foreach my $struc( @{$count_hash{$num_conn}} )
{
++$output_cnt;
if( $output_cnt > $_max_output )
{
last;
}
else
{
push( @results, $struc );
}
}
if( $output_cnt > $_max_output )
{
last;
}
}
# clear out memory space
delete( $RPT_CACHE->{destcount} );
# Set the heading
$ret_string .= swrite( $top_format, @heading_names );
# Write the contents
foreach my $line( @results )
{
my $ip = $line->[0];
my $num_conn = $line->[1];
my $num_bytes = trimbytes( $line->[2], 5 );
my $name = trimhostname( iptoname( $ip ), $max_hostname_length, '>' );
$ret_string .= swrite( $format, $name, $ip, $num_bytes, $num_conn );
}
return( $ret_string );
}
sub servicecount
{
my $sub_name = 'servicecount';
# [0] CONN_COUNT
# [1] BYTES_IN
# [2] BYTES_OUT
my $_conn_struc = $_[0] || return( undef );
my $service = Bro::Log::Conn::service( $_conn_struc ) || return( undef );
if( Bro::Log::Conn::connectsucceed( $_conn_struc ) )
{
my $src_bytes = Bro::Log::Conn::source_bytes( $_conn_struc );
my $dest_bytes = Bro::Log::Conn::destination_bytes( $_conn_struc );
++$RPT_CACHE->{$sub_name}->{$service}->[0];
if( Bro::Log::Conn::source_network( $_conn_struc ) eq 'L' )
{
$RPT_CACHE->{$sub_name}->{$service}->[1] += $dest_bytes;
$RPT_CACHE->{$sub_name}->{$service}->[2] += $src_bytes;
}
else
{
$RPT_CACHE->{$sub_name}->{$service}->[1] += $src_bytes;
$RPT_CACHE->{$sub_name}->{$service}->[2] += $dest_bytes;
}
return( 1 );
}
else
{
return( 0 );
}
}
sub output_servicecount
{
my $sub_name = 'output_servicecount';
my $_max_output_count = $_[0] || 20;
my $top_format;
my $format;
my @results;
my @heading_names = ( 'Service', 'Conn. Count', '% of Total', 'Bytes In', 'Bytes Out' );
my $ret_string;
if( ! $top_format )
{
$top_format = <<'END'
@<<<<<<<<<<< @>>>>>>>>>>> @>>>>>>>>> @>>>>>>>> @>>>>>>>>
------------ ------------ ---------- --------- ---------
END
}
if( ! $format )
{
$format = <<'END'
@<<<<<<<<<<< @>>>>>>>>>>> @>>>>>>>>> @>>>>>>>> @>>>>>>>>
END
}
my %count_hash;
my $total_count = 0;
while( my( $name, $struc ) = each( %{$RPT_CACHE->{servicecount}} ) )
{
$total_count += $struc->[0];
push( @{$count_hash{$struc->[0]}},
[ $name, $struc->[1], $struc->[2] ] );
}
my $ret_count = 0;
foreach my $num( sort { $b <=> $a } keys( %count_hash ) )
{
if( $ret_count < $_max_output_count )
{
foreach my $struc( @{$count_hash{$num}} )
{
if( $ret_count < $_max_output_count )
{
my $avg_of_total = sprintf( "%.2f", $num / $total_count * 100 );
my $service = $struc->[0];
my $bytes_in = trimbytes( $struc->[1], 5 );
my $bytes_out = trimbytes( $struc->[2], 5 );
push( @results, [ $service, $num, $avg_of_total, $bytes_in, $bytes_out ] );
++$ret_count;
}
else
{
last;
}
}
}
else
{
last;
}
}
# Clean up some memory
delete( $RPT_CACHE->{servicecount} );
# Print the heading
$ret_string .= swrite( $top_format, @heading_names );
foreach my $line( @results )
{
$ret_string .= swrite( $format, @{$line} );
}
return( $ret_string );
}
sub localserviceusers
{
my $sub_name = 'localserviceusers';
my $_conn_struc = $_[0] || return( undef );
my $service_name = $_[1] || 'smtp';
my $service = Bro::Log::Conn::service( $_conn_struc );
if( $service eq $service_name )
{
my $src_net = Bro::Log::Conn::source_network( $_conn_struc );
if( $src_net eq 'L' and Bro::Log::Conn::connectsucceed( $_conn_struc ) )
{
my $source_ip = Bro::Log::Conn::source_ip( $_conn_struc );
++$RPT_CACHE->{$sub_name}->{$service_name}->{$source_ip};
}
}
return( 1 );
}
sub output_localserviceusers
{
my $sub_name = 'output_localserviceusers';
my $service_name = $_[0] || return( undef );
my $max_count = $_[1] || $MAX_LOCAL_SERVICE_USERS;
my $top_format;
my $format;
my @results;
my $ret_string;
my @heading_names = ( 'Hostname', 'IP', 'Conn. Count' );
my $total_count = keys( %{$RPT_CACHE->{localserviceusers}->{$service_name}} );
my $max_hostname_length = 39;
my $actual_count = 0;
if( ! $top_format )
{
$top_format = <<'END'
@|||||||||||||||||||||||||||||||||||||| @|||||||||||||| @>>>>>>>>>>>
--------------------------------------- --------------- ------------
END
}
if( ! $format )
{
$format = <<'END'
@>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> @<<<<<<<<<<<<<< @>>>>>>>>>>>
END
}
my %count_hash;
while( my( $key, $val ) = each( %{$RPT_CACHE->{localserviceusers}->{$service_name}} ) )
{
push( @{$count_hash{$val}}, $key );
}
foreach my $num( sort { $b <=> $a } keys( %count_hash ) )
{
foreach my $ip( @{$count_hash{$num}} )
{
if( $actual_count + 1 > $max_count )
{
last;
}
$results[$actual_count] = [ $ip, $num ];
++$actual_count;
}
}
# Clean up some memory usage
delete( $RPT_CACHE->{localserviceusers}->{$service_name} );
# Set the heading
$ret_string .= swrite( $top_format, @heading_names );
# Write the contents
foreach my $line( @results )
{
# my $ip = $line->[0];
# my $num_conn = $line->[1];
my $name = trimhostname( iptoname( $line->[0] ), $max_hostname_length, '>' );
$ret_string .= swrite( $format, $name, $line->[0], $line->[1] );
}
if( $actual_count > 0 )
{
if( $total_count > $max_count )
{
my $not_listed = $total_count - $max_count;
$ret_string .= <<"END";
A maximum of $max_count entries are show.
There are another $not_listed that are not displayed.
END
}
}
else
{
$ret_string = "\n No data to report for this section\n";
}
return( $ret_string );
}
sub successfailcount
{
my $sub_name = 'successfailcount';
my $_conn_struc = $_[0] || return( undef );
if( Bro::Log::Conn::connectsucceed( $_conn_struc ) )
{
++$RPT_CACHE->{$sub_name}->{SUCCESS};
}
else
{
# connection is failed
++$RPT_CACHE->{$sub_name}->{FAIL};
}
}
sub output_successfailcount
{
my $sub_name = 'output_successfailcount';
my $format = $_[0];
my $ret_string;
if( ! $format )
{
$format = <<'END'
Successful: @<<<<<<<<<<<<<<<
Unsuccessful: @<<<<<<<<<<<<<<<
Ratio: @<<<<<<
END
}
# Success and fail counts must be greater than zero
if( $RPT_CACHE->{successfailcount}->{FAIL} < 1 or
$RPT_CACHE->{successfailcount}->{SUCCESS} < 1 )
{
return( 'undef' );
}
my $ratio = $RPT_CACHE->{successfailcount}->{FAIL} / $RPT_CACHE->{successfailcount}->{SUCCESS};
$ret_string = swrite( $format,
$RPT_CACHE->{successfailcount}->{SUCCESS},
$RPT_CACHE->{successfailcount}->{FAIL},
"1:$ratio" );
return( $ret_string );
}
sub bytetransferpairs
{
my $sub_name = 'bytetransferpairs';
# This report can be very memory expensive. It can also be very processor
# intesive as the hash tables can get very large and take longer and
# longer to traverse.
my $conn_struc = $_[0] || return( undef );
my $local_host;
my $remote_host;
my $local_bytes;
my $remote_bytes;
if( Bro::Log::Conn::source_network( $conn_struc ) eq 'L' )
{
$local_host = Bro::Log::Conn::source_ip( $conn_struc );
$remote_host = Bro::Log::Conn::destination_ip( $conn_struc );
$local_bytes = Bro::Log::Conn::source_bytes( $conn_struc );
$remote_bytes = Bro::Log::Conn::destination_bytes( $conn_struc );
}
else
{
$remote_host = Bro::Log::Conn::source_ip( $conn_struc );
$local_host = Bro::Log::Conn::destination_ip( $conn_struc );
$remote_bytes = Bro::Log::Conn::source_bytes( $conn_struc );
$local_bytes = Bro::Log::Conn::destination_bytes( $conn_struc );
}
if( $local_bytes > 0 and $remote_bytes > 0 )
{
$RPT_CACHE->{bytetransferpairs}->{$local_host}->{$remote_host}->{LOCAL_BYTES} += $local_bytes;
$RPT_CACHE->{bytetransferpairs}->{$local_host}->{$remote_host}->{REMOTE_BYTES} += $remote_bytes;
++$RPT_CACHE->{bytetransferpairs}->{$local_host}->{$remote_host}->{CONN_COUNT};
return( 1 );
}
elsif( exists( $RPT_CACHE->{bytetransferpairs}->{$local_host} ) and
exists( $RPT_CACHE->{bytetransferpairs}->{$local_host}->{$remote_host} ) )
{
$RPT_CACHE->{bytetransferpairs}->{$local_host}->{$remote_host}->{LOCAL_BYTES} += $local_bytes || 0;
$RPT_CACHE->{bytetransferpairs}->{$local_host}->{$remote_host}->{REMOTE_BYTES} += $remote_bytes || 0;
++$RPT_CACHE->{bytetransferpairs}->{$local_host}->{$remote_host}->{CONN_COUNT};
return( 1 );
}
else
{
return( 0 );
}
}
sub output_bytetransferpairs
{
my $sub_name = 'output_bytetransferpairs';
my $max_hostname_length = 22;
my $max_output = $_[0] || 20;
my $ret_string;
my $_base = $RPT_CACHE->{bytetransferpairs};
my %reversed_hash;
my @ordered_list;
my $top_format;
my $format;
$top_format = <<"END";
Hot Report - Top $max_output
Local Remote Conn.
Local Host Remote Host Bytes Bytes Count
----------------------- ----------------------- --------- --------- -------
END
$format = <<'END';
@<<<<<<<<<<<<<<<<<<<<<< @<<<<<<<<<<<<<<<<<<<<<< @>>>>>>>> @>>>>>>>> @<<<<<<<<
END
foreach my $l_host( keys( %{$_base} ) )
{
foreach my $r_host( keys( %{$_base->{$l_host}} ) )
{
my $big_bytes;
if( $_base->{$l_host}->{$r_host}->{LOCAL_BYTES} > $_base->{$l_host}->{$r_host}->{REMOTE_BYTES} )
{
$big_bytes = $_base->{$l_host}->{$r_host}->{LOCAL_BYTES};
}
else
{
$big_bytes = $_base->{$l_host}->{$r_host}->{REMOTE_BYTES};
}
push( @{$reversed_hash{$big_bytes}}, { REF => $_base->{$l_host}->{$r_host},
LOCAL_HOST => $l_host,
REMOTE_HOST => $r_host, } );
}
}
my @ordered_list = sort( { $b<=>$a } keys( %reversed_hash ) );
my $i = 0;
while( defined( my $key = shift( @ordered_list ) ) and $i < $max_output )
{
foreach my $data( @{$reversed_hash{$key}} )
{
my $local_bytes = trimbytes( $data->{REF}->{LOCAL_BYTES}, 6 );
my $remote_bytes = trimbytes( $data->{REF}->{REMOTE_BYTES}, 6 );
my $conn_count = $data->{REF}->{CONN_COUNT};
my $local_name = trimhostname( iptoname( $data->{LOCAL_HOST} ), $max_hostname_length, '>' );
my $remote_name = trimhostname( iptoname( $data->{REMOTE_HOST} ), $max_hostname_length, '>' );
$ret_string .= swrite( $format,
$local_name,
$remote_name,
$local_bytes,
$remote_bytes,
$conn_count );
++$i;
if( !( $i < $max_output ) )
{
last;
}
}
}
# Free up some memory
$_base = undef;
%reversed_hash = ();
delete( $RPT_CACHE->{bytetransferpairs} );
if( length( $ret_string ) < 32 )
{
$ret_string = $top_format . " No data to report\n";
}
else
{
$ret_string = $top_format . $ret_string . "\n";
}
return( $ret_string );
}
sub output_successcount
{
my $sub_name = 'output_successcount';
my $ret_val = $RPT_CACHE->{successfailcount}->{SUCCESS};
# Clean up some memory
delete( $RPT_CACHE->{successfailcount}->{SUCCESS} );
return( $ret_val );
}
sub output_failcount
{
my $sub_name = 'output_failcount';
my $ret_val = $RPT_CACHE->{successfailcount}->{FAIL};
# Clean up some memory
delete( $RPT_CACHE->{successfailcount}->{FAIL} );
return( $ret_val );
}
sub availablereports
{
my $sub_name = 'availablereports';
my @ret_list = keys( %REPORT_MAP );
return( @ret_list );
}
sub reportinputfunc
{
my $sub_name = 'reportinputfunc';
my $report_name = $_[0] || return( undef );
if( exists( $REPORT_MAP{$report_name} ) )
{
return( $REPORT_MAP{$report_name}->{'input'} );
}
else
{
return( undef );
}
}
sub reportoutputfunc
{
my $sub_name = 'reportoutputfunc';
my $report_name = $_[0] || return( undef );
if( exists( $REPORT_MAP{$report_name} ) )
{
return( $REPORT_MAP{$report_name}->{'output'} );
}
else
{
return( undef );
}
}
1;

File diff suppressed because it is too large Load diff