add kavscanner plugin (thanks to Hanno Hecker)
move clamav, check_for_hi_virus and klez_filter to virus/ (did anyone mentino subversion?) git-svn-id: https://svn.perl.org/qpsmtpd/trunk@255 958fd67b-6ff1-0310-b445-bb7760255be9
This commit is contained in:
parent
f5a0a0998a
commit
8c059e38ef
44
plugins/virus/check_for_hi_virus
Normal file
44
plugins/virus/check_for_hi_virus
Normal file
@ -0,0 +1,44 @@
|
||||
#!/usr/bin/perl -w
|
||||
|
||||
sub register {
|
||||
my $self = shift;
|
||||
$self->register_hook('data_post', 'check_for_hi_virus');
|
||||
}
|
||||
|
||||
sub check_for_hi_virus {
|
||||
my ($self, $transaction) = @_;
|
||||
|
||||
# make sure we read from the beginning;
|
||||
$transaction->body_resetpos;
|
||||
|
||||
my $line_number = 0;
|
||||
my $seen_file = 0;
|
||||
my $ct_filename = '';
|
||||
my $cd_filename = '';
|
||||
|
||||
while ($_ = $transaction->body_getline) {
|
||||
last if $line_number++ > 40;
|
||||
if (/^Content-Type: (.*)/) {
|
||||
my $val = $1;
|
||||
if ($val =~ /name="(.*)"/) {
|
||||
$seen_file = 1;
|
||||
$ct_filename = $1;
|
||||
}
|
||||
}
|
||||
if (/^Content-Disposition: (.*)/) {
|
||||
my $val = $1;
|
||||
if ($val =~ /filename="(.*)"/) {
|
||||
$seen_file = 1;
|
||||
$cd_filename = $1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($seen_file and $ct_filename and $cd_filename) {
|
||||
if ($ct_filename ne $cd_filename) {
|
||||
return (DENY, "Probably the 'Hi' virus");
|
||||
}
|
||||
}
|
||||
|
||||
return DECLINED;
|
||||
}
|
66
plugins/virus/clamav
Normal file
66
plugins/virus/clamav
Normal file
@ -0,0 +1,66 @@
|
||||
#!/usr/bin/perl -w
|
||||
# Clam-AV plugin.
|
||||
|
||||
use File::Temp qw(tempfile);
|
||||
|
||||
sub register {
|
||||
my ($self, $qp, @args) = @_;
|
||||
$self->register_hook("data_post", "clam_scan");
|
||||
|
||||
if (@args > 0) {
|
||||
# Untaint scanner location
|
||||
if ($args[0] =~ /^(\/[\/\-\_\.a-z0-9A-Z]*)$/) {
|
||||
$self->{_clamscan_loc} = $1;
|
||||
} else {
|
||||
$self->log(LOGERROR, "FATAL ERROR: Unexpected characters in clamav argument 1");
|
||||
exit 3;
|
||||
}
|
||||
$self->log(LOGWARN, "WARNING: Ignoring additional arguments.") if (@args > 1);
|
||||
} else {
|
||||
$self->{_clamscan_loc} = "/usr/local/bin/clamscan";
|
||||
}
|
||||
}
|
||||
|
||||
sub clam_scan {
|
||||
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->{_clamscan_loc}." --stdout -i --max-recursion=50 --disable-summary $filename 2>&1";
|
||||
$self->log(LOGDEBUG, "Running: $cmd");
|
||||
my $output = `$cmd`;
|
||||
|
||||
my $result = ($? >> 8);
|
||||
my $signal = ($? & 127);
|
||||
|
||||
unlink($filename);
|
||||
chomp($output);
|
||||
|
||||
$output =~ s/^.* (.*) FOUND$/$1 /mg;
|
||||
|
||||
$self->log(LOGDEBUG, "clamscan results: $output");
|
||||
|
||||
if ($signal) {
|
||||
$self->log(LOGINFO, "clamscan exited with signal: $signal");
|
||||
return (DECLINED);
|
||||
}
|
||||
if ($result == 1) {
|
||||
$self->log(LOGINFO, "Virus(es) found");
|
||||
# return (DENY, "Virus Found: $output");
|
||||
$transaction->header->add('X-Virus-Found', 'Yes');
|
||||
$transaction->header->add('X-Virus-Details', $output);
|
||||
}
|
||||
elsif ($result) {
|
||||
$self->log(LOGWARN, "ClamAV error: $result\n");
|
||||
}
|
||||
$transaction->header->add('X-Virus-Checked', 'Checked');
|
||||
return (DECLINED);
|
||||
}
|
178
plugins/virus/kavscanner
Normal file
178
plugins/virus/kavscanner
Normal file
@ -0,0 +1,178 @@
|
||||
#!/usr/bin/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;
|
||||
|
||||
sub register {
|
||||
my ($self, $qp, @args) = @_;
|
||||
$self->register_hook("data_post", "kav_scan");
|
||||
|
||||
if (@args % 2) {
|
||||
warn "kavscanner: Wrong number of arguments";
|
||||
$self->{_kavscanner_bin} = "/opt/AVP/kavscanner";
|
||||
} 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(1, "FATAL ERROR: Unexpected characters in kavscanner argument");
|
||||
exit 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub kav_scan {
|
||||
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(1, "Running: $cmd");
|
||||
my @output = `$cmd`;
|
||||
chomp(@output);
|
||||
|
||||
my $result = ($? >> 8);
|
||||
my $signal = ($? & 127);
|
||||
|
||||
unlink($filename);
|
||||
close $temp_fh;
|
||||
|
||||
if ($signal) {
|
||||
$self->log(1, "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(1, "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($_->address);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$self->log(0, "corrupt or unknown Kaspersky scanner/resource problems - exit status $result");
|
||||
}
|
||||
}
|
||||
|
||||
$self->log(1, "kavscanner results: $description");
|
||||
|
||||
$transaction->header->add('X-Virus-Checked', 'Checked by '.$self->qp->config("me"));
|
||||
return (DECLINED);
|
||||
}
|
||||
|
||||
# vim: ts=2 sw=2 expandtab
|
37
plugins/virus/klez_filter
Normal file
37
plugins/virus/klez_filter
Normal file
@ -0,0 +1,37 @@
|
||||
sub register {
|
||||
my ($self, $qp) = @_;
|
||||
$self->register_hook("data_post", "check_klez");
|
||||
}
|
||||
|
||||
sub check_klez {
|
||||
my ($self, $transaction) = @_;
|
||||
|
||||
# klez files are always sorta big .. how big? Dunno.
|
||||
return (DECLINED)
|
||||
if $transaction->body_size < 60_000;
|
||||
# 220k was too little, so let's just disable the "big size check"
|
||||
# or $transaction->body_size > 1_000_000;
|
||||
|
||||
# maybe it would be worthwhile to add a check for
|
||||
# Content-Type: multipart/alternative; here?
|
||||
|
||||
# make sure we read from the beginning;
|
||||
$transaction->body_resetpos;
|
||||
|
||||
my $line_number = 0;
|
||||
my $seen_klez_signature = 0;
|
||||
|
||||
while ($_ = $transaction->body_getline) {
|
||||
last if $line_number++ > 40;
|
||||
|
||||
m/^Content-type:.*(?:audio|application)/i
|
||||
and ++$seen_klez_signature and next;
|
||||
|
||||
return (DENY, "Klez Virus Detected")
|
||||
if $seen_klez_signature
|
||||
and m!^TVqQAAMAAAAEAAAA//8AALgAAAAAAAAAQA!;
|
||||
|
||||
}
|
||||
|
||||
return (DECLINED);
|
||||
}
|
Loading…
Reference in New Issue
Block a user