2012-06-22 11:38:01 +02:00
|
|
|
#!perl -w
|
|
|
|
# Kasperski-AV plugin.
|
|
|
|
|
|
|
|
=head1 NAME
|
|
|
|
|
|
|
|
kavscanner - plugin for qpsmtpd which calls the Kasperski anti virus scanner
|
|
|
|
|
|
|
|
=head1 DESCRIPTION
|
|
|
|
|
|
|
|
Check a mail with the B<kavscanner> and deny if it matches a configured virus
|
|
|
|
list.
|
|
|
|
|
|
|
|
=head1 VERSION
|
|
|
|
|
|
|
|
this is B<kavscanner> version 1.0
|
|
|
|
|
|
|
|
=head1 CONFIGURATION
|
|
|
|
|
|
|
|
Add (perl-)regexps to the F<kav_deny> configuration file, one per line for the
|
|
|
|
virii you want to block, e.g.:
|
|
|
|
|
|
|
|
I-Worm\.Sober\..*
|
|
|
|
I-Worm\.NetSky\..*
|
|
|
|
|
|
|
|
NOTE: untested and disabled currently, need volunteers :-)
|
|
|
|
|
|
|
|
If this list does not match the virus found in the mail, you may set
|
|
|
|
I<bcc_virusadmin viradm@your.company.com> in the plugin config to send a
|
|
|
|
B<Bcc:> to the given mail address, i.e. the line
|
|
|
|
|
|
|
|
kavscanner bcc_virusadmin viradm@your.company.com
|
|
|
|
|
|
|
|
in the F<config/plugin> file instead of just
|
|
|
|
|
|
|
|
kavscanner
|
|
|
|
|
|
|
|
Set the location of the binary with
|
|
|
|
|
|
|
|
kavscanner kavscanner_bin /path/to/kavscanner
|
|
|
|
|
|
|
|
(default: F</opt/AVP/kavscanner>), NOTE: this may be broken, you want to
|
|
|
|
set B<kavscanner_bin> explicitly ;-)
|
|
|
|
|
|
|
|
=head1 NOTES
|
|
|
|
|
|
|
|
This is a merge of the clam_av plugin for qpsmtpd and qmail-scanner-queue.pl
|
|
|
|
L<http://qmail-scanner.sourceforge.net/> with my own improvements ;-)
|
|
|
|
Only tested with kavscanner 4.0.x, and bcc_virusadmin untested, as we have no
|
|
|
|
use for it currently. I wait for an official change in Qpsmtpd::Transaction
|
|
|
|
(reset/set the RCPT TO list) to activate and test the currently disabled
|
|
|
|
B<to_virusadmin> option.
|
|
|
|
|
|
|
|
=cut
|
|
|
|
|
|
|
|
use File::Temp qw(tempfile);
|
|
|
|
use Mail::Address;
|
2013-04-21 06:50:39 +02:00
|
|
|
|
2012-06-22 11:38:01 +02:00
|
|
|
sub register {
|
2013-04-21 06:50:39 +02:00
|
|
|
my ($self, $qp, @args) = @_;
|
|
|
|
|
|
|
|
if (@args % 2) {
|
|
|
|
$self->log(LOGWARN, "kavscanner: Wrong number of arguments");
|
|
|
|
$self->{_kavscanner_bin} = "/opt/AVP/kavscanner";
|
2012-06-22 11:38:01 +02:00
|
|
|
}
|
2013-04-21 06:50:39 +02:00
|
|
|
else {
|
|
|
|
my %args = @args;
|
|
|
|
foreach my $key (keys %args) {
|
|
|
|
my $arg = $key;
|
|
|
|
$key =~ s/^/_/;
|
|
|
|
$self->{$key} = $args{$arg};
|
|
|
|
}
|
|
|
|
|
|
|
|
# Untaint scanner location
|
|
|
|
if (exists $self->{_kavscanner_bin}
|
|
|
|
&& $self->{_kavscanner_bin} =~ /^(\/[\/\-\_\.a-z0-9A-Z]*)$/)
|
|
|
|
{
|
|
|
|
$self->{_kavscanner_bin} = $1;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$self->log(LOGALERT,
|
|
|
|
"FATAL ERROR: Unexpected characters in kavscanner argument");
|
|
|
|
exit 3;
|
|
|
|
}
|
2012-06-22 11:38:01 +02:00
|
|
|
}
|
|
|
|
}
|
2013-04-21 06:50:39 +02:00
|
|
|
|
2012-06-22 11:38:01 +02:00
|
|
|
sub hook_data_post {
|
2013-04-21 06:50:39 +02:00
|
|
|
my ($self, $transaction) = @_;
|
|
|
|
|
|
|
|
my ($temp_fh, $filename) = tempfile();
|
|
|
|
print $temp_fh $transaction->header->as_string;
|
|
|
|
print $temp_fh "\n";
|
|
|
|
$transaction->body_resetpos;
|
|
|
|
while (my $line = $transaction->body_getline) {
|
|
|
|
print $temp_fh $line;
|
|
|
|
}
|
|
|
|
seek($temp_fh, 0, 0);
|
|
|
|
|
|
|
|
# Now do the actual scanning!
|
|
|
|
my $cmd = $self->{_kavscanner_bin} . " -Y -P -B -MP -MD -* $filename 2>&1";
|
|
|
|
$self->log(LOGNOTICE, "Running: $cmd");
|
|
|
|
my @output = `$cmd`;
|
|
|
|
chomp(@output);
|
|
|
|
|
|
|
|
my $result = ($? >> 8);
|
|
|
|
my $signal = ($? & 127);
|
|
|
|
|
|
|
|
unlink($filename);
|
|
|
|
close $temp_fh;
|
|
|
|
|
|
|
|
if ($signal) {
|
|
|
|
$self->log(LOGWARN, "kavscanner exited with signal: $signal");
|
|
|
|
return (DECLINED);
|
|
|
|
}
|
|
|
|
|
|
|
|
my $description = 'clean';
|
|
|
|
my @infected = ();
|
|
|
|
my @suspicious = ();
|
|
|
|
if ($result > 0) {
|
|
|
|
if ($result =~ /^(2|3|4|8)$/) {
|
|
|
|
foreach (@output) {
|
|
|
|
if (/^.* infected: (.*)$/) {
|
|
|
|
|
|
|
|
# This covers the specific
|
|
|
|
push @infected, $1;
|
|
|
|
}
|
|
|
|
elsif (/^\s*.* suspicion: (.*)$/) {
|
|
|
|
|
|
|
|
# This covers the potential viruses
|
|
|
|
push @suspicious, $1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$description =
|
|
|
|
"infected by: "
|
|
|
|
. join(", ", @infected) . "; "
|
|
|
|
. "suspicions: "
|
|
|
|
. join(", ", @suspicious);
|
|
|
|
|
|
|
|
# else we may get a veeeery long X-Virus-Details: line or log entry
|
|
|
|
$description = substr($description, 0, 60);
|
|
|
|
$self->log(LOGWARN, "There be a virus! ($description)");
|
|
|
|
### Untested by now, need volunteers ;-)
|
|
|
|
#if ($self->qp->config("kav_deny")) {
|
|
|
|
# foreach my $d (keys %{$self->qp->config("kav_deny", "map")}) {
|
|
|
|
# foreach my $v (@infected) {
|
|
|
|
# return(DENY, "Virus found: $description")
|
|
|
|
# if ($v =~ /^$d$/i);
|
|
|
|
# }
|
|
|
|
# foreach my $s (@suspicious) {
|
|
|
|
# return(DENY, "Virus found: $description")
|
|
|
|
# if ($s =~ /^$d$/i);
|
|
|
|
# }
|
|
|
|
# }
|
|
|
|
#}
|
|
|
|
$transaction->header->add('X-Virus-Found', 'Yes');
|
|
|
|
$transaction->header->add('X-Virus-Details', $description);
|
|
|
|
### maybe the spamassassin plugin can skip this mail if a virus
|
|
|
|
### was found (and $transaction->notes('virus_flag') exists :))
|
|
|
|
### ...ok, works with our spamassassin plugin version
|
|
|
|
### -- hah
|
|
|
|
$transaction->notes('virus', $description);
|
|
|
|
$transaction->notes('virus_flag', 'Yes');
|
|
|
|
|
|
|
|
#### requires modification of Qpsmtpd/Transaction.pm:
|
|
|
|
# if ($self->{_to_virusadmin}) {
|
|
|
|
# my @addrs = ();
|
|
|
|
# foreach (@{$transaction->recipients}) {
|
|
|
|
# push @addr, $_->address;
|
|
|
|
# }
|
|
|
|
# $transaction->header->add('X-Virus-Orig-RcptTo', join(", ", @addrs));
|
|
|
|
# $transaction->set_recipients(@{ Mail::Address->parse($self->{_to_virusadmin}) });
|
|
|
|
# } elsif ($self->{_bcc_virusadmin}) {
|
|
|
|
if ($self->{_bcc_virusadmin}) {
|
|
|
|
foreach (@{Mail::Address->parse($self->{_bcc_virusadmin})}) {
|
|
|
|
$transaction->add_recipient($_);
|
|
|
|
}
|
|
|
|
}
|
2012-06-22 11:38:01 +02:00
|
|
|
}
|
2013-04-21 06:50:39 +02:00
|
|
|
else {
|
|
|
|
$self->log(LOGEMERG,
|
|
|
|
"corrupt or unknown Kaspersky scanner/resource problems - exit status $result"
|
|
|
|
);
|
2012-06-22 11:38:01 +02:00
|
|
|
}
|
|
|
|
}
|
2013-04-21 06:50:39 +02:00
|
|
|
|
|
|
|
$self->log(LOGINFO, "kavscanner results: $description");
|
|
|
|
|
|
|
|
$transaction->header->add('X-Virus-Checked',
|
|
|
|
'Checked by ' . $self->qp->config("me"));
|
|
|
|
return (DECLINED);
|
|
|
|
}
|
2012-06-22 11:38:01 +02:00
|
|
|
|