* plugins/virus/clamav

Scan temporary file directly now that the spooled file includes the
    entire message

*   plugins/virus/bitdefender - John Peacock
    plugins/virus/hbedv - Hanno Hecker
    New AV plugins


git-svn-id: https://svn.perl.org/qpsmtpd/trunk@377 958fd67b-6ff1-0310-b445-bb7760255be9
This commit is contained in:
John Peacock 2005-03-01 19:55:18 +00:00
parent ec7aff1415
commit 889845af24
3 changed files with 298 additions and 17 deletions

134
plugins/virus/bitdefender Normal file
View File

@ -0,0 +1,134 @@
#!/usr/bin/perl -Tw
=head1 NAME
bitdefender -- BitDefender Linux Edition antivirus plugin for qpsmtpd
=head1 DESCRIPTION
This plugin scans incoming mail with the BitDefender Linux Edition scanner,
and can at your option reject or flag infected messages.
=head1 CONFIGURATION
=over 4
=item B<bitdefender_location>
Full path to the BitDefender binary and all signature files; defaults to
/opt/bdc/bdc.
=item B<deny_viruses>
Whether the scanner will automatically delete messages which have viruses.
Takes either 'yes' or 'no' (defaults to 'yes').
=item B<max_size>
Maximum size in kilobytes for messages which will be scanned; defaults to 128k;
=back
=head1 DEPENDENCIES
=over 4
=item B<BitDefender>
The BitDefender Linux Edition is available to use, free of charge, from
this link:
<http://www.bitdefender.com/bd/site/products.php?p_id=16>
Please read the documentation for configuring automatic updates of the
virus profiles.
=back
=head1 AUTHOR
John Peacock <jpeacock@cpan.org>
=head1 COPYRIGHT AND LICENSE
Copyright (c) 2004 John Peacock
Based lightly on the clamav plugin
This plugin is licensed under the same terms as the qpsmtpd package itself.
Please see the LICENSE file included with qpsmtpd for details.
=cut
use File::Path;
use strict;
use warnings;
sub register {
my ( $self, $qp, @args ) = @_;
$self->register_hook( "data_post", "bdc_scan" );
while (@args) {
$self->{"_bitd"}->{ pop @args } = pop @args;
}
$self->{"_bitd"}->{"bitdefender_location"} ||= "/opt/bdc/bdc";
$self->{"_bitd"}->{"deny_viruses"} ||= "yes";
$self->{"_bitd"}->{"max_size"} ||= 128;
$self->{"_bitd"}->{"max_size"} *= 1024;
}
sub bdc_scan {
my ( $self, $transaction ) = @_;
if ( $transaction->body_size > $self->{"_bitd"}->{"max_size"} ) {
$self->log( LOGWARN,
'Mail too large to scan ('
. $transaction->body_size . " vs "
. $self->{"_bitd"}->{"max_size"}
. ")" );
return (DECLINED);
}
# Ignore non-multipart emails
my $content_type = $transaction->header->get('Content-Type');
$content_type =~ s/\s/ /g if defined $content_type;
unless ( $content_type
&& $content_type =~ m!\bmultipart/.*\bboundary="?([^"]+)!i )
{
$self->log( LOGERROR, "non-multipart mail - skipping" );
return DECLINED;
}
my $filename = $transaction->body_filename;
unless (defined $filename) {
$self->log(LOGERROR, "didn't get a filename");
return DECLINED;
}
# Now do the actual scanning!
open my $bdc, "-|",
$self->{"_bitd"}->{"bitdefender_location"}
. " --mail --all --arc $filename";
my $output;
while (<$bdc>) {
if (/infected: (.+)$/) {
$output = $1;
last;
}
}
close $bdc;
if ($output) {
$self->log( LOGINFO, "Virus(es) found: $output" );
if ( $self->{"_bitd"}->{"deny_viruses"} eq "yes" ) {
return ( DENY, "Virus Found: $output" );
}
}
return (DECLINED);
}
1;

View File

@ -101,8 +101,6 @@ Please see the LICENSE file included with qpsmtpd for details.
=cut =cut
use File::Temp qw(tempfile);
use strict; use strict;
use warnings; use warnings;
@ -156,28 +154,18 @@ sub register {
sub clam_scan { sub clam_scan {
my ($self, $transaction) = @_; my ($self, $transaction) = @_;
if ($transaction->body_size > $self->{_max_size}) { if ($transaction->body_size > $self->{_max_size}) {
$self->log(LOGWARN, 'Mail too large to scan ('. $self->log(LOGWARN, 'Mail too large to scan ('.
$transaction->body_size . " vs $self->{_max_size})" ); $transaction->body_size . " vs $self->{_max_size})" );
return (DECLINED); return (DECLINED);
} }
my ($temp_fh, $filename) = tempfile("qpsmtpd.clamav.$$.XXXXXX", my $filename = $transaction->body_filename;
DIR => $self->{_spool_dir}); unless (defined $filename) {
unless ($temp_fh) { $self->log(LOGERROR, "didn't get a filename");
$self->logerror("Couldn't open tempfile in $self->{_spool_dir}: $!");
return DECLINED; return DECLINED;
} }
print $temp_fh "From ",
$transaction->sender->format, " " , scalar gmtime, "\n";
print $temp_fh $transaction->header->as_string, "\n";
$transaction->body_resetpos;
while (my $line = $transaction->body_getline) {
print $temp_fh $line;
}
seek($temp_fh, 0, 0);
my $mode = (stat($self->{_spool_dir}))[2]; my $mode = (stat($self->{_spool_dir}))[2];
if ( $mode & 07077 ) { # must be sharing spool directory with external app if ( $mode & 07077 ) { # must be sharing spool directory with external app
$self->log(LOGWARN, $self->log(LOGWARN,
@ -195,7 +183,6 @@ sub clam_scan {
my $result = ($? >> 8); my $result = ($? >> 8);
my $signal = ($? & 127); my $signal = ($? & 127);
unlink($filename);
chomp($output); chomp($output);
$output =~ s/^.* (.*) FOUND$/$1 /mg; $output =~ s/^.* (.*) FOUND$/$1 /mg;

160
plugins/virus/hbedv Normal file
View File

@ -0,0 +1,160 @@
#!/usr/bin/perl -w
# H+B EDV-AV plugin.
#
=head1 NAME
hbedv - plugin for qpsmtpd which calls the H+BEDV anti virus scanner
=head1 DESCRIPTION
The B<hbedv> plugin checks a mail for viruses with the H+BEDV anti virus
scanner (see L<http://www.antivir.de/> for info). It can deny mails if a
virus was found with a configurable deny list.
=head1 VERSION
this is B<hbedv> version 1.1
=head1 CONFIGURATION
Add (perl-)regexps to the F<hbedv_deny> configuration file, one per line for the
virii you want to block, e.g.:
Worm\/Sober\..*
Worm\/NetSky\..*
or just
.*
to block any virus ;)
Set the location of the binary with
hbedv hbedvscanner /path/to/antivir
in the plugin config if qpsmtpd, the location defaults to I</usr/bin/antivir>.
=head1 NOTES
If the hbedv_deny config file is empty or could not be found, any virus
will be blocked.
This plugin started life as a copy of the B<clamav> plugin.
=head1 LICENCE
Written by Hanno Hecker E<lt>hah@uu-x.deE<gt>.
The B<hbedv> plugin is published under the same licence as qpsmtpd itself.
=cut
sub register {
my ($self, $qp, @args) = @_;
$self->register_hook("data_post", "hbedv_scan");
if (@args % 2) {
$self->log(LOGERROR, "FATAL ERROR: odd number of arguments");
exit 3;
}
my %args = @args;
if (!exists $args{hbedvscanner}) {
$self->{_hbedvscan_loc} = "/usr/bin/antivir";
} else {
if ($args{hbedvscanner} =~ /^(\/[\/\-\_\.a-z0-9A-Z]*)$/) {
$self->{_hbedvscan_loc} = $1;
} else {
$self->log(LOGERROR, "FATAL ERROR: Unexpected characters in hbedvscanner argument");
exit 3;
}
}
}
sub hbedv_scan {
my ($self, $transaction) = @_;
my $filename = $transaction->body_filename;
unless (defined $filename) {
$self->log(LOGWARN, "didn't get a file name");
return (DECLINED);
}
# Now do the actual scanning!
my $cmd = $self->{_hbedvscan_loc}." --archive-max-recursion=50 --alltypes -z -noboot -nombr -rs $filename 2>&1";
$self->log(LOGDEBUG, "Running: $cmd");
my @output = `$cmd`;
my $result = ($? >> 8);
my $signal = ($? & 127);
chomp(@output);
my @virii = ();
foreach my $line (@output) {
next unless $line =~ /^ALERT: \[([^\]]+)\s+(\w+)?\]/; # $2 =~ /^(virus|worm)$/;
push @virii, $1;
}
@virii = unique(@virii);
$self->log(LOGDEBUG, "results: ".join("//",@output));
if ($signal) {
$self->log(LOGWARN, "scanner exited with signal: $signal");
return (DECLINED);
}
my $output = join(", ", @virii);
$output = substr($output, 0, 60);
if ($result == 1 || $result == 3) {
$self->log(LOGWARN, "Virus(es) found: $output");
# return (DENY, "Virus Found: $output");
# $transaction->header->add('X-Virus-Found', 'Yes', 0);
# $transaction->header->add('X-Virus-Details', $output, 0);
$transaction->header->add('X-H+BEDV-Virus-Found', 'Yes', 0);
$transaction->header->add('X-H+BEDV-Virus-Details', $output, 0);
}
elsif ($result == 200) {
$self->log(LOGWARN, "Program aborted, not enough memory available");
}
elsif ($result == 211) {
$self->log(LOGWARN, "Programm aborted, because the self check failed");
}
elsif ($result == 214) {
$self->log(LOGWARN, "License key not found");
}
elsif ($result) {
$self->log(LOGWARN, "Error: $result, look for exit codes in the output of '"
.$self->{_hbedvscan_loc}." --help' for more info\n");
}
# $transaction->header->add('X-Virus-Checked', 'Checked', 0);
$transaction->header->add('X-H+BEDV-Virus-Checked', 'Checked', 0);
return (DECLINED) unless $result;
if (@virii) {
return(DENY, "Virus found: $output")
unless $self->qp->config("hbedv_deny");
foreach my $d ($self->qp->config("hbedv_deny")) {
foreach my $v (@virii) {
if ($v =~ /^$d$/i) {
$self->log(LOGWARN, "Denying mail with virus '$v'");
return(DENY, "Virus found: $output");
}
}
}
}
return (DECLINED);
}
sub unique {
## This is the short version, I haven't tried if any warnings
## are generated by perl if you use just this... if you need
## every cpu cycle, try this:
## my %h;foreach (@_) { ++$h{$_}; }; return keys(%h);
my @list = @_;
my %hash;
foreach my $item (@list) {
exists $hash{$item} || ($hash{$item} = 1);
}
return keys(%hash)
}