2012-06-27 09:27:35 +02:00
|
|
|
#!/usr/bin/perl
|
|
|
|
|
|
|
|
use strict;
|
|
|
|
use warnings;
|
|
|
|
|
|
|
|
use Cwd;
|
|
|
|
use Data::Dumper;
|
|
|
|
use DBIx::Simple;
|
2013-04-22 08:12:53 +02:00
|
|
|
use IO::File;
|
2012-06-27 09:27:35 +02:00
|
|
|
use File::stat;
|
|
|
|
use Time::TAI64 qw/ tai2unix /;
|
|
|
|
|
|
|
|
$Data::Dumper::Sortkeys = 1;
|
|
|
|
|
|
|
|
my $logdir = get_log_dir();
|
|
|
|
my @logfiles = get_logfiles($logdir);
|
|
|
|
|
|
|
|
my (%plugins, %os, %message_ids);
|
|
|
|
my $has_cleanup;
|
|
|
|
my $db = get_db();
|
2013-04-22 08:12:53 +02:00
|
|
|
check_plugins_table();
|
2012-06-27 09:27:35 +02:00
|
|
|
|
2013-04-21 06:54:43 +02:00
|
|
|
foreach my $file (@logfiles) {
|
2012-06-27 09:27:35 +02:00
|
|
|
my ($fid, $offset) = check_logfile($file);
|
|
|
|
$fid or next;
|
2013-04-21 06:54:43 +02:00
|
|
|
parse_logfile($file, $fid, $offset);
|
|
|
|
}
|
2012-06-27 09:27:35 +02:00
|
|
|
|
|
|
|
exit;
|
|
|
|
|
|
|
|
sub trim_message {
|
|
|
|
my $mess = shift;
|
2013-03-27 23:14:23 +01:00
|
|
|
|
2012-06-27 09:27:35 +02:00
|
|
|
return '' if $mess eq 'skip, naughty';
|
|
|
|
return '' if $mess eq 'skip, relay client';
|
|
|
|
return '' if $mess eq 'skip, no match';
|
|
|
|
return '' if $mess eq 'skip: unsigned';
|
|
|
|
return '' if $mess eq 'skip, not a null sender';
|
|
|
|
return '' if $mess eq 'pass';
|
|
|
|
return '' if $mess eq 'pass, no record';
|
|
|
|
return '' if $mess eq 'pass, Deliverable through vpopmail';
|
|
|
|
return '' if $mess eq 'pass, clean';
|
|
|
|
return '' if $mess =~ /^fail. NAUGHTY/;
|
|
|
|
return '' if $mess =~ /^PTR:\s/;
|
|
|
|
return '' if $mess eq 'TLS setup returning';
|
|
|
|
|
|
|
|
return $mess;
|
2013-04-21 06:54:43 +02:00
|
|
|
}
|
2012-06-27 09:27:35 +02:00
|
|
|
|
|
|
|
sub get_os_id {
|
|
|
|
my $p0f_string = shift or return;
|
|
|
|
|
|
|
|
$p0f_string =~ s/\s+$//;
|
|
|
|
$p0f_string =~ s/^\s+//;
|
2013-04-21 06:54:43 +02:00
|
|
|
return if !$p0f_string;
|
2012-06-27 09:27:35 +02:00
|
|
|
return if $p0f_string =~ /no match/;
|
|
|
|
return if $p0f_string =~ /^skip/;
|
|
|
|
return if $p0f_string =~ /^\d/;
|
|
|
|
return if $p0f_string =~ /^\(/;
|
|
|
|
return if $p0f_string !~ /\w/;
|
|
|
|
return if $p0f_string =~ /no longer in the cache/;
|
|
|
|
|
2013-04-21 06:54:43 +02:00
|
|
|
if (!scalar keys %os) {
|
|
|
|
my $ref = exec_query('SELECT * FROM os');
|
|
|
|
foreach my $o (@$ref) {
|
|
|
|
$os{$o->{name}} = $o->{id};
|
|
|
|
}
|
|
|
|
}
|
2012-06-27 09:27:35 +02:00
|
|
|
|
2013-04-21 06:54:43 +02:00
|
|
|
if (!defined $os{$p0f_string}) {
|
2012-06-27 09:27:35 +02:00
|
|
|
warn "missing OS for $p0f_string\n";
|
2013-04-21 06:54:43 +02:00
|
|
|
}
|
2012-06-27 09:27:35 +02:00
|
|
|
|
|
|
|
return $os{$p0f_string};
|
2013-04-21 06:54:43 +02:00
|
|
|
}
|
2012-06-27 09:27:35 +02:00
|
|
|
|
|
|
|
sub get_plugin_id {
|
|
|
|
my $plugin = shift;
|
|
|
|
|
2013-04-21 06:54:43 +02:00
|
|
|
if (!scalar keys %plugins) {
|
|
|
|
my $ref = exec_query('SELECT * FROM plugin');
|
|
|
|
foreach my $p (@$ref) {
|
|
|
|
$plugins{$p->{name}} = $p->{id};
|
|
|
|
$plugins{$p->{id}} = $p->{name};
|
|
|
|
}
|
|
|
|
$ref = exec_query('SELECT * FROM plugin_aliases');
|
|
|
|
foreach my $pa (@$ref) {
|
|
|
|
$plugins{$pa->{name}} = $pa->{plugin_id};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!defined $plugins{$plugin}) {
|
2012-06-27 09:27:35 +02:00
|
|
|
|
|
|
|
#warn Dumper(\%plugins);
|
|
|
|
die "missing DB plugin $plugin\n";
|
2013-04-21 06:54:43 +02:00
|
|
|
}
|
2012-06-27 09:27:35 +02:00
|
|
|
|
|
|
|
return $plugins{$plugin};
|
2013-04-21 06:54:43 +02:00
|
|
|
}
|
2012-06-27 09:27:35 +02:00
|
|
|
|
|
|
|
sub get_msg_id {
|
2013-04-21 06:54:43 +02:00
|
|
|
my ($fid, $pid) = @_;
|
2012-06-27 09:27:35 +02:00
|
|
|
|
2013-04-21 06:54:43 +02:00
|
|
|
return $message_ids{"$fid-$pid"} if $message_ids{"$fid-$pid"};
|
2012-06-27 09:27:35 +02:00
|
|
|
|
|
|
|
#print "searching for message $pid...";
|
2013-04-21 06:54:43 +02:00
|
|
|
my $msgs = exec_query('SELECT * FROM message WHERE file_id=? AND qp_pid=?',
|
|
|
|
[$fid, $pid]);
|
|
|
|
|
2012-06-27 09:27:35 +02:00
|
|
|
#print scalar @$msgs ? "y\n" : "n\n";
|
2013-04-21 06:54:43 +02:00
|
|
|
if ($msgs->[0]{id}) {
|
|
|
|
$message_ids{"$fid-$pid"} = $msgs->[0]{id};
|
|
|
|
}
|
2012-06-27 09:27:35 +02:00
|
|
|
return $msgs->[0]{id};
|
2013-04-21 06:54:43 +02:00
|
|
|
}
|
2012-06-27 09:27:35 +02:00
|
|
|
|
|
|
|
sub create_message {
|
2013-04-21 06:54:43 +02:00
|
|
|
my ($fid, $ts, $pid, $message) = @_;
|
2012-06-27 09:27:35 +02:00
|
|
|
|
|
|
|
my ($host, $ip) = split /\s/, $message;
|
2013-04-21 06:54:43 +02:00
|
|
|
$ip = substr $ip, 1, -1; # remove brackets
|
2012-06-27 09:27:35 +02:00
|
|
|
|
2013-03-27 23:14:23 +01:00
|
|
|
my $id = exec_query(
|
2013-04-21 06:54:43 +02:00
|
|
|
"INSERT INTO message SET file_id=?, connect_start=FROM_UNIXTIME(?), qp_pid=?, ip=INET_ATON(?)",
|
|
|
|
[$fid, $ts, $pid, $ip]
|
2012-06-27 09:27:35 +02:00
|
|
|
);
|
|
|
|
|
2013-04-21 06:54:43 +02:00
|
|
|
if ($host && $host ne 'Unknown') {
|
|
|
|
exec_query("UPDATE message SET hostname=? WHERE id=?", [$host, $id]);
|
|
|
|
}
|
|
|
|
|
2013-03-27 23:14:23 +01:00
|
|
|
#warn "host updated: $host\n";
|
2013-04-21 06:54:43 +02:00
|
|
|
}
|
2012-06-27 09:27:35 +02:00
|
|
|
|
|
|
|
sub insert_plugin {
|
2013-04-21 06:54:43 +02:00
|
|
|
my ($msg_id, $plugin, $message) = @_;
|
2012-06-27 09:27:35 +02:00
|
|
|
|
2013-04-21 06:54:43 +02:00
|
|
|
my $plugin_id = get_plugin_id($plugin);
|
2012-06-27 09:27:35 +02:00
|
|
|
|
2013-04-21 06:54:43 +02:00
|
|
|
if ($plugin eq 'ident::geoip') {
|
2012-06-27 09:27:35 +02:00
|
|
|
my ($gip, $distance) = $message =~ /(.*?),\s+([\d]+)\skm/;
|
2013-04-21 06:54:43 +02:00
|
|
|
if ($distance) {
|
|
|
|
exec_query('UPDATE message SET distance=? WHERE id=?',
|
|
|
|
[$distance, $msg_id]);
|
2012-06-27 09:27:35 +02:00
|
|
|
$message = $gip;
|
|
|
|
}
|
|
|
|
}
|
2013-04-21 06:54:43 +02:00
|
|
|
elsif ($plugin =~ /^ident::p0f/) {
|
|
|
|
my $os_id = get_os_id($message);
|
|
|
|
if ($os_id) {
|
|
|
|
exec_query('UPDATE message SET os_id=? WHERE id=?',
|
|
|
|
[$os_id, $msg_id]);
|
2012-06-27 09:27:35 +02:00
|
|
|
$message = 'pass';
|
|
|
|
}
|
|
|
|
}
|
2013-04-21 06:54:43 +02:00
|
|
|
elsif ($plugin eq 'connection_time') {
|
2012-06-27 09:27:35 +02:00
|
|
|
my ($seconds) = $message =~ /\s*([\d\.]+)\s/;
|
2013-04-21 06:54:43 +02:00
|
|
|
if ($seconds) {
|
|
|
|
exec_query('UPDATE message SET time=? WHERE id=?',
|
|
|
|
[$seconds, $msg_id]);
|
2012-06-27 09:27:35 +02:00
|
|
|
$message = 'pass';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-04-21 06:54:43 +02:00
|
|
|
my $result = get_score($message);
|
|
|
|
if ($result) {
|
2012-06-27 09:27:35 +02:00
|
|
|
$message = trim_message($message);
|
2013-04-21 06:54:43 +02:00
|
|
|
}
|
2012-06-27 09:27:35 +02:00
|
|
|
|
2013-04-21 06:54:43 +02:00
|
|
|
exec_query(
|
|
|
|
'INSERT INTO message_plugin SET msg_id=?, plugin_id=?, result=?, string=?',
|
|
|
|
[$msg_id, $plugin_id, $result, $message]
|
2012-06-27 09:27:35 +02:00
|
|
|
);
|
2013-04-21 06:54:43 +02:00
|
|
|
}
|
2012-06-27 09:27:35 +02:00
|
|
|
|
|
|
|
sub parse_logfile {
|
2013-04-21 06:54:43 +02:00
|
|
|
my $file = shift;
|
|
|
|
my $fid = shift;
|
2012-06-27 09:27:35 +02:00
|
|
|
my $offset = shift || 0;
|
2013-04-21 06:54:43 +02:00
|
|
|
my $path = "$logdir/$file";
|
2012-06-27 09:27:35 +02:00
|
|
|
|
|
|
|
print "parsing file $file (id: $fid) from offset $offset\n";
|
|
|
|
open my $F, '<', $path or die "could not open $path: $!";
|
2013-04-21 06:54:43 +02:00
|
|
|
seek($F, $offset, 0) if $offset;
|
2012-06-27 09:27:35 +02:00
|
|
|
|
2013-04-21 06:54:43 +02:00
|
|
|
while (defined(my $line = <$F>)) {
|
2012-06-27 09:27:35 +02:00
|
|
|
chomp $line;
|
2013-04-21 06:54:43 +02:00
|
|
|
next if !$line;
|
|
|
|
my ($type, $pid, $hook, $plugin, $message) = parse_line($line);
|
2012-06-27 09:27:35 +02:00
|
|
|
|
2013-04-21 06:54:43 +02:00
|
|
|
next if !$type;
|
2012-06-27 09:27:35 +02:00
|
|
|
next if $type eq 'info';
|
|
|
|
next if $type eq 'unknown';
|
|
|
|
next if $type eq 'response';
|
2013-04-21 06:54:43 +02:00
|
|
|
next if $type eq 'init'; # doesn't occur in all deployment models
|
2012-06-27 09:27:35 +02:00
|
|
|
next if $type eq 'cleanup';
|
|
|
|
next if $type eq 'error';
|
|
|
|
|
2013-04-21 06:54:43 +02:00
|
|
|
my $ts = tai2unix((split /\s/, $line)[0]); # print "ts: $ts\n";
|
2012-06-27 09:27:35 +02:00
|
|
|
|
2013-04-21 06:54:43 +02:00
|
|
|
my $msg_id = get_msg_id($fid, $pid) or do {
|
|
|
|
create_message($fid, $ts, $pid, $message) if $type eq 'connect';
|
2012-06-27 09:27:35 +02:00
|
|
|
next;
|
|
|
|
};
|
|
|
|
|
2013-03-27 23:14:23 +01:00
|
|
|
#warn "type: $type\n";
|
2013-04-21 06:54:43 +02:00
|
|
|
if ($type eq 'plugin') {
|
|
|
|
next if $plugin eq 'naughty'; # housekeeping only
|
2013-04-22 08:12:53 +02:00
|
|
|
next if $plugin eq 'karma' && 'karma adjust' eq substr($message,0,12);
|
2013-04-21 06:54:43 +02:00
|
|
|
insert_plugin($msg_id, $plugin, $message);
|
2012-06-27 09:27:35 +02:00
|
|
|
}
|
2013-04-21 06:54:43 +02:00
|
|
|
elsif ($type eq 'queue') {
|
|
|
|
exec_query('UPDATE message SET result=? WHERE id=?', [3, $msg_id]);
|
2012-06-27 09:27:35 +02:00
|
|
|
}
|
2013-04-21 06:54:43 +02:00
|
|
|
elsif ($type eq 'reject') {
|
|
|
|
exec_query('UPDATE message SET result=? WHERE id=?', [-3, $msg_id]);
|
2012-06-27 09:27:35 +02:00
|
|
|
}
|
2013-04-21 06:54:43 +02:00
|
|
|
elsif ($type eq 'close') {
|
|
|
|
if ($message eq 'Connection Timed Out') {
|
|
|
|
exec_query('UPDATE message SET result=? WHERE id=?',
|
|
|
|
[-1, $msg_id]);
|
|
|
|
}
|
2012-06-27 09:27:35 +02:00
|
|
|
}
|
2013-04-21 06:54:43 +02:00
|
|
|
elsif ($type eq 'connect') { }
|
|
|
|
elsif ($type eq 'dispatch') {
|
|
|
|
if (substr($message, 0, 21) eq 'dispatching MAIL FROM') {
|
|
|
|
my ($from) = $message =~ /<(.*?)>/;
|
|
|
|
exec_query('UPDATE message SET mail_from=? WHERE id=?',
|
|
|
|
[$from, $msg_id]);
|
2012-06-27 09:27:35 +02:00
|
|
|
}
|
2013-04-21 06:54:43 +02:00
|
|
|
elsif (substr($message, 0, 19) eq 'dispatching RCPT TO') {
|
|
|
|
my ($to) = $message =~ /<(.*?)>/;
|
|
|
|
exec_query(
|
|
|
|
'UPDATE message SET rcpt_to=? WHERE id=? AND rcpt_to IS NULL',
|
|
|
|
[$to, $msg_id]
|
|
|
|
);
|
2012-06-27 09:27:35 +02:00
|
|
|
}
|
2013-04-21 06:54:43 +02:00
|
|
|
elsif ($message =~ m/dispatching (EHLO|HELO) (.*)/) {
|
|
|
|
exec_query('UPDATE message SET helo=? WHERE id=?',
|
|
|
|
[$2, $msg_id]);
|
2012-06-27 09:27:35 +02:00
|
|
|
}
|
2013-04-21 06:54:43 +02:00
|
|
|
elsif ($message eq 'dispatching DATA') { }
|
|
|
|
elsif ($message eq 'dispatching QUIT') { }
|
|
|
|
elsif ($message eq 'dispatching STARTTLS') { }
|
|
|
|
elsif ($message eq 'dispatching RSET') { }
|
2012-06-27 09:27:35 +02:00
|
|
|
else {
|
|
|
|
# anything here is likely an unrecognized command
|
|
|
|
#print "$message\n";
|
2013-04-21 06:54:43 +02:00
|
|
|
}
|
2012-06-27 09:27:35 +02:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
print "$type $pid $hook $plugin $message\n";
|
2013-04-21 06:54:43 +02:00
|
|
|
}
|
|
|
|
}
|
2012-06-27 09:27:35 +02:00
|
|
|
|
|
|
|
close $F;
|
2013-04-21 06:54:43 +02:00
|
|
|
}
|
2012-06-27 09:27:35 +02:00
|
|
|
|
|
|
|
sub check_logfile {
|
|
|
|
my $file = shift;
|
|
|
|
my $path = "$logdir/$file";
|
|
|
|
|
2013-04-21 06:54:43 +02:00
|
|
|
die "missing file $logdir/$file" if !-f "$logdir/$file";
|
2012-06-27 09:27:35 +02:00
|
|
|
|
2013-04-21 06:54:43 +02:00
|
|
|
my $inode = stat($path)->ino or die "unable to get inode for $path\n";
|
2012-06-27 09:27:35 +02:00
|
|
|
my $size = stat($path)->size or die "unable to get size for $path\n";
|
|
|
|
my $exists;
|
|
|
|
|
2013-03-27 23:14:23 +01:00
|
|
|
#warn "check if file $file is in the DB as 'current'\n";
|
2013-04-21 06:54:43 +02:00
|
|
|
if ($file =~ /^\@/) {
|
|
|
|
$exists = exec_query('SELECT * FROM log WHERE inode=? AND name=?',
|
|
|
|
[$inode, 'current']);
|
|
|
|
if (@$exists) {
|
2012-06-27 09:27:35 +02:00
|
|
|
print "Updating current -> $file\n";
|
2013-04-21 06:54:43 +02:00
|
|
|
exec_query('UPDATE log SET name=? WHERE inode=? AND name=?',
|
|
|
|
[$file, $inode, 'current']);
|
2014-09-18 03:28:51 +02:00
|
|
|
return $exists->[0]{id}, $exists->[0]{size}; # continue parsing
|
2013-04-21 06:54:43 +02:00
|
|
|
}
|
|
|
|
}
|
2012-06-27 09:27:35 +02:00
|
|
|
|
2013-04-21 06:54:43 +02:00
|
|
|
if ($file eq 'current') {
|
|
|
|
$exists = exec_query('SELECT * FROM log WHERE inode=? AND name=?',
|
|
|
|
[$inode, $file]);
|
|
|
|
if (@$exists) {
|
|
|
|
exec_query('UPDATE log SET size=? WHERE inode=? AND name=?',
|
|
|
|
[$size, $inode, 'current']);
|
2014-09-18 03:28:51 +02:00
|
|
|
return $exists->[0]{id}, $exists->[0]{size}; # continue parsing
|
2013-04-21 06:54:43 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$exists =
|
|
|
|
exec_query('SELECT * FROM log WHERE name=? AND size=?', [$file, $size]);
|
2012-06-27 09:27:35 +02:00
|
|
|
return if @$exists; # log file hasn't changed, ignore it
|
2013-04-21 06:54:43 +02:00
|
|
|
#print Dumper($exists);
|
2012-06-27 09:27:35 +02:00
|
|
|
|
|
|
|
# file is a new one we haven't seen, add to DB and parse
|
2013-03-27 23:14:23 +01:00
|
|
|
my $id = exec_query(
|
2012-06-27 09:27:35 +02:00
|
|
|
'INSERT INTO log SET inode=?, size=?, name=?, created=FROM_UNIXTIME(?)',
|
2013-04-21 06:54:43 +02:00
|
|
|
[$inode, $size, $file, stat($path)->ctime]
|
2012-06-27 09:27:35 +02:00
|
|
|
);
|
|
|
|
print "new file id: $id\n";
|
2014-09-18 03:28:51 +02:00
|
|
|
return $id;
|
2013-04-21 06:54:43 +02:00
|
|
|
}
|
2012-06-27 09:27:35 +02:00
|
|
|
|
|
|
|
sub get_log_dir {
|
|
|
|
|
2013-04-21 06:54:43 +02:00
|
|
|
if (-d "log/main") {
|
2012-06-27 09:27:35 +02:00
|
|
|
my $wd = Cwd::cwd();
|
|
|
|
return "$wd/log/main";
|
2013-04-21 06:54:43 +02:00
|
|
|
}
|
2012-06-27 09:27:35 +02:00
|
|
|
|
2013-04-21 06:54:43 +02:00
|
|
|
foreach my $user (qw/ qpsmtpd smtpd /) {
|
2012-06-27 09:27:35 +02:00
|
|
|
|
2013-04-21 06:54:43 +02:00
|
|
|
my ($homedir) = (getpwnam($user))[7] or next;
|
2012-06-27 09:27:35 +02:00
|
|
|
|
2013-04-21 06:54:43 +02:00
|
|
|
if (-d "$homedir/log") {
|
2012-06-27 09:27:35 +02:00
|
|
|
return "$homedir/log/main";
|
2013-04-21 06:54:43 +02:00
|
|
|
}
|
|
|
|
if (-d "$homedir/smtpd/log") {
|
2012-06-27 09:27:35 +02:00
|
|
|
return "$homedir/smtpd/log/main";
|
2013-04-21 06:54:43 +02:00
|
|
|
}
|
|
|
|
}
|
2012-06-27 09:27:35 +02:00
|
|
|
|
2013-04-21 06:54:43 +02:00
|
|
|
}
|
2012-06-27 09:27:35 +02:00
|
|
|
|
|
|
|
sub get_logfiles {
|
|
|
|
my $dir = shift;
|
|
|
|
|
|
|
|
opendir my $D, $dir or die "unable to open log dir $dir\n";
|
|
|
|
|
|
|
|
my @files;
|
2013-04-21 06:54:43 +02:00
|
|
|
while (defined(my $f = readdir($D))) {
|
|
|
|
next if !-f "$dir/$f"; # ignore anything that's not a file
|
|
|
|
if ($f =~ /^\@.*s$/) {
|
2012-06-27 09:27:35 +02:00
|
|
|
push @files, $f;
|
2013-04-21 06:54:43 +02:00
|
|
|
}
|
2012-06-27 09:27:35 +02:00
|
|
|
}
|
2013-04-21 06:54:43 +02:00
|
|
|
push @files, "current"; # always have this one last
|
2012-06-27 09:27:35 +02:00
|
|
|
|
|
|
|
closedir $D;
|
|
|
|
return @files;
|
2013-04-21 06:54:43 +02:00
|
|
|
}
|
2012-06-27 09:27:35 +02:00
|
|
|
|
|
|
|
sub parse_line {
|
|
|
|
my $line = shift;
|
|
|
|
my ($tai, $pid, $message) = split /\s+/, $line, 3;
|
2013-04-21 06:54:43 +02:00
|
|
|
return if !$message; # garbage in the log file
|
2012-06-27 09:27:35 +02:00
|
|
|
|
|
|
|
# lines seen many times per connection
|
2013-04-21 06:54:43 +02:00
|
|
|
return parse_line_plugin($line) if substr($message, 0, 1) eq '(';
|
2014-09-18 03:28:51 +02:00
|
|
|
return 'dispatch', $pid, undef, undef, $message
|
2013-04-21 06:54:43 +02:00
|
|
|
if substr($message, 0, 12) eq 'dispatching ';
|
2014-09-18 03:28:51 +02:00
|
|
|
return 'queue', $pid, undef, undef, $message
|
2013-04-21 06:54:43 +02:00
|
|
|
if substr($message, 0, 11) eq '250 Queued!';
|
2014-09-18 03:28:51 +02:00
|
|
|
return 'response', $pid, undef, undef, $message
|
2013-04-21 06:54:43 +02:00
|
|
|
if $message =~ /^[2|3]\d\d/;
|
2012-06-27 09:27:35 +02:00
|
|
|
|
|
|
|
# lines seen about once per connection
|
2014-09-18 03:28:51 +02:00
|
|
|
return 'init', $pid, undef, undef, $message
|
2013-04-21 06:54:43 +02:00
|
|
|
if substr($message, 0, 19) eq 'Accepted connection';
|
2014-09-18 03:28:51 +02:00
|
|
|
return 'connect', $pid, undef, undef, substr($message, 16)
|
2013-04-21 06:54:43 +02:00
|
|
|
if substr($message, 0, 15) eq 'Connection from';
|
2014-09-18 03:28:51 +02:00
|
|
|
return 'connect', $pid, undef, undef, substr($message, 16)
|
2013-04-21 06:54:43 +02:00
|
|
|
if substr($message, 0, 8) eq 'connect ';
|
2014-09-18 03:28:51 +02:00
|
|
|
return 'close', $pid, undef, undef, $message
|
2013-04-21 06:54:43 +02:00
|
|
|
if substr($message, 0, 6) eq 'close ';
|
2014-09-18 03:28:51 +02:00
|
|
|
return 'close', $pid, undef, undef, $message
|
2013-04-21 06:54:43 +02:00
|
|
|
if $message eq 'Connection Timed Out';
|
2014-09-18 03:28:51 +02:00
|
|
|
return 'close', $pid, undef, undef, $message
|
2013-04-21 06:54:43 +02:00
|
|
|
if substr($message, 0, 20) eq 'click, disconnecting';
|
|
|
|
return parse_line_cleanup($line)
|
|
|
|
if substr($message, 0, 11) eq 'cleaning up';
|
2012-06-27 09:27:35 +02:00
|
|
|
|
|
|
|
# lines seen less than once per connection
|
2014-09-18 03:28:51 +02:00
|
|
|
return 'info', $pid, undef, undef, $message
|
2013-04-21 06:54:43 +02:00
|
|
|
if $message eq 'spooling message to disk';
|
2014-09-18 03:28:51 +02:00
|
|
|
return 'reject', $pid, undef, undef, $message
|
|
|
|
if $message =~ /^[4|5]\d\d/;
|
|
|
|
return 'reject', $pid, undef, undef, $message
|
2013-04-21 06:54:43 +02:00
|
|
|
if substr($message, 0, 14) eq 'deny mail from';
|
2014-09-18 03:28:51 +02:00
|
|
|
return 'reject', $pid, undef, undef, $message
|
2013-04-21 06:54:43 +02:00
|
|
|
if substr($message, 0, 18) eq 'denysoft mail from';
|
2014-09-18 03:28:51 +02:00
|
|
|
return 'info', $pid, undef, undef, $message
|
2013-04-21 06:54:43 +02:00
|
|
|
if substr($message, 0, 15) eq 'Lost connection';
|
2014-09-18 03:28:51 +02:00
|
|
|
return 'info', $pid, undef, undef, $message
|
2013-04-21 06:54:43 +02:00
|
|
|
if $message eq 'auth success cleared naughty';
|
2014-09-18 03:28:51 +02:00
|
|
|
return 'info', $pid, undef, undef, $message
|
2013-04-21 06:54:43 +02:00
|
|
|
if substr($message, 0, 15) eq 'Running as user';
|
2014-09-18 03:28:51 +02:00
|
|
|
return 'info', $pid, undef, undef, $message
|
2013-04-21 06:54:43 +02:00
|
|
|
if substr($message, 0, 16) eq 'Loaded Qpsmtpd::';
|
2014-09-18 03:28:51 +02:00
|
|
|
return 'info', $pid, undef, undef, $message
|
2013-04-21 06:54:43 +02:00
|
|
|
if substr($message, 0, 24) eq 'Permissions on spool_dir';
|
2014-09-18 03:28:51 +02:00
|
|
|
return 'info', $pid, undef, undef, $message
|
2013-04-21 06:54:43 +02:00
|
|
|
if substr($message, 0, 13) eq 'Listening on ';
|
2014-09-18 03:28:51 +02:00
|
|
|
return 'info', $pid, undef, undef, $message
|
2013-04-21 06:54:43 +02:00
|
|
|
if substr($message, 0, 18) eq 'size_threshold set';
|
2014-09-18 03:28:51 +02:00
|
|
|
return 'info', $pid, undef, undef, $message
|
2013-04-21 06:54:43 +02:00
|
|
|
if substr($message, 0, 12) eq 'tls: ciphers';
|
2014-09-18 03:28:51 +02:00
|
|
|
return 'error', $pid, undef, undef, $message
|
2013-04-21 06:54:43 +02:00
|
|
|
if substr($message, 0, 22) eq 'of uninitialized value';
|
2014-09-18 03:28:51 +02:00
|
|
|
return 'error', $pid, undef, undef, $message
|
2013-04-21 06:54:43 +02:00
|
|
|
if substr($message, 0, 8) eq 'symbol "';
|
2014-09-18 03:28:51 +02:00
|
|
|
return 'error', $pid, undef, undef, $message
|
2013-04-21 06:54:43 +02:00
|
|
|
if substr($message, 0, 9) eq 'error at ';
|
2014-09-18 03:28:51 +02:00
|
|
|
return 'error', $pid, undef, undef, $message
|
2013-04-21 06:54:43 +02:00
|
|
|
if substr($message, 0, 15) eq 'Could not print';
|
2012-06-27 09:27:35 +02:00
|
|
|
|
|
|
|
print "UNKNOWN LINE: $line\n";
|
2014-09-18 03:28:51 +02:00
|
|
|
return 'unknown', $pid, undef, undef, $message;
|
2013-04-21 06:54:43 +02:00
|
|
|
}
|
2012-06-27 09:27:35 +02:00
|
|
|
|
|
|
|
sub parse_line_plugin {
|
|
|
|
my ($line) = @_;
|
|
|
|
|
2013-04-21 06:54:43 +02:00
|
|
|
# @tai 13486 (connect) ident::p0f: Windows (XP/2000 (RFC1323+, w, tstamp-))
|
|
|
|
# @tai 13681 (connect) dnsbl: fail, NAUGHTY
|
|
|
|
# @tai 15787 (connect) karma: pass, no penalty (0 naughty, 3 nice, 3 connects)
|
|
|
|
# @tai 27500 (queue) queue::qmail_2dqueue: (for 27481) Queuing to /var/qmail/bin/qmail-queue
|
|
|
|
my ($tai, $pid, $hook, $plugin, $message) = split /\s/, $line, 5;
|
2012-06-27 09:27:35 +02:00
|
|
|
$plugin =~ s/:$//;
|
|
|
|
|
2013-04-21 06:54:43 +02:00
|
|
|
return parse_line_plugin_p0f($line) if $plugin =~ /^ident::p0f/;
|
|
|
|
return parse_line_plugin_dspam($line) if $plugin =~ /^dspam/;
|
|
|
|
return parse_line_plugin_spamassassin($line) if $plugin =~ /^spamassassin/;
|
2012-06-27 09:27:35 +02:00
|
|
|
|
2013-04-21 06:54:43 +02:00
|
|
|
if ($plugin eq 'sender_permitted_from') {
|
2012-06-27 09:27:35 +02:00
|
|
|
$message = 'pass' if $message =~ /^pass/;
|
|
|
|
$message = 'fail' if $message =~ /^fail/;
|
|
|
|
$message = 'skip' if $message =~ /^none/;
|
|
|
|
}
|
2013-04-21 06:54:43 +02:00
|
|
|
elsif ($plugin eq 'queue::qmail_2dqueue') {
|
2012-06-27 09:27:35 +02:00
|
|
|
($pid) = $message =~ /\(for ([\d]+)\)/;
|
|
|
|
$message = 'pass' if $message =~ /Queuing/;
|
|
|
|
}
|
2013-04-21 06:54:43 +02:00
|
|
|
elsif ($plugin =~ /(?:early|karma|helo|rcpt_ok)/) {
|
2012-06-27 09:27:35 +02:00
|
|
|
$message = 'pass' if $message =~ /^pass/;
|
|
|
|
}
|
2013-04-21 06:54:43 +02:00
|
|
|
elsif ($plugin =~ /resolvable_fromhost/) {
|
2012-06-27 09:27:35 +02:00
|
|
|
$message = 'pass' if $message =~ /^pass/;
|
2013-04-21 06:54:43 +02:00
|
|
|
}
|
2012-06-27 09:27:35 +02:00
|
|
|
|
2014-09-18 03:28:51 +02:00
|
|
|
return 'plugin', $pid, $hook, $plugin, $message;
|
2013-04-21 06:54:43 +02:00
|
|
|
}
|
2012-06-27 09:27:35 +02:00
|
|
|
|
|
|
|
sub parse_line_plugin_dspam {
|
|
|
|
my $line = shift;
|
|
|
|
|
2013-04-21 06:54:43 +02:00
|
|
|
my ($tai, $pid, $hook, $plugin, $message) = split /\s/, $line, 5;
|
2012-06-27 09:27:35 +02:00
|
|
|
$plugin =~ s/:$//;
|
|
|
|
|
2013-04-21 06:54:43 +02:00
|
|
|
if ($message =~ /Innocent, (\d\.\d\d c)/) {
|
2012-06-27 09:27:35 +02:00
|
|
|
$message = "pass, $1";
|
2013-04-21 06:54:43 +02:00
|
|
|
}
|
|
|
|
if ($message =~ /Spam, (\d\.\d\d c)/) {
|
2012-06-27 09:27:35 +02:00
|
|
|
$message = "fail, $1";
|
2013-04-21 06:54:43 +02:00
|
|
|
}
|
2012-06-27 09:27:35 +02:00
|
|
|
|
2014-09-18 03:28:51 +02:00
|
|
|
return 'plugin', $pid, $hook, $plugin, $message;
|
2013-04-21 06:54:43 +02:00
|
|
|
}
|
2012-06-27 09:27:35 +02:00
|
|
|
|
|
|
|
sub parse_line_plugin_spamassassin {
|
|
|
|
my $line = shift;
|
|
|
|
|
2013-04-21 06:54:43 +02:00
|
|
|
my ($tai, $pid, $hook, $plugin, $message) = split /\s/, $line, 5;
|
2012-06-27 09:27:35 +02:00
|
|
|
$plugin =~ s/:$//;
|
|
|
|
|
2013-04-21 06:54:43 +02:00
|
|
|
if ($message =~ /pass, Ham, ([\d\-\.]+)\s/) {
|
2012-06-27 09:27:35 +02:00
|
|
|
$message = "pass, $1";
|
2013-04-21 06:54:43 +02:00
|
|
|
}
|
|
|
|
if ($message =~ /^fail, Spam,\s([\d\.]+)\s< 100/) {
|
2012-06-27 09:27:35 +02:00
|
|
|
$message = "fail, $1";
|
2013-04-21 06:54:43 +02:00
|
|
|
}
|
2013-03-27 23:14:23 +01:00
|
|
|
|
2014-09-18 03:28:51 +02:00
|
|
|
return 'plugin', $pid, $hook, $plugin, $message;
|
2013-04-21 06:54:43 +02:00
|
|
|
}
|
2012-06-27 09:27:35 +02:00
|
|
|
|
|
|
|
sub parse_line_plugin_p0f {
|
|
|
|
my $line = shift;
|
|
|
|
|
2013-04-21 06:54:43 +02:00
|
|
|
my ($tai, $pid, $hook, $plugin, $message) = split /\s/, $line, 5;
|
2012-06-27 09:27:35 +02:00
|
|
|
$plugin =~ s/:$//;
|
|
|
|
|
2013-04-21 06:54:43 +02:00
|
|
|
if (substr($message, -5, 5) eq 'hops)') {
|
|
|
|
($message) = split(/\s\(/, $message);
|
|
|
|
}
|
2012-06-27 09:27:35 +02:00
|
|
|
|
|
|
|
$message = 'iOS' if $message =~ /^iOS/;
|
|
|
|
$message = 'Solaris' if $message =~ /^Solaris/;
|
|
|
|
$message = 'Mac OS X' if $message =~ /^Mac OS X/;
|
|
|
|
$message = 'FreeBSD' if $message =~ /^FreeBSD/;
|
|
|
|
$message = 'Linux' if $message =~ /^Linux/;
|
|
|
|
$message = 'OpenBSD' if $message =~ /^OpenBSD/;
|
|
|
|
$message = 'Windows NT' if $message =~ /^Windows \(?NT/;
|
|
|
|
$message = 'Windows 95' if $message =~ /^Windows \(?95/;
|
|
|
|
$message = 'Windows 98' if $message =~ /^Windows \(?98/;
|
|
|
|
$message = 'Windows XP' if $message =~ /^Windows \(?XP/;
|
|
|
|
$message = 'Windows 2000' if $message =~ /^Windows \(?2000/;
|
|
|
|
$message = 'Windows 2003' if $message =~ /^Windows \(?2003/;
|
|
|
|
$message = 'Windows 7 or 8' if $message =~ /^Windows 7/;
|
|
|
|
$message = 'Windows 7 or 8' if $message =~ /^Windows 8/;
|
|
|
|
$message = 'Google' if $message =~ /^Google/;
|
|
|
|
$message = 'HP-UX' if $message =~ /^HP\-UX/;
|
|
|
|
$message = 'NetCache' if $message =~ /^NetCache/i;
|
|
|
|
$message = 'Cisco' if $message =~ /^Cisco/i;
|
|
|
|
$message = 'Netware' if $message =~ /Netware/i;
|
|
|
|
|
2014-09-18 03:28:51 +02:00
|
|
|
return 'plugin', $pid, $hook, $plugin, $message;
|
2013-04-21 06:54:43 +02:00
|
|
|
}
|
2012-06-27 09:27:35 +02:00
|
|
|
|
|
|
|
sub parse_line_cleanup {
|
|
|
|
my ($line) = @_;
|
2013-04-21 06:54:43 +02:00
|
|
|
|
2012-06-27 09:27:35 +02:00
|
|
|
# @tai 85931 cleaning up after 3210
|
2013-03-27 23:14:23 +01:00
|
|
|
my $pid = (split /\s+/, $line)[-1];
|
2012-06-27 09:27:35 +02:00
|
|
|
$has_cleanup++;
|
2014-09-18 03:28:51 +02:00
|
|
|
return 'cleanup', $pid, undef, undef, $line;
|
2013-04-21 06:54:43 +02:00
|
|
|
}
|
2012-06-27 09:27:35 +02:00
|
|
|
|
|
|
|
sub get_score {
|
|
|
|
my $mess = shift;
|
2013-04-21 06:54:43 +02:00
|
|
|
return 3 if $mess eq 'TLS setup returning';
|
|
|
|
return 3 if $mess =~ /^pass/;
|
|
|
|
return -3 if $mess =~ /^fail/;
|
|
|
|
return -2 if $mess =~ /^negative/;
|
|
|
|
return 2 if $mess =~ /^positive/;
|
|
|
|
return 1 if $mess =~ /^skip/;
|
2012-06-27 09:27:35 +02:00
|
|
|
return 0;
|
2013-04-21 06:54:43 +02:00
|
|
|
}
|
2012-06-27 09:27:35 +02:00
|
|
|
|
|
|
|
sub get_db {
|
|
|
|
|
2013-04-22 08:12:53 +02:00
|
|
|
my %dbv = get_config('log2sql');
|
|
|
|
|
|
|
|
$dbv{dsn} ||= 'DBI:mysql:database=qpsmtpd;host=db;port=3306';
|
|
|
|
$dbv{user} ||= 'qplog';
|
|
|
|
$dbv{pass} ||= 't0ps3cret';
|
|
|
|
|
|
|
|
print Dumper(\%dbv);
|
|
|
|
my $db = DBIx::Simple->connect($dbv{dsn}, $dbv{user}, $dbv{pass})
|
2013-04-21 06:54:43 +02:00
|
|
|
or die DBIx::Simple->error;
|
2012-06-27 09:27:35 +02:00
|
|
|
|
|
|
|
return $db;
|
2013-04-21 06:54:43 +02:00
|
|
|
}
|
2012-06-27 09:27:35 +02:00
|
|
|
|
2013-04-22 08:12:53 +02:00
|
|
|
sub get_config {
|
|
|
|
my $file = shift or die "missing file name\n";
|
|
|
|
my %values;
|
|
|
|
foreach my $line ( get_config_contents( $file ) ) {
|
|
|
|
next if $line =~ /^#/;
|
|
|
|
chomp $line;
|
|
|
|
my ($key,$val) = split /\s*=\s*/, $line, 2;
|
|
|
|
$values{$key} = $val;
|
|
|
|
};
|
|
|
|
return %values;
|
|
|
|
};
|
|
|
|
|
|
|
|
sub get_config_contents {
|
|
|
|
my $name = shift;
|
|
|
|
|
|
|
|
my @config_dirs = qw[ config ../config log plugins ];
|
|
|
|
foreach my $dir ( @config_dirs ) {
|
|
|
|
next if ! -f "$dir/$name";
|
|
|
|
|
|
|
|
my $fh = IO::File->new();
|
|
|
|
if ( ! $fh->open( "$dir/$name", '<' ) ) {
|
|
|
|
warn "unable to open config file $dir/$name\n";
|
|
|
|
next;
|
|
|
|
};
|
|
|
|
my @contents = <$fh>;
|
|
|
|
return @contents;
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
sub check_plugins_table {
|
|
|
|
my $rows = exec_query( 'SELECT COUNT(*) FROM plugin');
|
|
|
|
return if scalar @$rows != 0;
|
|
|
|
my @lines = get_config_contents('registry.txt');
|
|
|
|
foreach my $line ( @lines ) {
|
|
|
|
next if $line =~ /^\s*#/; # ignore comments
|
|
|
|
chomp $line;
|
|
|
|
next if ! $line;
|
|
|
|
my ($id, $name, $abb3, $abb5, $aliases) = split /\s+/, $line, 5;
|
|
|
|
my $q = "REPLACE INTO plugin (id,name,abb3,abb5) VALUES (??)";
|
|
|
|
print "query: $q, $id, $name, $abb3, $abb5\n";
|
|
|
|
exec_query($q, [$id, $name, $abb3, $abb5 ]);
|
|
|
|
next if ! $aliases;
|
|
|
|
foreach my $alias ( split /\s*,\s*/, $aliases ) {
|
|
|
|
next if ! $alias;
|
|
|
|
my $aq = "REPLACE INTO plugin_aliases (plugin_id,name) VALUES (??)";
|
|
|
|
print "aqury: $aq, $id, $alias\n";
|
|
|
|
exec_query($aq, [$id, $alias]);
|
|
|
|
};
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2012-06-27 09:27:35 +02:00
|
|
|
sub exec_query {
|
2013-04-21 06:54:43 +02:00
|
|
|
my $query = shift;
|
2012-06-27 09:27:35 +02:00
|
|
|
my $params = shift;
|
|
|
|
die "invalid arguments to exec_query!" if @_;
|
|
|
|
my @params;
|
2013-04-21 06:54:43 +02:00
|
|
|
if (defined $params) {
|
2012-06-27 09:27:35 +02:00
|
|
|
@params = ref $params eq 'ARRAY' ? @$params : $params;
|
2013-04-21 06:54:43 +02:00
|
|
|
}
|
2012-06-27 09:27:35 +02:00
|
|
|
|
|
|
|
my $err = "query failed: $query\n";
|
2013-04-21 06:54:43 +02:00
|
|
|
if (scalar @params) {
|
2012-06-27 09:27:35 +02:00
|
|
|
$err .= join(',', @params);
|
2013-04-21 06:54:43 +02:00
|
|
|
}
|
2012-06-27 09:27:35 +02:00
|
|
|
|
2013-03-27 23:14:23 +01:00
|
|
|
#warn "err: $err\n";
|
2013-04-22 08:12:53 +02:00
|
|
|
if ($query =~ /(?:REPLACE|INSERT) INTO/) {
|
|
|
|
my ($table) = $query =~ /(?:REPLACE|INSERT) INTO (\w+)\s/;
|
2013-04-21 06:54:43 +02:00
|
|
|
$db->query($query, @params);
|
2013-04-22 08:12:53 +02:00
|
|
|
warn "$db->error\n$err" if $db->error ne 'DBI error: ';
|
|
|
|
return if $query =~ /^REPLACE/;
|
2013-04-21 06:54:43 +02:00
|
|
|
my $id = $db->last_insert_id(undef, undef, $table, undef) or die $err;
|
2012-06-27 09:27:35 +02:00
|
|
|
return $id;
|
|
|
|
}
|
2013-04-21 06:54:43 +02:00
|
|
|
elsif ($query =~ /^UPDATE/i) {
|
|
|
|
return $db->query($query, @params);
|
2013-03-27 23:14:23 +01:00
|
|
|
}
|
2013-04-21 06:54:43 +02:00
|
|
|
elsif ($query =~ /DELETE/) {
|
|
|
|
$db->query($query, @params) or die $err;
|
2012-06-27 09:27:35 +02:00
|
|
|
return $db->query("SELECT ROW_COUNT()")->list;
|
2013-04-21 06:54:43 +02:00
|
|
|
}
|
2012-06-27 09:27:35 +02:00
|
|
|
|
2013-04-21 06:54:43 +02:00
|
|
|
my $r = $db->query($query, @params)->hashes or die $err;
|
2012-06-27 09:27:35 +02:00
|
|
|
return $r;
|
2013-04-21 06:54:43 +02:00
|
|
|
}
|
2012-06-27 09:27:35 +02:00
|
|
|
|