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

13
scripts/perl/MANIFEST Normal file
View file

@ -0,0 +1,13 @@
lib/Bro/Config.pm
lib/Bro/Log.pm
lib/Bro/Log/Alarm.pm
lib/Bro/Log/Conn.pm
lib/Bro/Report.pm
lib/Bro/Report/Alarm.pm
lib/Bro/Report/Conn.pm
lib/Bro/Signature.pm
Makefile.PL
MANIFEST This list of files
README
script/edit-brorule.pl
script/site-report.pl

230
scripts/perl/Makefile.PL Normal file
View file

@ -0,0 +1,230 @@
require 5.006_001;
use ExtUtils::MakeMaker;
use Cwd;
use strict;
# See lib/ExtUtils/MakeMaker.pm for details of how to influence
# the contents of the Makefile that is written.
my @args = @ARGV;
my @cleaned_args;
my $scripts_dir = './script';
my $scripts_list;
my $brohome = '';
my $broconfig = '';
my %extra_args = ( 'BROHOME' => \$brohome, 'BROCONFIG' => \$broconfig, );
# Look for any extra args that are not recognized by MakeMaker. Use and
# then omit from the array of the final args to pass to MakeMaker.
foreach my $arg( @args )
{
$arg =~ m/^(.+)=(.+)/;
my $key = $1;
my $val = $2;
if( exists( $extra_args{$key} ) )
{
${$extra_args{$key}} = $val;
}
else
{
push( @cleaned_args, $arg );
}
}
# If any extra args that are not recognized by MakeMaker existed they are removed
# by now.
@_ = @cleaned_args;
@ARGV = @cleaned_args;
if( ! $brohome )
{
if( exists( $ENV{BROHOME} ) )
{
$brohome = $ENV{BROHOME};
}
else
{
$brohome = '/usr/local/bro';
}
}
if( ! $broconfig )
{
$broconfig = "$brohome/etc/bro.cfg";
}
check_prereqs();
$scripts_list = get_exe_list();
foreach my $file( @{$scripts_list} )
{
setbroconfig( $broconfig, $file );
}
WriteMakefile(
'NAME' => 'Bro',
'DISTNAME' => 'Bro-Utilities',
'VERSION_FROM' => 'lib/Bro/Config.pm', # finds $VERSION
'PREREQ_PM' => { 'Config::General' => 2.27,
'Time::Local' => 0,
'Getopt::Long' => 0,
'Socket' => 0,
},
'EXE_FILES' => $scripts_list,
'dist' => {
'COMPRESS' => 'gzip',
'SUFFIX' => 'gz'
},
($] >= 5.005 ? ## Add these new keywords supported since 5.005
('AUTHOR' => 'Roger Winslow <rwinslow@lbl.gov>') : ()),
);
sub chk_version
{
no strict qw( refs vars );
my($pkg,$wanted,$msg) = @_;
local($|) = 1;
print "Checking for $pkg...";
eval { my $p; ($p = $pkg . ".pm") =~ s#::#/#g; require $p; };
print ${"${pkg}::VERSION"} ? "found v" . ${"${pkg}::VERSION"}
: "not found";
print "\n";
my $vnum = ${"${pkg}::VERSION"} || 0;
if( $vnum >= $wanted )
{
print "$pkg is installed\n";
return( 1 );
}
else
{
return();
}
use strict;
}
sub check_prereqs
{
my $failed_prereq = 0;
# Require perl version 5.6.1 or greater
eval { require 5.006_001; };
if( $@ )
{
die( "The minimum version of perl required is 5.6.1 (5.006_001). Please use a different perl binary to install this package.\n" );
}
if( chk_version( 'Config::General' => '2.27' ) )
{
# do nothing
}
else
{
my $orig_dir = cwd();
# Bypass the user prompt for this version
# my $confer = prompt( "Config::General is not installed. Would you like to install it now?",
# 'yes' );
my $confer = 'y';
if( $confer =~ m/yes|y/i )
{
chdir 'ext';
unpack_archive( 'Config-General-2.27.tar.gz' );
chdir 'Config-General-2.27';
print "Installing Config-General-2.27.\n";
sleep( 1 );
do 'Makefile.PL';
if( system( "make; make install" ) == 0 )
{
print "\n ........... done\n";
}
else
{
warn( "Failed to install perl package Config-General-2.27.\n" );
}
chdir "$orig_dir";
}
}
if( $failed_prereq )
{
warn( "Failed one or more prerequisite test, unable to continue.\n" );
exit( 1 );
}
print "\n";
}
sub unpack_archive
{
my $_archive = shift || return( undef );
system( "gzip -d < $_archive | tar xf -" );
}
sub get_exe_list
{
my @ret_list;
if( ! opendir( DIR, $scripts_dir ) )
{
warn( "Failed to open the scripts directory at $scripts_dir. Unable to continue.\n" );
exit( 1 );
}
while( my $file = readdir( DIR ) )
{
if( $file !~ m/^\./ and $file !~ m/^makefile.*/i and
-f "$scripts_dir/$file" )
{
push( @ret_list, "$scripts_dir/$file" );
}
}
closedir( DIR );
return( \@ret_list );
}
sub setbroconfig
{
my $sub_name = 'setbroconfig';
my $_broconfig = shift || return( undef );
my $_file = shift || return( undef );
if( ! open( INFILE, $_file ) )
{
warn( "$sub_name, Failed to open file $_file for reading.\n" );
return( undef );
}
if( ! open( OUTFILE, ">$_file.in" ) )
{
warn( "$sub_name, Failed to open file $_file.in for writing.\n" );
return( undef );
}
while( defined( my $line = <INFILE> ) )
{
$line =~ s/^([[:space:]]*\$DEFAULT_BRO_CONFIG_FILE[[:space:]]*=[[:space:]]*).+(\;.*)$/$1\'$_broconfig\'$2/;
$line =~ s/\$DEFAULT_BRO_HOME/$brohome/;
print OUTFILE $line;
}
close( OUTFILE );
close( INFILE );
system( "mv -f $_file.in $_file" );
return( 1 );
}

64
scripts/perl/README Normal file
View file

@ -0,0 +1,64 @@
This follows the same mantra as all other perl installers.
PURPOSE:
This will install perl modules, libraries, and scripts that are used
for reports, editing signatures, and other useful utilities.
DEFINITIONS:
$(PERL) is the path to the perl binary which you wish to use.
$(INSTALL_ROOT) is this directory which contains the Makefile.PL file.
BROHOME is the variable found in bro.cfg and defines the start of all
things Bro. (default: /usr/local/bro)
BROCONFIG is the location of the bro.cfg file. (default:
/usr/local/bro/etc/bro/cfg)
REQUIREMENTS:
The minimum version of perl required by this installer and it's libraries
is 5.6.1 (5.006_001)
The following perl modules are required:
Socket
Time::Local
Config::General (included and will install if neccessary)
Cwd
Getopt::Long
INSTALL:
$(PERL) Makefile.PL (optional args)
make
make install
INSTALLER NOTES:
For those of you maintaining this installer and/or want to include
additional packages to be installed here's how things are setup.
$(INSTALL_ROOT)/lib contains perl modules (ending in .pm) and will be
installed in the perl site directory.
$(INSTALL_ROOT)/script contains executable perl scripts which will be
installed in the directory defined by INSTALLSCRIPT. The bang paths
will be automatically changed to the path of the perl binary that was
used to run Makefile.PL. Files placed in here will also be scanned
for the variable $DEFAULT_BRO_CONFIG_FILE. The value will automatically
be changed to one of the following in the order listed:
arguments passed to Makfile.PL:
BROCONFIG (this is the path to bro.cfg)
BROHOME (this is the path to BROHOME. etc/bro.cfg will be appended)
Environment variable:
$BROHOME (this is the path to BROHOME. etc/bro.cfg will be appended)
$(INSTALL_ROOT)/ext contains gzipped perl modules which are included
as a convenience. These are packages created by other developers and
are usually found on cpan.org. It will be necessary to change Makefile.PL
if additional packages are placed in here and they need to be installed.

Binary file not shown.

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

File diff suppressed because it is too large Load diff

1367
scripts/perl/script/site-report.pl Executable file

File diff suppressed because it is too large Load diff