All the files
This commit is contained in:
51
bin/parserfilter
Executable file
51
bin/parserfilter
Executable file
@ -0,0 +1,51 @@
|
||||
#!/usr/bin/env perl
|
||||
use strict;
|
||||
use warnings;
|
||||
use diagnostics;
|
||||
use sigtrap qw/handler signal_handler normal-signals/;
|
||||
use Tie::Syslog;
|
||||
use My::Savepid qw(savepid);
|
||||
use My::parser::parser;
|
||||
use Glib;
|
||||
|
||||
my $config_file = '/usr/local/etc/parserfilter.conf';
|
||||
my $pidfile = '/var/run/parserfilter.pid';
|
||||
my $program = $0;
|
||||
|
||||
unless(-f $config_file) {
|
||||
die "Configuration file $config_file does not exist";
|
||||
}
|
||||
|
||||
my $x;
|
||||
my $loop = Glib::MainLoop->new;
|
||||
my $parser = My::parser::parser->new($config_file);
|
||||
my $time = Glib::Timeout->add(1000, \&loopsie);
|
||||
|
||||
if(my $tp = fork) {
|
||||
exit 0;
|
||||
} else {
|
||||
my $pid = $$;
|
||||
$x = tie *STDERR, 'Tie::Syslog', 'local0.err',$program,'pid','unix';
|
||||
$x->ExtendedSTDERR();
|
||||
&savepid($pid,$pidfile);
|
||||
close STDIN;
|
||||
$parser->load_parsers;
|
||||
$loop->run;
|
||||
}
|
||||
|
||||
sub loopsie {
|
||||
my $result = $parser->parse_all;
|
||||
return 1 if($result);
|
||||
return 0;
|
||||
}
|
||||
|
||||
sub signal_handler {
|
||||
my $signal = shift;
|
||||
if($signal eq 'HUP') {
|
||||
$parser->{'config'}->{'logger'}->reload_log;
|
||||
$parser->{'config'}->{'logger'}->log('Log reloaded');
|
||||
} else {
|
||||
$loop->quit;
|
||||
$parser->{'config'}->{'logger'}->log("$program stopped");
|
||||
}
|
||||
}
|
||||
46
bin/parserfilter-tester
Executable file
46
bin/parserfilter-tester
Executable file
@ -0,0 +1,46 @@
|
||||
#!/usr/bin/env perl
|
||||
use strict;
|
||||
use warnings;
|
||||
use Getopt::Long;
|
||||
use My::parser::stats;
|
||||
my $module;
|
||||
my @modules = ('ssh','dovecot','exim','apache','gitea_ssh');
|
||||
my $program = $0;
|
||||
GetOptions ("module=s" => \$module); #Only test one module
|
||||
unless($module) {
|
||||
print 'No module specified, use argument --module=(ssh|dovecot|exim|apache|gitea_ssh)'."\n";
|
||||
exit;
|
||||
}
|
||||
my @matches = grep { /$module/ } @modules;
|
||||
unless(@matches) {
|
||||
print 'Unsupported module '.$module.' specified, use argument --module=(ssh|dovecot|exim|apache|gitea_ssh)'."\n";
|
||||
exit;
|
||||
}
|
||||
print "Please paste a line to parse here:\n";
|
||||
my $frompipe = <STDIN>;
|
||||
chomp($frompipe); #One line is fine for us, user may fuck up, but we're good..
|
||||
my $parser = &load($module);
|
||||
my $result = $parser->parser($frompipe);
|
||||
if($result->{'retval'}) {
|
||||
print 'Parser said: '.$result->{'retmsg'}."\n";
|
||||
print 'Regarded as a hostile action'."\n" if($result->{'hostile'});
|
||||
print 'Host: '.$result->{'host'}."\n";
|
||||
} else {
|
||||
print 'Parser found no match'."\n";
|
||||
}
|
||||
|
||||
sub load {
|
||||
my $parser = shift;
|
||||
my $filename = 'My/parser/'.$parser.'_parser.pm';
|
||||
my $newclass;
|
||||
eval {
|
||||
require $filename;
|
||||
my $classname = 'My::parser::'.$parser.'_parser';
|
||||
$newclass = $classname->new || die "Failed to load parser for $parser";
|
||||
} or do {
|
||||
my $e = $@;
|
||||
print 'Could not load parser '.$module.' from file '.$filename.': '.$e."\n";
|
||||
exit;
|
||||
};
|
||||
return $newclass;
|
||||
}
|
||||
65
lib/addtolist.pm
Normal file
65
lib/addtolist.pm
Normal file
@ -0,0 +1,65 @@
|
||||
package My::parser::addtolist;
|
||||
use strict;
|
||||
use warnings;
|
||||
use DBI;
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $config = shift;
|
||||
my $self = {};
|
||||
bless ($self, $class);
|
||||
$self->{'config'} = $config;
|
||||
$self->{'block_table'} = 'badhosts';
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub add {
|
||||
my $self = shift;
|
||||
my $params = shift;
|
||||
die "No params" unless $params;
|
||||
$self->{'dbh'} = $self->{'config'}->get_dbh unless($self->{'dbh'});
|
||||
my $return = {};
|
||||
$return = $self->addtoblacklist($params) if($params->{'list'} eq 'black');
|
||||
$return = $self->addtorejectlist($params) if($params->{'list'} eq 'reject');
|
||||
return $return;
|
||||
}
|
||||
|
||||
sub addtoblacklist {
|
||||
my $self = shift;
|
||||
my $work = shift;
|
||||
my $host = $work->{'host'};
|
||||
my $asn = $work->{'asn'} || 0;
|
||||
my $iso = $work->{'iso'} || '';
|
||||
return { retval => 0, retmsg => "No host specified" } unless($host);
|
||||
my $sth = $self->{'dbh'}->prepare("
|
||||
SELECT COUNT(*)
|
||||
FROM reject_iptables
|
||||
WHERE ip = ? AND
|
||||
time > DATE_SUB(NOW(), INTERVAL 5 MINUTE)");
|
||||
$sth->execute($host);
|
||||
my $rows = $sth->fetchrow_arrayref->[0];
|
||||
unless($rows) {
|
||||
my $sth = $self->{'dbh'}->prepare("INSERT INTO reject_iptables(ip,asn,iso) VALUES(?,?,?)") || return { retval => 0, retmsg => 'Failed to prepare statement in addtoblacklist '.DBI::errstr };
|
||||
$sth->execute($host,$asn,$iso) || return { retval => 0, retmsg => 'Failed to execute statement in addtoblacklist '.DBI::errstr };
|
||||
system "/sbin/pfctl -q -t $self->{'block_table'} -T add $host";
|
||||
system "/sbin/pfctl -q -k $host";
|
||||
return { retval => 1, retmsg => "Added pf rule" };
|
||||
} else {
|
||||
return { retval => 1, retmsg => "Recently got $rows entries" };
|
||||
}
|
||||
}
|
||||
|
||||
sub addtorejectlist {
|
||||
my $self = shift;
|
||||
my $work = shift;
|
||||
my $host = $work->{'host'};
|
||||
my $service = $work->{'service'} || '';
|
||||
my $asn = $work->{'asn'} || 0;
|
||||
my $iso = $work->{'iso'} || '';
|
||||
return { retval => 0, retmsg => "No host specified" } unless($host);
|
||||
my $sth = $self->{'dbh'}->prepare("INSERT INTO reject(ip,service,asn,iso) VALUES(?,?,?,?)") || return { retval => 0, retmsg => 'Failed to prepare statement in addtorejectlist '.DBI::errstr };
|
||||
$sth->execute($host,$service,$asn,$iso) || return { retval => 0, retmsg => 'Failed to executute statement in addtorejectlist '.DBI::errstr };
|
||||
return { retval => 1, retmsg => "Added to reject list" };
|
||||
}
|
||||
|
||||
1;
|
||||
45
lib/apache.pm
Normal file
45
lib/apache.pm
Normal file
@ -0,0 +1,45 @@
|
||||
package My::parser::apache;
|
||||
use strict;
|
||||
use warnings;
|
||||
use File::Tail 0.91;
|
||||
use My::parser::apache_parser;
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $config = shift;
|
||||
my $self = {};
|
||||
bless ($self, $class);
|
||||
|
||||
$self->{'config'} = $config;
|
||||
$self->{'parser'} = My::parser::apache_parser->new();
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub parse {
|
||||
my $self = shift;
|
||||
my @result;
|
||||
while(my $string = $self->fetch) {
|
||||
last unless($string);
|
||||
if (my $line = $self->{'parser'}->parser($string)) {
|
||||
push(@result,$line);
|
||||
}
|
||||
}
|
||||
return { retval => 0 } unless(scalar(@result)); # nothing to say, nothing to report
|
||||
return { retval => 1, retmsg => 'Here comes the results', lines => \@result };
|
||||
}
|
||||
|
||||
sub fetch {
|
||||
my $self = shift;
|
||||
my $fetcher = $self->{'config'}->get_fetcher('apache');
|
||||
die "Fetcher for apache went away?" unless($fetcher);
|
||||
my $line;
|
||||
my ($nfound,$timeleft,@pending) = File::Tail::select(undef,undef,undef,1,$fetcher);
|
||||
foreach (@pending) {
|
||||
$line = $_->read;
|
||||
chomp($line);
|
||||
}
|
||||
return 0 unless($line);
|
||||
return $line;
|
||||
}
|
||||
|
||||
1;
|
||||
81
lib/apache_parser.pm
Normal file
81
lib/apache_parser.pm
Normal file
@ -0,0 +1,81 @@
|
||||
package My::parser::apache_parser;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $self = {};
|
||||
bless ($self, $class);
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub parser {
|
||||
my $self = shift;
|
||||
my $string = shift;
|
||||
my ($reply,$hostile,$host) = ("No match for $string",0,'');
|
||||
my $re_host = qr/[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/;
|
||||
if($string =~ m/proxy_fcgi:error.*Got error 'Primary script unknown'/) {
|
||||
$_ = $string;
|
||||
$reply = 'Request for unknown fcgi-script';
|
||||
$hostile = 1;
|
||||
PARSE:
|
||||
m/(\[client\ ($re_host)\:0\])/gcix && do {
|
||||
$host = $2;
|
||||
};
|
||||
} elsif($string =~ m/script not found or unable to stat/) {
|
||||
$_ = $string;
|
||||
$reply = 'Script not found';
|
||||
$hostile = 1;
|
||||
PARSE:
|
||||
m/(\[client\ ($re_host)\:0\])/gcix && do {
|
||||
$host = $2;
|
||||
};
|
||||
} elsif($string =~ m/ap_pass_brigade failed in handle_request_ipc function/) {
|
||||
$_ = $string;
|
||||
$hostile = 1;
|
||||
$reply = 'Connection closed early';
|
||||
PARSE:
|
||||
m/(\[client\ ($re_host)\:0\])/gcix && do {
|
||||
$host = $2;
|
||||
};
|
||||
} elsif($string =~ m/Invalid URI in request/) {
|
||||
$_ = $string;
|
||||
$hostile = 1;
|
||||
$reply = 'Invalid URI';
|
||||
PARSE:
|
||||
m/(\[client\ ($re_host)\:0\])/gcix && do {
|
||||
$host = $2;
|
||||
};
|
||||
} elsif($string =~ m/invalid URI path /) {
|
||||
$_ = $string;
|
||||
$hostile = 1;
|
||||
$reply = 'Invalid URI';
|
||||
PARSE:
|
||||
m/(\[client\ ($re_host)\:0\])/gcix && do {
|
||||
$host = $2;
|
||||
};
|
||||
} elsif($string =~ m/\(63\)File name too long: /) {
|
||||
$_ = $string;
|
||||
$hostile = 1;
|
||||
$reply = 'File name too long';
|
||||
PARSE:
|
||||
m/(\[client\ ($re_host)\:0\])/gcix && do {
|
||||
$host = $2;
|
||||
};
|
||||
} elsif($string =~ m/ AH00135: Invalid method in request /) {
|
||||
$_ = $string;
|
||||
$hostile = 1;
|
||||
$reply = 'Invalid request method';
|
||||
PARSE:
|
||||
m/(\[client\ ($re_host)\:0\])/gcix && do {
|
||||
$host = $2;
|
||||
};
|
||||
} elsif($string =~ m/mod_fcgid: cleanup zombie process/) {
|
||||
$reply = 'fcgi process killed';
|
||||
} elsif($string =~ m/scoreboard already in used/) {
|
||||
$reply = 'Scoreboard already in use';
|
||||
}
|
||||
return { retval => 1, retmsg => $reply, hostile => $hostile, host => $host, string => $string };
|
||||
}
|
||||
|
||||
return 1;
|
||||
78
lib/block.pm
Normal file
78
lib/block.pm
Normal file
@ -0,0 +1,78 @@
|
||||
package My::parser::block;
|
||||
use strict;
|
||||
use warnings;
|
||||
use My::parser::addtolist;
|
||||
use My::parser::stats;
|
||||
use POSIX qw(ceil floor);
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $self = {};
|
||||
bless ($self, $class);
|
||||
$self->{'config'} = shift;
|
||||
$self->{'addtolist'} = My::parser::addtolist->new($self->{'config'});
|
||||
$self->{'stats'} = My::parser::stats->new($self->{'config'});
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub blocklogic {
|
||||
my $self = shift;
|
||||
my $result = shift;
|
||||
return { retval => 0, retmsg => 'Too few arguments to blocklogic' } unless($result);
|
||||
my $host = $result->{'host'};
|
||||
my $service = $result->{'service'};
|
||||
my $geoip = $result->{'geoip'};
|
||||
my $short = $self->{'config'}->get_as_single_val('config','short');
|
||||
my $long = $self->{'config'}->get_as_single_val('config','long');
|
||||
my $retstr;
|
||||
my $stats;
|
||||
my $logline;
|
||||
my $modifier = 0;
|
||||
|
||||
my $fromreject = $self->{'addtolist'}->add( { list => 'reject', host => $host, service => $service, asn => $geoip->{'asn'}, iso => $geoip->{'iso'} } );
|
||||
return { retval => 0, hostile => 0, retmsg => $fromreject->{'retmsg'} } unless($fromreject->{'retval'});
|
||||
if ($stats = $self->{'stats'}->checker({ host => $host, asn => $geoip->{'asn'}, iso => $geoip->{'iso'} })) {
|
||||
$self->{'config'}->{'logger'}->log($stats->{'retmsg'}) unless($stats->{'retval'});
|
||||
my $hrs = $stats->{$short}->{'reject'} || 0;
|
||||
my $ars = $stats->{$short}->{'asn'} || 0;
|
||||
my $irs = $stats->{$short}->{'iso'} || 0;
|
||||
my $hbs = $stats->{$short}->{'blocks'} || 0;
|
||||
my $abs = $stats->{$short}->{'block_asn'} || 0;
|
||||
my $ibs = $stats->{$short}->{'block_iso'} || 0;
|
||||
my $hrl = $stats->{$long}->{'reject'} || 0;
|
||||
my $arl = $stats->{$long}->{'asn'} || 0;
|
||||
my $irl = $stats->{$long}->{'iso'} || 0;
|
||||
my $hbl = $stats->{$long}->{'blocks'} || 0;
|
||||
my $abl = $stats->{$long}->{'block_asn'} || 0;
|
||||
my $ibl = $stats->{$long}->{'block_iso'} || 0;
|
||||
my $hostile = 0;
|
||||
my $shortrejectpoints = ($hrs * 100) + ($ars * 50) + ($irs * 25);
|
||||
my $shortblockpoints = ($hbs * 750) + ($abs * 250) + ($ibs * 50);
|
||||
my $longrejectpoints = ($hrl * 30) + ($arl * 10) + ($irl);
|
||||
my $longblockpoints = ($hbl * 150) + ($abl * 75) + ($ibl * 10);
|
||||
my $points = $shortrejectpoints + $shortblockpoints + $longrejectpoints + $longblockpoints;
|
||||
if (my $recent_hostile = $self->{'stats'}->recent_hostile()) {
|
||||
unless($recent_hostile->{'retval'}) {
|
||||
$self->{'config'}->{'logger'}->log($recent_hostile->{'retmsg'})
|
||||
} else {
|
||||
my $rh = $recent_hostile->{'rows'} - 1;
|
||||
if($rh > 0) {
|
||||
$modifier = 1 + ($rh / 5);
|
||||
$points = floor($points * $modifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
$hostile++ if($points > 999);
|
||||
|
||||
my $fromblack = $self->{'addtolist'}->add( { list => 'black', host => $host, asn => $geoip->{'asn'}, iso => $geoip->{'iso'} } ) if($hostile);
|
||||
$self->{'config'}->{'logger'}->log($fromblack->{'retmsg'}) unless($fromblack->{'retval'});
|
||||
$logline .= $fromblack->{'retmsg'}.', ' if($fromblack->{'retmsg'});
|
||||
$logline .= "Points: $points";
|
||||
$logline .= '(mod='.$modifier.')' if($modifier);
|
||||
return { retval => 1, hostile => $hostile, retmsg => $logline };
|
||||
} else {
|
||||
return { retval => 0, hostile => 0, retmsg => 'Blocklogic failed, no idea why:)' };
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
||||
209
lib/config.pm
Normal file
209
lib/config.pm
Normal file
@ -0,0 +1,209 @@
|
||||
package My::parser::config;
|
||||
use strict;
|
||||
use warnings;
|
||||
use Getopt::Long;
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $file = shift;
|
||||
|
||||
my $self = {};
|
||||
bless ($self, $class);
|
||||
|
||||
my $test = 0;
|
||||
my $parse;
|
||||
|
||||
GetOptions(
|
||||
"cf=s" => \$file, #string, config file from options
|
||||
"test=i" => \$test, #integer, test mode (no writing to db, no blacklisting)
|
||||
"parse=s" => \$parse, #string, parser testing. Parse only the submitted line, test=1 needed
|
||||
);
|
||||
$self->{'file'} = $file;
|
||||
$self->parse if($self->load);
|
||||
$self->{'config'}->{'test'} = $test;
|
||||
$self->{'config'}->{'parse'} = $parse;
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub load {
|
||||
my $self = shift;
|
||||
my $file = $self->{'file'};
|
||||
|
||||
open(FH, "<", "$file") || die "Could not open $file for reading: ".$!;
|
||||
while(<FH>) {
|
||||
my $line = $_;
|
||||
chomp($line);
|
||||
next unless($line =~ m/\S/);
|
||||
push(@{$self->{'conf_file'}},$line) unless($line =~ m/^\#/);
|
||||
}
|
||||
close(FH);
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub parse {
|
||||
my $self = shift;
|
||||
foreach(@{$self->{'conf_file'}}) {
|
||||
my ($opt,$arg) = split("=");
|
||||
$opt =~ s/^\s+//;
|
||||
$opt =~ s/\s+$//;
|
||||
$arg =~ s/^\s+//;
|
||||
$arg =~ s/\s+$//;
|
||||
my @argarr = split("','",$arg);
|
||||
foreach my $argie(@argarr) {
|
||||
$argie =~ s/^\'//;
|
||||
$argie =~ s/\'$//;
|
||||
push(@{$self->{'config'}->{$opt}},$argie);
|
||||
}
|
||||
}
|
||||
delete $self->{'conf_file'};
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub get_modules {
|
||||
my $self = shift;
|
||||
my $type = $self->{'config'}->{'modules'};
|
||||
return $type;
|
||||
}
|
||||
|
||||
sub get_parsers {
|
||||
my $self = shift;
|
||||
my $return = $self->{'parsers'};
|
||||
return $return;
|
||||
}
|
||||
|
||||
sub get_fetchers {
|
||||
my $self = shift;
|
||||
my $return = $self->{'fetchers'};
|
||||
return $return;
|
||||
}
|
||||
|
||||
sub get_parser_info {
|
||||
my $self = shift;
|
||||
my $m = shift;
|
||||
my $toret = {};
|
||||
if(defined($self->{'config'}->{$m})) {
|
||||
for(my $i = 0; $i < @{$self->{'config'}->{$m}}; $i++) {
|
||||
my $val;
|
||||
$val = 'type' if($i == 0);
|
||||
$val = 'source' if($i == 1);
|
||||
$val = 'program' if($i == 2);
|
||||
$toret->{$val} = @{$self->{'config'}->{$m}}[$i]
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
return $toret;
|
||||
}
|
||||
|
||||
sub get_fetcher {
|
||||
my $self = shift;
|
||||
my $m = shift;
|
||||
my $toret;
|
||||
if(defined($self->get_parser_info($m))) {
|
||||
my $type = $self->get_parser_info($m)->{'type'};
|
||||
if ($toret = $self->{'config'}->{'fetchers'}->{$type}->{$m}) {
|
||||
return $toret;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
sub get_fetcher_module {
|
||||
my $self = shift;
|
||||
my $m = shift;
|
||||
my $toret;
|
||||
if(defined($self->{'fetcher'}->{$m})) {
|
||||
my $toret = $self->{'fetcher'}->{$m};
|
||||
return $toret;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
sub get_dbh {
|
||||
my $self = shift;
|
||||
my $dbh = $self->{'config'}->{'fetchers'}->{'db'}->{'dbh'};
|
||||
return $dbh;
|
||||
}
|
||||
|
||||
sub set_dbh {
|
||||
my $self = shift;
|
||||
my $dbh = shift;
|
||||
$self->{'config'}->{'fetchers'}->{'db'}->{'dbh'} = $dbh;
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub set_fetcher {
|
||||
my $self = shift;
|
||||
my $type = shift; ## Type of source
|
||||
my $m = shift; ## m for module? name of service
|
||||
my $fetcher = shift; ## hash for reaching fetcher
|
||||
if($type && $m) {
|
||||
$self->{'config'}->{'fetchers'}->{$type}->{$m} = $fetcher;
|
||||
#print localtime(time).": Fetcher set to $type($fetcher) for service $m\n"; FIXME add debug option in config?
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
sub set {
|
||||
my $self = shift;
|
||||
my $node = shift || undef;
|
||||
my $key = shift || undef;
|
||||
my $value = shift || undef;
|
||||
if(defined($node) && defined($key) && defined($value)) {
|
||||
$self->{$node}->{$key} = $value;
|
||||
return 1;
|
||||
} elsif (defined($node) && defined($key)) {
|
||||
$self->{$node} = $key;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
sub get {
|
||||
my $self = shift;
|
||||
my $node = shift || undef;
|
||||
my $key = shift || undef;
|
||||
if(defined($node) && defined($key)) {
|
||||
my $toret = $self->{$node}->{$key};
|
||||
return $toret;
|
||||
} elsif(defined($node)) {
|
||||
my $toret = $self->{$node};
|
||||
return $toret;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
sub get_as_single_val {
|
||||
my $self = shift;
|
||||
my $node = shift || undef;
|
||||
my $key = shift || undef;
|
||||
my $toret;
|
||||
if(defined($node) && defined($key)) {
|
||||
$toret = exists $self->{$node}->{$key} ? $self->{$node}->{$key} : undef;
|
||||
} elsif(defined($node)) {
|
||||
$toret = exists $self->{$key} ? $self->{$key} : undef;
|
||||
}
|
||||
if($toret) {
|
||||
if(scalar(@{$toret})) {
|
||||
$toret = join('',@{$toret});
|
||||
}
|
||||
return $toret;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
sub print {
|
||||
my $self = shift;
|
||||
foreach my $key(keys %{$self->{'config'}}) {
|
||||
$self->{'config'}->{'logger'}->log("$key: ");
|
||||
my $tolog;
|
||||
foreach my $arg(@{$self->{'config'}->{$key}}) {
|
||||
$tolog .= "$arg ";
|
||||
}
|
||||
$self->{'config'}->{'logger'}->log($tolog);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
1;
|
||||
48
lib/db.pm
Normal file
48
lib/db.pm
Normal file
@ -0,0 +1,48 @@
|
||||
package My::parser::db;
|
||||
use strict;
|
||||
use warnings;
|
||||
use DBI;
|
||||
#use Scalar::Util qw(weaken);
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $config = shift;
|
||||
my $rest = shift;
|
||||
return \$class if($rest);
|
||||
my $self = {};
|
||||
bless ($self, $class);
|
||||
|
||||
$self->{'config'} = $config;
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub init {
|
||||
my $self = shift;
|
||||
my $parser = shift; ## not needed for this module as the db-connection is for all modules
|
||||
$self->connect || die "Could not connect to db";
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub connect {
|
||||
my $self = shift;
|
||||
my $config = $self->{'config'};
|
||||
my $usr = $config->get_as_single_val('config','dbusr');
|
||||
my $pwd = $config->get_as_single_val('config','dbpwd');
|
||||
my $host = $config->get_as_single_val('config','dbhost');
|
||||
my $db = $config->get_as_single_val('config','db');
|
||||
my $dbh;
|
||||
if($usr && $pwd && $host && $db) {
|
||||
while(1) {
|
||||
last if($dbh = DBI->connect("DBI:mysql:database=$db;host=$host",$usr,$pwd, { PrintError => 1, mysql_auto_reconnect=>1, AutoCommit => 1 }));
|
||||
sleep(10);
|
||||
}
|
||||
$config->set_dbh($dbh);
|
||||
#$self->{'config'}->{'logger'}->log("Sucsessfully connected to db"); FIXME add debug to config?
|
||||
return 1;
|
||||
} else {
|
||||
$self->{'config'}->{'logger'}->log("Unable to connect to db, not enough parameters");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
||||
43
lib/dovecot.pm
Normal file
43
lib/dovecot.pm
Normal file
@ -0,0 +1,43 @@
|
||||
package My::parser::dovecot;
|
||||
use strict;
|
||||
use warnings;
|
||||
use My::parser::dovecot_parser;
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $config = shift;
|
||||
my $self = {};
|
||||
bless ($self, $class);
|
||||
|
||||
$self->{'config'} = $config;
|
||||
$self->{'parser'} = My::parser::dovecot_parser->new();
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub parse {
|
||||
my $self = shift;
|
||||
my @result;
|
||||
while(my $string = $self->fetch) {
|
||||
last unless($string);
|
||||
if (my $line = $self->{'parser'}->parser($string)) {
|
||||
push(@result,$line);
|
||||
}
|
||||
}
|
||||
return { retval => 0 } unless(scalar(@result));
|
||||
return { retval => 1, retmsg => 'Here comes the results', lines => \@result };
|
||||
}
|
||||
|
||||
sub fetch {
|
||||
my $self = shift;
|
||||
my $fetcher = $self->{'config'}->get_fetcher('dovecot');
|
||||
my $line;
|
||||
my ($nfound,$timeleft,@pending) = File::Tail::select(undef,undef,undef,1,$fetcher);
|
||||
foreach (@pending) {
|
||||
$line = $_->read;
|
||||
chomp($line);
|
||||
}
|
||||
return 0 unless($line);
|
||||
return $line;
|
||||
}
|
||||
|
||||
1;
|
||||
113
lib/dovecot_parser.pm
Normal file
113
lib/dovecot_parser.pm
Normal file
@ -0,0 +1,113 @@
|
||||
package My::parser::dovecot_parser;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $self = {};
|
||||
bless ($self, $class);
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub parser {
|
||||
my $self = shift;
|
||||
my $string = shift;
|
||||
my ($reply,$hostile,$host) = ("No match for $string",0,'');
|
||||
my $re_host = qr/[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/;
|
||||
my $re_usr = qr/[a-zA-Z0-9_-]*/;
|
||||
my $re_pid = qr/[0-9]*/;
|
||||
my $re_uid = qr/[a-zA-Z0-9_\-+:\/]*/;
|
||||
my $re_info = qr/\($re_usr\)\<$re_pid\>\<$re_uid\>/;
|
||||
if($string =~ m/ Info: Disconnected: Logged out /) {
|
||||
$reply = 'Normal logout';
|
||||
} elsif($string =~ m/ imap-login: (Info: |)Login: /) {
|
||||
$reply = 'Normal login';
|
||||
} elsif($string =~ m/ indexer-worker$re_info: Info: /) {
|
||||
$reply = 'Indexer worker message';
|
||||
} elsif($string =~ m/ Disconnected (in IDLE|for inactivity) /) {
|
||||
$reply = 'Idle disconnect';
|
||||
} elsif($string =~ m/imap($re_info): (Info: |Disconnected: |)Connection closed/) {
|
||||
$reply = 'Normal connection closed';
|
||||
} elsif($string =~ m/ imap$re_info: (Info: |)Disconnected: Logged out /) {
|
||||
$reply = 'Normal log out';
|
||||
} elsif($string =~ m/ master: Warning: Time moved /) {
|
||||
$reply = 'Clock adjustment';
|
||||
} elsif($string =~ m/auth failed/) {
|
||||
$_ = $string;
|
||||
$reply = 'Auth failure';
|
||||
$hostile = 1;
|
||||
PARSER:
|
||||
m/ (rip=($re_host)) /gcix && do {
|
||||
$host = $2;
|
||||
};
|
||||
} elsif($string =~ m/(Authentication error (\(Password mismatch\?\)|unknown user))/) {
|
||||
$_ = $string;
|
||||
$reply = 'Unknown user or auth error';
|
||||
$hostile = 1;
|
||||
PARSER:
|
||||
m/(([a-zA-Z0-9@._-]*),($re_host),.*\): pam_authenticate\(\) failed: (Authentication error \(Password mismatch\?\)|unknown user))/gi && do {
|
||||
$host = $3;
|
||||
};
|
||||
} elsif($string =~ m/unknown user/) {
|
||||
$_ = $string;
|
||||
$reply = 'Unknown user';
|
||||
$hostile = 1;
|
||||
PARSER:
|
||||
m/(((Info: |)conn unix:auth-worker \(uid=([0-9]{1,9})\): auth-worker<([0-9]{1,9}>: pam\(([a-zA-Z0-9@._-]*),($re_host),<($re_uid)>\): unknown user)))/gi && do {
|
||||
$host = $7;
|
||||
};
|
||||
} elsif($string =~ m/ imap-login: (Info: |)Disconnected/) {
|
||||
if($string =~ m/Connection closed/) {
|
||||
$hostile = 0;
|
||||
$reply = 'Disconnecting is legit';
|
||||
} elsif($string =~ m/ TLS handshaking: /) {
|
||||
$reply = 'TLS error';
|
||||
$hostile = 1;
|
||||
} elsif($string =~ m/Too many invalid commands/) {
|
||||
$reply = 'Too many invalid commands';
|
||||
$hostile = 1;
|
||||
} elsif($string =~ m/TLS: Disconnected,/) {
|
||||
$reply = 'Likely a sleeping android';
|
||||
$hostile = 0;
|
||||
} elsif($string =~ m/client didn't finish SASL auth/) {
|
||||
$reply = 'Timeout waiting for SASL auth';
|
||||
$hostile = 1;
|
||||
} elsif($string =~ m/no auth attempts in/) {
|
||||
if($string =~ m/, secured/) {
|
||||
$reply = 'Secured Disconnect during auth, either sleeping phone or attack on webmail';
|
||||
$hostile = 0;
|
||||
} else {
|
||||
$reply = 'Non-secure disconnect during auth';
|
||||
$hostile = 1;
|
||||
}
|
||||
}
|
||||
if($hostile) {
|
||||
$_ = $string;
|
||||
PARSER:
|
||||
m/\ (rip=($re_host))/gcix && do {
|
||||
$host = $2;
|
||||
};
|
||||
}
|
||||
} elsif($string =~ m/ imap-login: (Info: |) Aborted login /) {
|
||||
$_ = $string;
|
||||
$reply = 'Aborted login';
|
||||
$hostile = 1;
|
||||
PARSER:
|
||||
m/ (rip=($re_host)) /gcix && do {
|
||||
$host = $2;
|
||||
};
|
||||
} elsif($string =~ m/unknown user$/) {
|
||||
$_ = $string;
|
||||
$hostile = 1;
|
||||
$reply = 'Unknown user';
|
||||
PARSER:
|
||||
m/Info: pam\([a-zA-Z0-9@._-]*,($re_host),\<.*\>\): unknown user/gi && do {
|
||||
$host = $1;
|
||||
};
|
||||
} else {
|
||||
$reply = 'No match for '.$string;
|
||||
}
|
||||
return { retval => 1, retmsg => $reply, hostile => $hostile, host => $host, string => $string };
|
||||
}
|
||||
|
||||
1;
|
||||
45
lib/exim.pm
Normal file
45
lib/exim.pm
Normal file
@ -0,0 +1,45 @@
|
||||
package My::parser::exim;
|
||||
use strict;
|
||||
use warnings;
|
||||
use File::Tail 0.91;
|
||||
use My::parser::exim_parser;
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $config = shift;
|
||||
my $self = {};
|
||||
bless ($self, $class);
|
||||
|
||||
$self->{'config'} = $config;
|
||||
$self->{'parser'} = My::parser::exim_parser->new();
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub parse {
|
||||
my $self = shift;
|
||||
my @result;
|
||||
while(my $string = $self->fetch) {
|
||||
last unless($string);
|
||||
if (my $line = $self->{'parser'}->parser($string)) {
|
||||
push(@result,$line);
|
||||
}
|
||||
}
|
||||
return { retval => 0 } unless(scalar(@result)); # nothing to say, nothing to report
|
||||
return { retval => 1, retmsg => 'Here comes the results', lines => \@result };
|
||||
}
|
||||
|
||||
sub fetch {
|
||||
my $self = shift;
|
||||
my $fetcher = $self->{'config'}->get_fetcher('exim');
|
||||
die "Fetcher for exim went away?" unless($fetcher);
|
||||
my $line;
|
||||
my ($nfound,$timeleft,@pending) = File::Tail::select(undef,undef,undef,1,$fetcher);
|
||||
foreach (@pending) {
|
||||
$line = $_->read;
|
||||
chomp($line);
|
||||
}
|
||||
return 0 unless($line);
|
||||
return $line;
|
||||
}
|
||||
|
||||
1;
|
||||
173
lib/exim_parser.pm
Normal file
173
lib/exim_parser.pm
Normal file
@ -0,0 +1,173 @@
|
||||
package My::parser::exim_parser;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $self = {};
|
||||
bless ($self, $class);
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub parser {
|
||||
my $self = shift;
|
||||
my $string = shift;
|
||||
my ($reply,$hostile,$host) = ("No match for $string",0,'');
|
||||
my $re_host = qr/[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/;
|
||||
my $re_msgid = qr/[0-9a-z]{6}-[0-9a-z]{11}-[0-9a-z]{4}/i;
|
||||
my $saslauth = qr/saslauthd_server authenticator failed/i;
|
||||
if($string =~ m/ ($re_msgid) /) {
|
||||
$reply = 'MSG ID, normal delivery';
|
||||
} elsif($string =~ m/(Start|End) queue run/) {
|
||||
$reply = 'Queue run';
|
||||
} elsif($string =~ m/1 accept\(\) failure/) {
|
||||
$reply = 'Connection aborted by software';
|
||||
} elsif($string =~ m/SSL verify error: /) {
|
||||
if($string =~ m/self signed certificate in certificate chain/) {
|
||||
$reply = 'Self signed certificate in chain';
|
||||
} elsif($string =~ m/error=self signed certificate cert=/) {
|
||||
$reply = 'Self signed certificate';
|
||||
} else {
|
||||
$reply = 'Other SSL verify errors';
|
||||
}
|
||||
} elsif($string =~ m/Temporary DNS error while checking SPF record/) {
|
||||
$reply = 'Temporary DNS error while checking SPF record';
|
||||
} elsif($string =~ m/$saslauth /) {
|
||||
$_ = $string;
|
||||
$hostile = 1;
|
||||
$reply = 'saslauth error';
|
||||
PARSER:
|
||||
m/(\ \[($re_host)\]:)/gcix && do {
|
||||
$host = $2;
|
||||
};
|
||||
} elsif($string =~ m/> rejected (after DATA|RCPT)/) {
|
||||
$_ = $string;
|
||||
$reply = 'Rejected';
|
||||
$hostile = 1;
|
||||
PARSER:
|
||||
m/(\ \[($re_host)\]\ )/gcix && do {
|
||||
$host = $2;
|
||||
};
|
||||
} elsif($string =~ m/\ rejected (AUTH|MAIL)\ /) {
|
||||
$_ = $string;
|
||||
$reply = 'Rejected MAIL or AUTH';
|
||||
$hostile = 1;
|
||||
PARSER:
|
||||
m/(\ \[($re_host)\]\ )/gcix && do {
|
||||
$host = $2;
|
||||
};
|
||||
} elsif($string =~ m/ too many (nonmail|syntax or protocol errors|unrecognized commands) /) {
|
||||
$_ = $string;
|
||||
$reply = 'protocol violations';
|
||||
$hostile = 1;
|
||||
PARSER:
|
||||
m/(\ \[($re_host)\])/gcix && do {
|
||||
$host = $2;
|
||||
};
|
||||
} elsif($string =~ m/ SMTP protocol synchronization error /) {
|
||||
$_ = $string;
|
||||
$reply = 'protocol errors';
|
||||
$hostile = 1;
|
||||
PARSE:
|
||||
m/(\[($re_host)\]\ ) /gcix && do {
|
||||
$host = $2;
|
||||
};
|
||||
} elsif($string =~ m/ TLS error on connection /) {
|
||||
$_ = $string;
|
||||
$reply = 'TLS error';
|
||||
$hostile = 1;
|
||||
PARSE:
|
||||
m/(\ \[($re_host)\]\ )/gcix && do {
|
||||
$host = $2;
|
||||
};
|
||||
} elsif($string =~ m/ TLS error \(SSL_read\)\: on connection /) {
|
||||
$_ = $string;
|
||||
$reply = 'TLS error';
|
||||
$hostile = 1;
|
||||
PARSE:
|
||||
m/(\ \[($re_host)\]\ )/gcix && do {
|
||||
$host = $2;
|
||||
};
|
||||
} elsif($string =~ m/ no IP address found for host /) {
|
||||
$_ = $string;
|
||||
$reply = 'Problems resolving hostname';
|
||||
$hostile = 1;
|
||||
PARSE:
|
||||
m/(\ \[($re_host)\])/gcix && do {
|
||||
$host = $2;
|
||||
};
|
||||
} elsif($string =~ m/ no host name found for IP address /) {
|
||||
$_ = $string;
|
||||
$reply = 'No hostname';
|
||||
$hostile = 0;
|
||||
} elsif($string =~ m/unexpected disconnection while reading SMTP command from/) {
|
||||
$_ = $string;
|
||||
$reply = 'SMTP error, disconnected';
|
||||
$hostile = 1;
|
||||
PARSE:
|
||||
m/(\ \[($re_host)\]\ )/gcix && do {
|
||||
$host = $2;
|
||||
};
|
||||
} elsif($string =~ m/ sender verify fail for /) {
|
||||
$_ = $string;
|
||||
$reply = 'Sender verify failure';
|
||||
$hostile = 1;
|
||||
PARSE:
|
||||
m/(\ \[($re_host)\]\ )/gcix && do {
|
||||
$host = $2;
|
||||
};
|
||||
} elsif($string =~ m/ rejected (EH|HE)LO from /) {
|
||||
$_ = $string;
|
||||
$reply = 'HELO/EHLO error';
|
||||
$hostile = 1;
|
||||
PARSE:
|
||||
m/(\ \[($re_host)\](:|)\ )/gcix && do {
|
||||
$host = $2;
|
||||
};
|
||||
} elsif($string =~ m/ You are not me /) {
|
||||
$_ = $string;
|
||||
$reply = 'Forged HELO';
|
||||
$hostile = 1;
|
||||
PARSE:
|
||||
m/(\ \[($re_host)\]\ )/gcix && do {
|
||||
$host = $2;
|
||||
};
|
||||
} elsif($string =~ m/ rejected after DATA /) {
|
||||
$_ = $string;
|
||||
$reply = 'Parser error';
|
||||
$hostile = 1;
|
||||
PARSE:
|
||||
m/(\ \[($re_host)\]\ )/gcix && do {
|
||||
$host = $2;
|
||||
};
|
||||
} elsif($string =~ m/ (login_saslauthd_server|LOGIN|PLAIN) authenticator failed for /) {
|
||||
$_ = $string;
|
||||
$reply = 'Auth error';
|
||||
$hostile = 1;
|
||||
PARSE:
|
||||
m/(\ \[($re_host)\])/gcix && do {
|
||||
$host = $2;
|
||||
};
|
||||
} elsif($string =~ m/ SMTP command timeout /) {
|
||||
$_ = $string;
|
||||
$reply = 'SMTP timeout';
|
||||
$hostile = 1;
|
||||
PARSE:
|
||||
m/(\ \[($re_host)\])/gcix && do {
|
||||
$host = $2;
|
||||
};
|
||||
} elsif($string =~ m/SSL_write: /) {
|
||||
unless($string =~ m/syscall: Broken pipe/) { #If we get a broken pipe, it's most likely because we blocked the ip earlier, and this is just the pipe timing out
|
||||
$_ = $string;
|
||||
$reply = 'SSL error';
|
||||
$hostile = 1;
|
||||
PARSE:
|
||||
m/(\ \[$re_host\]\))/gcix && do {
|
||||
$host = $2;
|
||||
};
|
||||
}
|
||||
}
|
||||
return { retval => 1, retmsg => $reply, hostile => $hostile, host => $host, string => $string };
|
||||
}
|
||||
|
||||
return 1;
|
||||
72
lib/file.pm
Normal file
72
lib/file.pm
Normal file
@ -0,0 +1,72 @@
|
||||
package My::parser::file;
|
||||
use strict;
|
||||
use warnings;
|
||||
use File::Tail 0.91;
|
||||
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $config = shift;
|
||||
my $rest = shift;
|
||||
return \$class if($rest);
|
||||
my $self = {};
|
||||
bless ($self, $class);
|
||||
$self->{'config'} = $config;
|
||||
$self->{'config'}->{'fetcher'}->{'file'} = $self;
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub init {
|
||||
my $self = shift;
|
||||
my $parser = shift;
|
||||
if($parser) {
|
||||
my $hash = $self->{'config'}->get_parser_info($parser);
|
||||
if($hash->{'type'} eq 'file') {
|
||||
my %opts = %{ &set_defaults };
|
||||
my $filename = $hash->{'source'};
|
||||
my $maxinterval = $self->{'config'}->get_as_single_val('config','maxinterval');
|
||||
my $interval = $self->{'config'}->get_as_single_val('config','interval');
|
||||
my $adjustafter = $self->{'config'}->get_as_single_val('config','adjustafter');
|
||||
my $resetafter = $self->{'config'}->get_as_single_val('config','resetafter');
|
||||
my $maxbuf = $self->{'config'}->get_as_single_val('config','maxbuf');
|
||||
my $nowait = $self->{'config'}->get_as_single_val('config','nowait');
|
||||
my $ignore_nonexistant = $self->{'config'}->get_as_single_val('config','ignore_nonexistant');
|
||||
my $tail = $self->{'config'}->get_as_single_val('config','tail');
|
||||
my $reset_tail = $self->{'config'}->get_as_single_val('config','reset_tail');
|
||||
$opts{'name'} = $filename;
|
||||
$opts{'maxinterval'} = $maxinterval if(defined($maxinterval));
|
||||
$opts{'interval'} = $interval if(defined($interval));
|
||||
$opts{'adjustafter'} = $adjustafter if(defined($adjustafter));
|
||||
$opts{'resetafter'} = $resetafter if(defined($resetafter));
|
||||
$opts{'maxbuf'} = $maxbuf if(defined($maxbuf));
|
||||
$opts{'nowait'} = $nowait if(defined($nowait));
|
||||
$opts{'ignore_nonexistant'} = $ignore_nonexistant if(defined($ignore_nonexistant));
|
||||
$opts{'tail'} = $tail if(defined($tail));
|
||||
$opts{'reset_tail'} = $reset_tail if(defined($reset_tail));
|
||||
$self->{'config'}->{'logger'}->log("Initializing fetcher file from parser $parser with file $filename");
|
||||
my $newfetcher = File::Tail->new(%opts);
|
||||
$self->{'config'}->set_fetcher('file',$parser,$newfetcher);
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
sub set_defaults {
|
||||
my $opts;
|
||||
$opts->{'maxinterval'} = 60; #max time spent sleeping between checks
|
||||
$opts->{'interval'} = 10; #initial time before first check
|
||||
$opts->{'adjustafter'} = 10; #number of times $interval passes before adjusting the check interval
|
||||
$opts->{'resetafter'} = $opts->{'interval'} * $opts->{'adjustafter'}; #Number of seconds after last change to file before reopening the file
|
||||
$opts->{'maxbuf'} = 16384;
|
||||
$opts->{'nowait'} = 0; #Does not block on read, but returns an empty string if there is nothing to read.
|
||||
$opts->{'ignore_nonexistant'} = 0; #Do not complain if the file doesn't exist when it is first opened or when it is to be reopened.
|
||||
$opts->{'tail'} = 0; #When first started, read and return C<n> lines from the file. If C<n> is zero, start at the end of file. If C<n> is negative, return the whole file.
|
||||
$opts->{'reset_tail'} = 0; #Same as tail, but for when $resetafter has gone by without any changes to the file
|
||||
return $opts;
|
||||
}
|
||||
|
||||
1;
|
||||
40
lib/geoip.pm
Normal file
40
lib/geoip.pm
Normal file
@ -0,0 +1,40 @@
|
||||
package My::parser::geoip;
|
||||
use strict;
|
||||
use warnings;
|
||||
use IO::Socket::INET;
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $self = {};
|
||||
bless ($self,$class);
|
||||
$self->{'config'} = shift;
|
||||
my $config = $self->{'config'};
|
||||
$config->{'logger'}->log('Loaded My::parser::geoip');
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub parse {
|
||||
my $self = shift;
|
||||
my $host = shift;
|
||||
return unless($host =~ m/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/);
|
||||
$| = 1;
|
||||
my $socket = new IO::Socket::INET (
|
||||
PeerHost => '10.0.0.100',
|
||||
PeerPort => '7777',
|
||||
Proto => 'tcp'
|
||||
);
|
||||
$self->{'config'}->{'logger'}->log("Failed to look up GeoIP for $host, could not connect to GeoIP resolver") unless $socket;
|
||||
return { asn => 0, iso => '' } unless $socket;
|
||||
my $size = $socket->send($host."\n");
|
||||
my $response;
|
||||
$socket->recv($response, 1024);
|
||||
shutdown($socket,1);
|
||||
$socket->close;
|
||||
my ($asn,$iso) = split (",", $response);
|
||||
my $r = {};
|
||||
$r->{'asn'} = $asn;
|
||||
$r->{'iso'} = $iso;
|
||||
return $r;
|
||||
}
|
||||
|
||||
1;
|
||||
43
lib/gitea.pm
Normal file
43
lib/gitea.pm
Normal file
@ -0,0 +1,43 @@
|
||||
package My::parser::gitea;
|
||||
use strict;
|
||||
use warnings;
|
||||
use My::parser::gitea_parser;
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $config = shift;
|
||||
my $self = {};
|
||||
bless ($self, $class);
|
||||
|
||||
$self->{'config'} = $config;
|
||||
$self->{'parser'} = My::parser::gitea_parser->new();
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub parse {
|
||||
my $self = shift;
|
||||
my @result;
|
||||
while(my $string = $self->fetch) {
|
||||
last unless($string);
|
||||
if (my $line = $self->{'parser'}->parser($string)) {
|
||||
push(@result,$line);
|
||||
}
|
||||
}
|
||||
return { retval => 0 } unless(scalar(@result));
|
||||
return { retval => 1, retmsg => 'Here comes the results', lines => \@result };
|
||||
}
|
||||
|
||||
sub fetch {
|
||||
my $self = shift;
|
||||
my $fetcher = $self->{'config'}->get_fetcher('gitea');
|
||||
my $line;
|
||||
my ($nfound,$timeleft,@pending) = File::Tail::select(undef,undef,undef,1,$fetcher);
|
||||
foreach (@pending) {
|
||||
$line = $_->read;
|
||||
chomp($line);
|
||||
}
|
||||
return 0 unless($line);
|
||||
return $line;
|
||||
}
|
||||
|
||||
1;
|
||||
41
lib/gitea_parser.pm
Normal file
41
lib/gitea_parser.pm
Normal file
@ -0,0 +1,41 @@
|
||||
package My::parser::gitea_parser;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $self = {};
|
||||
bless ($self, $class);
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub parser {
|
||||
my $self = shift;
|
||||
my $string = shift;
|
||||
my ($reply,$hostile,$host) = ("No match for $string",0,'');
|
||||
my $re_host = qr/[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/;
|
||||
if($string =~ m/sshConnectionFailed/) {
|
||||
if($string =~ m/Failed connection from /) {
|
||||
$_ = $string;
|
||||
$reply = 'Failed connection';
|
||||
$hostile = 1;
|
||||
PARSE:
|
||||
m/($re_host)\:[0-9]{1,6} /gcix && do {
|
||||
$host = $1;
|
||||
};
|
||||
} elsif($string =~ m/Failed authentication attempt from /) {
|
||||
$_ = $string;
|
||||
$reply = 'Failed auth';
|
||||
$hostile = 1;
|
||||
PARSE:
|
||||
m/($re_host)\:[0-9]{1,6}/gcix && do {
|
||||
$host = $1;
|
||||
};
|
||||
}
|
||||
} else {
|
||||
$reply = 'non-ssh lines not supported yet';
|
||||
}
|
||||
return { retval => 1, retmsg => $reply, hostile => $hostile, host => $host, string => $string };
|
||||
}
|
||||
|
||||
return 1;
|
||||
24
lib/localcheck.pm
Normal file
24
lib/localcheck.pm
Normal file
@ -0,0 +1,24 @@
|
||||
package My::parser::localcheck;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $self = {};
|
||||
bless ($self, $class);
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub islocal {
|
||||
my $self = shift;
|
||||
my $host = shift;
|
||||
my @local_nets = ('127\.','10\.','192\.168\.','172\.((1[6-9])|(2[0-9])|(3[0-1]))\.','213\.236\.200\.(7|10)'); # array of regexes matching networks considered local and to be ignored
|
||||
my $local = 0;
|
||||
foreach my $test(@local_nets) {
|
||||
$local = 1 if($host =~ m/^$test/);
|
||||
last if $local;
|
||||
}
|
||||
return $local;
|
||||
}
|
||||
|
||||
return 1;
|
||||
35
lib/logger.pm
Normal file
35
lib/logger.pm
Normal file
@ -0,0 +1,35 @@
|
||||
package My::parser::logger;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $config = shift;
|
||||
my $self = {};
|
||||
bless ($self, $class);
|
||||
$self->{'config'} = $config;
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub log {
|
||||
my $self = shift;
|
||||
my $str = shift;
|
||||
my $fh = $self->{'logfh'};
|
||||
return 0 unless($str);
|
||||
print $fh localtime(time).': '.$str."\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub reload_log {
|
||||
my $self = shift;
|
||||
my $file = $self->{'config'}->get_as_single_val('config','logfile');
|
||||
die 'No output logfile' unless($file);
|
||||
close($self->{'logfh'}) if($self->{'logfh'});
|
||||
my $fh = $self->{'logfh'};
|
||||
open($fh, '>>', "$file") || die 'Could not open logfile for appending: '.$!;
|
||||
select $fh; $| = 1;
|
||||
$self->{'logfh'} = $fh;
|
||||
return 1;
|
||||
}
|
||||
|
||||
1;
|
||||
142
lib/parser.pm
Normal file
142
lib/parser.pm
Normal file
@ -0,0 +1,142 @@
|
||||
package My::parser::parser;
|
||||
use strict;
|
||||
use warnings;
|
||||
use My::parser::logger;
|
||||
use My::parser::config;
|
||||
use My::parser::geoip;
|
||||
use My::parser::localcheck;
|
||||
use My::parser::block;
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $config_file = shift;
|
||||
my $self = {};
|
||||
bless ($self, $class);
|
||||
$self->{'config'} = My::parser::config->new($config_file) || die "Could not load config file $config_file";
|
||||
$self->{'config'}->{'logger'} = My::parser::logger->new($self->{'config'});
|
||||
$self->{'config'}->{'logger'}->reload_log;
|
||||
$self->{'geoip'} = My::parser::geoip->new($self->{'config'});
|
||||
$self->{'localcheck'} = My::parser::localcheck->new;
|
||||
$self->{'block'} = My::parser::block->new($self->{'config'});
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub parse_all {
|
||||
my $self = shift;
|
||||
my $parsers = $self->{'config'}->get_parsers;
|
||||
|
||||
foreach my $service (keys %{$parsers}) {
|
||||
my $fromparser = $parsers->{$service}->parse;
|
||||
if($fromparser->{'retval'} == 1) {
|
||||
foreach my $line (@{$fromparser->{'lines'}}) {
|
||||
my $msg = $line->{'retmsg'};
|
||||
my $host = $line->{'host'};
|
||||
my $hostile = $line->{'hostile'};
|
||||
if ($msg && $hostile && $host) {
|
||||
unless($self->{'localcheck'}->islocal($host)) {
|
||||
my $tolog = $host."($service";
|
||||
my $geoip = $self->{'geoip'}->parse($host);
|
||||
if ($geoip->{'asn'} && $geoip->{'iso'}) {
|
||||
$tolog .= ",$geoip->{'asn'},$geoip->{'iso'}): ";
|
||||
} else {
|
||||
$geoip->{'asn'} = 0;
|
||||
$geoip->{'iso'} = '';
|
||||
$tolog .= '): ';
|
||||
}
|
||||
if(my $fromblock = $self->{'block'}->blocklogic({host => $host, geoip => $geoip, service => $service})) {
|
||||
my $blmsg = $fromblock->{'retmsg'};
|
||||
$tolog .= $blmsg;
|
||||
}
|
||||
$tolog .= ", $msg";
|
||||
$self->{'config'}->{'logger'}->log($tolog);
|
||||
} else {
|
||||
$self->{'config'}->{'logger'}->log("$host is local");
|
||||
}
|
||||
} elsif ($msg =~ m/^No match/) {
|
||||
$self->{'config'}->{'logger'}->log("$service said: $msg");
|
||||
} elsif ($hostile) {
|
||||
$self->{'config'}->{'logger'}->log("Parser error, $service reported hostile activity, but no host given. Message from parser was: $msg. String passed to parser: ".$line->{'string'});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
sub load_parsers {
|
||||
my $self = shift;
|
||||
my $config = $self->{'config'};
|
||||
die 'No modules defined in config' unless(scalar(@{$config->get_modules}));
|
||||
foreach my $parser(@{$config->get_modules}) {
|
||||
if(my $newparser = $self->load($parser)) {
|
||||
$config->set('parsers',$parser,$newparser);
|
||||
my $module_info = $config->get_parser_info($parser);
|
||||
my $fetcher_needed = $module_info->{'type'};
|
||||
my $isloaded = $config->{'loadedparsers'}->{$fetcher_needed};
|
||||
unless(defined($isloaded)) {
|
||||
if(my $fetcher_loaded = $self->load($fetcher_needed)) {
|
||||
$self->dyninit($fetcher_loaded,$fetcher_needed,$parser);
|
||||
#$self->{'config'}->{'logger'}->log("Loaded dependency from $parser; $fetcher_needed"); FIXME add debug in config?
|
||||
} else {
|
||||
die "Failed to load $fetcher_needed, needed by $parser";
|
||||
}
|
||||
} else {
|
||||
my $toinit = $config->get_fetcher_module($fetcher_needed);
|
||||
if(defined($toinit)) {
|
||||
unless($self->dyninit($toinit,$fetcher_needed,$parser)) {
|
||||
$self->{'config'}->{'logger'}->log("Dyninit failed for $fetcher_needed for $parser");
|
||||
}
|
||||
} else {
|
||||
$self->{'config'}->{'logger'}->log('No dyninit needed for '.$fetcher_needed.' for '.$parser);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$self->{'config'}->{'logger'}->log("Failed to load parser for $parser");
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub dyninit {
|
||||
my $self = shift;
|
||||
my $fetcher_loaded = shift;
|
||||
my $fetcher_needed = shift;
|
||||
my $parser = shift;
|
||||
if(my $initr = $self->init($fetcher_loaded,$parser)) {
|
||||
$self->{'config'}->set('fetchers',$fetcher_needed,$fetcher_loaded);
|
||||
#$self->{'config'}->{'logger'}->log("Dependency $fetcher_needed initialized"); FIXME add debug in config?
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
sub load {
|
||||
my $self = shift;
|
||||
my $config = $self->{'config'};
|
||||
my $parser = shift;
|
||||
my $filename = 'My/parser/'.$parser.'.pm';
|
||||
my $newclass;
|
||||
eval {
|
||||
require $filename;
|
||||
my $classname = 'My::parser::'.$parser;
|
||||
$newclass = $classname->new($config) || die "Failed to load parser for $parser";
|
||||
$self->{'config'}->{'logger'}->log("Loaded $classname");
|
||||
} or do {
|
||||
my $e = $@;
|
||||
$self->{'config'}->{'logger'}->log("Failed to load $filename: $e");
|
||||
return 0;
|
||||
};
|
||||
$config->{'loadedparsers'}->{$parser} = 'loaded';
|
||||
return $newclass;
|
||||
}
|
||||
|
||||
sub init {
|
||||
my $self = shift;
|
||||
my $fetcher = shift;
|
||||
my $parser = shift;
|
||||
return 1 if($fetcher->init($parser));
|
||||
return 0;
|
||||
}
|
||||
|
||||
1;
|
||||
59
lib/ssh.pm
Normal file
59
lib/ssh.pm
Normal file
@ -0,0 +1,59 @@
|
||||
package My::parser::ssh;
|
||||
use strict;
|
||||
use DBI;
|
||||
use warnings;
|
||||
use My::parser::ssh_parser;
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $self = {};
|
||||
bless ($self, $class);
|
||||
$self->{'config'} = shift;
|
||||
$self->{'parser'} = My::parser::ssh_parser->new();
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub fetch {
|
||||
my $self = shift;
|
||||
$self->{'dbh'} = $self->{'config'}->get_dbh unless($self->{'dbh'});
|
||||
my $seq = $self->{'seq'} || 0;
|
||||
my $retmsg;
|
||||
my @toreturn;
|
||||
unless($seq) {
|
||||
my $seqsth = $self->{'dbh'}->prepare("SELECT seq FROM logs WHERE program = 'sshd' ORDER BY seq DESC LIMIT 1") or $retmsg = DBI::errstr;
|
||||
$seqsth->execute or $retmsg = DBI::errstr unless($retmsg);
|
||||
$seq = $seqsth->fetchrow_arrayref->[0] or $retmsg = DBI::errstr unless($retmsg);
|
||||
}
|
||||
return { retval => 0, retmsg => $retmsg, error => 1 } if($retmsg);
|
||||
my $sth = $self->{'dbh'}->prepare("SELECT msg,seq FROM logs WHERE program = 'sshd' AND seq > $seq") or $retmsg = DBI::errstr unless($retmsg);
|
||||
$sth->execute or $retmsg = DBI::errstr unless($retmsg);
|
||||
while(my $ref = $sth->fetchrow_hashref) {
|
||||
my $string = $$ref{'msg'};
|
||||
$seq = $$ref{'seq'};
|
||||
push(@toreturn,$string);
|
||||
}
|
||||
$self->{'seq'} = $seq;
|
||||
return { retval => 0, retmsg => $retmsg, error => 1 } if($retmsg);
|
||||
return { retval => 0, retmsg => 'Nothing to return' } unless(scalar(@toreturn));
|
||||
return { retval => 1, retmsg => 'Here comes the results', lines => \@toreturn };
|
||||
}
|
||||
|
||||
sub parse {
|
||||
my $self = shift;
|
||||
my @result;
|
||||
my $string = $self->fetch;
|
||||
if($string->{'retval'}) {
|
||||
foreach my $str(@{$string->{'lines'}}) {
|
||||
my $r = $self->{'parser'}->parser($str);
|
||||
if($r->{'retval'}) {
|
||||
delete $r->{'retval'};
|
||||
push(@result,$r);
|
||||
}
|
||||
}
|
||||
return { retval => 1, retmsg => 'Here comes the results', lines => \@result };
|
||||
}
|
||||
return { retval => 0, retmsg => "ssh-fetcher returned an error: $string->{'retmsg'}" } if($string->{'error'});
|
||||
return { retval => 0 }; # nothing to return, nothing to say:)
|
||||
}
|
||||
|
||||
1;
|
||||
178
lib/ssh_parser.pm
Normal file
178
lib/ssh_parser.pm
Normal file
@ -0,0 +1,178 @@
|
||||
package My::parser::ssh_parser;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $self = {};
|
||||
bless ($self, $class);
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub parser {
|
||||
my $self = shift;
|
||||
my $string = shift;
|
||||
my ($reply,$hostile,$host) = ('',0,'');
|
||||
my $re_host = qr/[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/;
|
||||
my $re_user = qr/[\w\d_\-.]*/;
|
||||
if($string =~ m/Accepted publickey for/) {
|
||||
$reply = 'Accept publickey';
|
||||
} elsif($string =~ m/Received disconnect from $re_host port [0-9]{1,5}:11: disconnected by user/) {
|
||||
$reply = 'Normal disconnect';
|
||||
} elsif($string =~ m/Failed unknown for (invalid user |)$re_user from $re_host port [0-9]{1,5} ssh2/) {
|
||||
$reply = 'Log spam, also logged as a failure';
|
||||
} elsif($string =~ m/Disconnected from user $re_user $re_host port [0-9]{1,5}$/) {
|
||||
$reply = 'Normal disconnect';
|
||||
} elsif($string =~ m/Did not receive identification string from /) {
|
||||
$_ = $string;
|
||||
$reply = 'No identification string';
|
||||
$hostile = 1;
|
||||
PARSER:
|
||||
m/ ($re_host) /gcix && do {
|
||||
$host = $1;
|
||||
};
|
||||
} elsif($string =~ m/User $re_user from $re_host not allowed because /) {
|
||||
$_ = $string;
|
||||
$reply = 'Blocked user';
|
||||
$hostile = 1;
|
||||
PARSER:
|
||||
m/ ($re_host) /gcix && do {
|
||||
$host = $1;
|
||||
};
|
||||
} elsif($string =~ m/(i|I)nvalid user .* from $re_host port/) {
|
||||
$_ = $string;
|
||||
$reply = 'Invalid user';
|
||||
$hostile = 1;
|
||||
PARSER:
|
||||
m/(from\ ($re_host)) /gcix && do {
|
||||
$host = $2;
|
||||
};
|
||||
} elsif($string =~ m/(Disconnecting|Received disconnect from|Disconnected from|Connection closed by|Connection reset by) (authenticating |invalid |)(user .* |)$re_host port [0-9]{1,6}.*\[preauth\]/) {
|
||||
$_ = $string;
|
||||
$reply = 'Received disconnect';
|
||||
$hostile = 1;
|
||||
PARSER:
|
||||
m/\ ($re_host)\ /gcix && do {
|
||||
$host = $1;
|
||||
};
|
||||
} elsif($string =~ m/refused connect from .* \($re_host\)/) {
|
||||
$_ = $string;
|
||||
$reply = 'Blocked by tcpwrappers';
|
||||
$hostile = 1;
|
||||
PARSER:
|
||||
m/ \(($re_host)\) /gcix && do {
|
||||
$host = $1;
|
||||
};
|
||||
} elsif($string =~ m/error: maximum authentication attempts exceeded for (invalid user |)$re_user from ($re_host) port [0-9]{1,6} (ssh2 |)\[preauth\]/) {
|
||||
$_ = $string;
|
||||
$reply = 'Auth attempt limit exceeded';
|
||||
$hostile = 1;
|
||||
PARSER:
|
||||
m/ ($re_host) /gcix && do {
|
||||
$host = $1;
|
||||
};
|
||||
} elsif($string =~ m/fatal: Unable to negotiate with/) {
|
||||
$_ = $string;
|
||||
$reply = 'Unable to negotiate';
|
||||
$hostile = 1;
|
||||
PARSER:
|
||||
m/ ($re_host) /gcix && do {
|
||||
$host = $1;
|
||||
};
|
||||
} elsif($string =~ m/Bad protocol version identification/) {
|
||||
$_ = $string;
|
||||
$reply = 'Bad protocol';
|
||||
$hostile = 1;
|
||||
PARSER:
|
||||
m/ ($re_host) /gcix && do {
|
||||
$host = $1;
|
||||
};
|
||||
} elsif($string =~ m/Could not write ident string to/) {
|
||||
$_ = $string;
|
||||
$reply = 'Could not write ident string';
|
||||
$hostile = 1;
|
||||
PARSER:
|
||||
m/ ($re_host) /gcix && do {
|
||||
$host = $1;
|
||||
};
|
||||
} elsif($string =~ m/Disconnecting (authenticating|invalid) user.*Change of username or service not allowed/) {
|
||||
$_ = $string;
|
||||
$reply = "Change of username or service";
|
||||
$hostile = 1;
|
||||
PARSER:
|
||||
m/ ($re_host) /gcix && do {
|
||||
$host = $1;
|
||||
};
|
||||
} elsif($string =~ m/Failed publickey for $re_user from $re_host port [\d]{1,6}.*/) {
|
||||
$_ = $string;
|
||||
$reply = 'Failed publickey';
|
||||
$hostile = 1;
|
||||
PARSER:
|
||||
m/\ ($re_host)\ /gcix && do {
|
||||
$host = $1;
|
||||
};
|
||||
} elsif($string =~ m/ssh_dispatch_run_fatal/) {
|
||||
$_ = $string;
|
||||
$reply = 'ssh dispatch fatal';
|
||||
$hostile = 1;
|
||||
PARSER:
|
||||
m/ ($re_host) /gcix && do {
|
||||
$host = $1;
|
||||
};
|
||||
} elsif($string =~ m/Unable to negotiate with $re_host port/) {
|
||||
$_ = $string;
|
||||
$reply = 'Unable to negotiate. Weak key exchange';
|
||||
$hostile = 1;
|
||||
PARSER:
|
||||
m/ ($re_host) /gcix && do {
|
||||
$host = $1;
|
||||
};
|
||||
} elsif($string =~ m/Protocol major versions differ for/) {
|
||||
$_ = $string;
|
||||
$reply = 'Protocol major versions differ';
|
||||
$hostile = 1;
|
||||
PARSER:
|
||||
m/ ($re_host) /gcix && do {
|
||||
$host = $1;
|
||||
};
|
||||
} elsif($string =~ m/Unable to negotiate with .* no matching MAC found/) {
|
||||
$host = '';
|
||||
$reply = 'no matching MAC, ancient client trying to connect';
|
||||
} elsif($string =~ m/\/etc\/hosts\.allow/) {
|
||||
$host = '';
|
||||
} elsif($string =~ m/Disconnecting: Too many authentication failures/) {
|
||||
$host = '';
|
||||
} elsif($string =~ m/input_userauth_request: invalid user.*\[preauth\]/) {
|
||||
$host = '';
|
||||
} elsif($string =~ m/user $re_user login class/) {
|
||||
$host = '';
|
||||
$reply = 'Useless log info';
|
||||
} elsif($string =~ m/(Disconnected|Connection closed) (from|by) (invalid|) user $re_user $re_host port [0-9]{1,6} \[preauth\]/) {
|
||||
$host = '';
|
||||
$reply = 'Log info';
|
||||
} elsif($string =~ m/Fssh_kex_exchange_identification/) {
|
||||
$hostile = 0;
|
||||
$reply = 'kex exchange identification problem';
|
||||
} elsif($string =~ m/fatal: Timeout before authentication for $re_host port [0-9]{1,6}/) {
|
||||
$_ = $string;
|
||||
$hostile = 1;
|
||||
$reply = 'Timeout before auth';
|
||||
PARSER:
|
||||
m/ ($re_host) /gcix && do {
|
||||
$host = $1;
|
||||
};
|
||||
} elsif($string =~ m/banner exchange: Connection from $re_host port [0-9]{1,6}: invalid format/) {
|
||||
$_ = $string;
|
||||
$hostile = 1;
|
||||
$reply = 'Invalid format during banner exchange';
|
||||
PARSER:
|
||||
m/ ($re_host) /gcix && do {
|
||||
$host = $1;
|
||||
};
|
||||
} else {
|
||||
$reply = 'No match for '.$string;
|
||||
}
|
||||
return { retval => 1, retmsg => $reply, hostile => $hostile, host => $host, string => $string };
|
||||
}
|
||||
|
||||
1;
|
||||
90
lib/stats.pm
Normal file
90
lib/stats.pm
Normal file
@ -0,0 +1,90 @@
|
||||
package My::parser::stats;
|
||||
use strict;
|
||||
use warnings;
|
||||
use DBI;
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $config = shift;
|
||||
my $self = {};
|
||||
bless ($self, $class);
|
||||
$self->{'config'} = $config;
|
||||
my $short_time = $config->get_as_single_val('config','short') || die "Failed to get short value";
|
||||
my $long_time = $config->get_as_single_val('config','long') || die "Failed to get long value";
|
||||
$self->{'short'} = $short_time;
|
||||
$self->{'long'} = $long_time;
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub recent_hostile {
|
||||
my $self = shift;
|
||||
$self->{'dbh'} = $self->{'config'}->get_dbh unless($self->{'dbh'});
|
||||
my $sth = $self->{'dbh'}->prepare("
|
||||
SELECT COUNT(1)
|
||||
FROM reject
|
||||
WHERE time > DATE_SUB(NOW(), INTERVAL 5 MINUTE)")
|
||||
|| return { retval => 0, retmsg => 'Failed checking for rejects last 5 minutes: '.DBI::errstr };
|
||||
$sth->execute() || return { retval => 0, retmsg => 'Failed execute on checking for rejects last 5 minutes: '.DBI::errstr };
|
||||
my $rows = $sth->fetchrow_arrayref->[0];
|
||||
return { retval => 1, rows => $rows, retmsg => $rows.' the last 5 minutes' };
|
||||
}
|
||||
|
||||
sub checker {
|
||||
my $self = shift;
|
||||
my $tocheck = shift;
|
||||
my $host = $tocheck->{'host'};
|
||||
my $asn = $tocheck->{'asn'};
|
||||
my $iso = $tocheck->{'iso'};
|
||||
unless($self && $host) {
|
||||
return { retval => 0, retmsg => "Too few variables to run My::parser::stats->checker, got host($host), asn($asn), iso($iso)" };
|
||||
}
|
||||
my $retval;
|
||||
my $rows;
|
||||
my $msg;
|
||||
my $return = { retval => 1 };
|
||||
my @times = ($self->{'short'},$self->{'long'});
|
||||
#### Mapping between checks and tables in db
|
||||
my %checks = ('reject' => 'reject','blocks' => 'reject_iptables','iso' => 'reject','asn' => 'reject', 'block_iso' => 'reject_iptables', 'block_asn' => 'reject_iptables');
|
||||
#### Mapping between checks and column in db
|
||||
my %values = ('reject' => 'ip','blocks' => 'ip', 'iso' => 'iso', 'asn' => 'asn', 'block_iso' => 'iso', 'block_asn' => 'asn');
|
||||
my %keys = ('reject' => $host, 'blocks' => $host, 'iso' => $iso, 'block_iso' => $iso, 'asn' => $asn, 'block_asn' => $asn);
|
||||
$self->{'dbh'} = $self->{'config'}->get_dbh unless($self->{'dbh'});
|
||||
foreach my $time(@times) {
|
||||
foreach my $c(keys %checks) {
|
||||
my $fromcheck = $self->check($keys{$c},$time,$values{$c},$checks{$c});
|
||||
if($fromcheck->{'retval'}) {
|
||||
my $temp = $fromcheck->{'rows'};
|
||||
$return->{$time}->{$c} = $temp;
|
||||
# } else {
|
||||
# $self->{'config'}->{'logger'}->log("Check failed: $fromcheck->{'retmsg'}");
|
||||
}
|
||||
}
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
|
||||
sub check {
|
||||
my $self = shift;
|
||||
my $host = shift;
|
||||
my $allowed_time = shift;
|
||||
my $value = shift;
|
||||
my $table = shift;
|
||||
my $retval;
|
||||
my $rows;
|
||||
my $msg;
|
||||
if($host && $allowed_time && $value && $table) {
|
||||
my $sth = $self->{'dbh'}->prepare("
|
||||
SELECT COUNT(1)
|
||||
FROM $table
|
||||
WHERE $value = ? AND
|
||||
time > DATE_SUB(NOW(), INTERVAL ? MINUTE)")
|
||||
|| return { retval => 0, retmsg => "Checking $host in $table for $value in the last $allowed_time minutes failed: ".DBI::errstr };
|
||||
$sth->execute($host,$allowed_time) || return { retval => 0, retmsg => "Execute failed on $table, checking for $host in $allowed_time: ".DBI::errstr };
|
||||
my $rows = $sth->fetchrow_arrayref->[0];
|
||||
return { retval => 1, rows => $rows, retmsg => "$rows in $allowed_time minutes" };
|
||||
} else {
|
||||
return { retval => 0, retmsg => "Check called with too few arguments, we got host: $host, allowed_time: $allowed_time, value: $value, table: $table" };
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
||||
Reference in New Issue
Block a user