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 => \×tamp, timestamp => \×tamp, notice => \¬ice_type, notice_type => \¬ice_type, notice_act => \¬ice_action, notice_action => \¬ice_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 = ) 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 ); }