diff --git a/Changes b/Changes index 5ff6eda..571b500 100644 --- a/Changes +++ b/Changes @@ -5,6 +5,8 @@ Mail::Address does RFC822 addresses, we need SMTP addresses. Replace Mail::Address with Peter J. Holzer's Qpsmtpd::Address module. + Add "plugin/virus/uvscan" - McAfee commandline virus scanner + 0.28 - 2004/06/05 diff --git a/plugins/virus/uvscan b/plugins/virus/uvscan new file mode 100644 index 0000000..b706db2 --- /dev/null +++ b/plugins/virus/uvscan @@ -0,0 +1,124 @@ +#!/usr/bin/perl -w +=head1 NAME + +uvscan + +=head1 DESCRIPTION + +A qpsmtpd plugin for the McAfee commandline virus scanner, uvscan. + +=head1 INSTALL AND CONFIG + +Place this plugin in the plugin/virus directory beneath the standard +qpsmtpd installation. If you installed uvscan with the default path, you +can use this plugin with default options (nothing specified): + +=over 4 + +=item B + +Full path to the uvscan binary and all signature files; defaults to +/usr/local/bin/uvscan. + +=item B + +Whether the scanner will automatically delete messages which have viruses. +Takes either 'yes' or 'no' (defaults to 'yes'). + +=back + +=head1 AUTHOR + +John Peacock + +=head1 COPYRIGHT AND LICENSE + +Copyright (c) 2004 John Peacock + +Based heavily 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 + +sub register { + my ($self, $qp, @args) = @_; + $self->register_hook("data_post", "uvscan"); + + while (@args) { + $self->{"_uvscan"}->{pop @args}=pop @args; + } + $self->{"_uvscan"}->{"uvscan_location"}||="/usr/local/bin/uvscan"; +} + +sub uvscan { + my ($self, $transaction) = @_; + + return (DECLINED) + if $transaction->body_size > 250_000; + + my $filename = $transaction->body_filename; + return (DECLINED) unless $filename; + + # Now do the actual scanning! + my @cmd =($self->{"_uvscan"}->{"uvscan_location"}, + '--mime', '--unzip', '--secure', '--noboot', + $filename, '2>&1 |'); + $self->log(LOGINFO, "Running: ",join(' ', @cmd)); + open(FILE, join(' ', @cmd)); #perl 5.6 doesn't properly support the pipe + # mode list form of open, but this is basically the same thing. This form + # of exec is safe(ish). + my $output; + while () { $output.=$_; } + close FILE; + + my $result = ($? >> 8); + my $signal = ($? & 127); + + unlink($filename); + + my $virus; + if ($output && $output =~ m/.*\W+Found (.*)\n/m) { + $virus=$1; + } + if ($output && $output =~ m/password-protected/m) { + return (DENY, 'We do not accept password-protected zip files!'); + } + + if ($signal) { + $self->log(LOGWARN, "uvscan exited with signal: $signal"); + return (DECLINED); + } + if ($result == 2) { + $self->log(LOGERROR, "Integrity check for a DAT file failed."); + return (DECLINED); + } elsif ($result == 6) { + $self->log(LOGERROR, "A general problem has occurred."); + return (DECLINED); + } elsif ($result == 8) { + $self->log(LOGERROR, "The program could not find a DAT file."); + return (DECLINED); + } elsif ($result == 15) { + $self->log(LOGERROR, "The program self-check failed"); + return (DECLINED); + } elsif ( $result ) { # all of the possible virus returns + if ($result == 12) { + $self->log(LOGERROR, "The program tried to clean a file but failed."); + } elsif ($result == 13) { + $self->log(LOGERROR, "One or more virus(es) found"); + } elsif ($result == 19) { + $self->log(LOGERROR, "Successfully cleaned the file"); + } + + if (lc($self->{"_uvscan"}->{"deny_viruses"}) eq "yes") { + return (DENY, "Virus Found: $virus"); + } + $transaction->header->add('X-Virus-Found', 'Yes'); + $transaction->header->add('X-Virus-Details', $virus); + return (DECLINED); + } + + $transaction->header->add('X-Virus-Checked', 'Checked'); + return (DECLINED); +}