cvs commit: qpsmtpd/plugins/virus clamav
[email protected] (Devin Carraway)
| Newsgroups | perl.cvs.qpsmtpd |
|---|---|
| Message-ID | <[email protected]> |
cvsuser 04/07/17 16:31:40 Modified: plugins/virus clamav Log: Integrate fixes/enhancements from myself and Peter Eisch <[email protected]>: - name=value style configuration arguments (old format still supported) - max_size for scan (default 512k) - Pass messages to clamscan in mbox format to satisfy clamdscan - Made detect action configurable (reject or add-header) - Logging fixes - POD Revision Changes Path 1.2 +136 -23 qpsmtpd/plugins/virus/clamav Index: clamav =================================================================== RCS file: /cvs/public/qpsmtpd/plugins/virus/clamav,v retrieving revision 1.1 retrieving revision 1.2 diff -u -w -r1.1 -r1.2 --- clamav 30 Jun 2004 09:25:07 -0000 1.1 +++ clamav 17 Jul 2004 23:31:40 -0000 1.2 @@ -1,32 +1,140 @@ -#!/usr/bin/perl -w -# Clam-AV plugin. +#!/usr/bin/perl -Tw + +=head1 NAME + +clamav -- ClamAV antivirus plugin for qpsmtpd + +$Id: clamav,v 1.2 2004/07/17 23:31:40 aqua Exp $ + +=head1 DESCRIPTION + +This plugin scans incoming mail with the clamav A/V scanner, and can at your +option reject or flag infected messages. + +=head1 CONFIGURATION + +Arguments to clamav should be specified in the form of name=value pairs, +separated by whitespace. For sake of backwards compatibility, a single +leading argument containing only alphanumerics, -, _, . and slashes will +be tolerated, and interpreted as the path to clamscan/clamdscan. All +new installations should use the name=value form as follows: + +=over 4 + +=item clamscan_path=I<path> (e.g. I<clamscan_path=/usr/bin/clamdscan>) + +Path to the clamav commandline scanner. Using clamdscan is recommended +for sake of performance. + +Mail will be passed to the clamav scanner in Berkeley mbox format (that is, +with a "From " line). + +=item action=E<lt>I<add-header> | I<reject>E<gt> (e.g. I<action=reject>) + +Selects an action to take when an inbound message is found to be infected. +Valid arguments are 'add-header' and 'reject'. All rejections are hard +5xx-code rejects; the SMTP error will contain an explanation of the virus +found in the mail (for example, '552 Virus Found: Worm.SomeFool.P'). + +The default action is 'add-header'. + +=item max_size=I<bytes> (e.g. I<max_size=1048576>) + +Specifies the maximum size, in bytes, for mail to be scanned. Any mail +exceeding this size will be left alone. This is recommended, as large mail +can take an exceedingly long time to scan. The default is 524288, or 512k. + +=item tmp_dir=I<path> (e.g. I<max_size=/tmp>) + +Specify an alternate temporary directory. If not specified, the qpsmtpd +I<spool_dir> will be used. If neither is available, I<~/tmp/> will be tried, +and if that that fails the plugin will gracefully fail. + +=back + +=head2 CLAMAV CONFIGURATION + +At the least, you should have 'ScanMail' supplied in your clamav.conf file. +It is recommended that you also have sane limits on ArchiveMaxRecursion and +StreamMaxLength also. + +=head1 LICENSE + +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::Temp qw(tempfile); +use strict; +use warnings; + sub register { my ($self, $qp, @args) = @_; - $self->register_hook("data_post", "clam_scan"); + my %args; - if (@args > 0) { - # Untaint scanner location - if ($args[0] =~ /^(\/[\/\-\_\.a-z0-9A-Z]*)$/) { + if ($args[0] && $args[0] =~ /^(\/[\/\-\_\.a-z0-9A-Z]*)$/ && -x $1) { $self->{_clamscan_loc} = $1; - } else { - $self->log(LOGERROR, "FATAL ERROR: Unexpected characters in clamav argument 1"); - exit 3; + shift @args; } - $self->log(LOGWARN, "WARNING: Ignoring additional arguments.") if (@args > 1); - } else { - $self->{_clamscan_loc} = "/usr/local/bin/clamscan"; + + for (@args) { + if (/^max_size=(\d+)$/) { + $self->{_max_size} = $1; + } + elsif (/^clamscan_path=(\/[\/\-\_\.a-z0-9A-Z]*)$/) { + $self->{_clamscan_loc} = $1; + } + elsif (/^tmp_dir=(\/[\/\-\_\.a-z0-9A-Z]*)$/) { + $self->{_spool_dir} = $1; + } + elsif (/^action=(add-header|reject)$/) { + $self->{_action} = $1; + } + else { + $self->log(LOGERROR, "Unrecognized argument '$_' to clamav plugin"); + return undef; + } + } + + $self->{_max_size} ||= 512 * 1024; + $self->{_spool_dir} ||= + $self->qp->config('spool_dir') || + Qpsmtpd::Utils::tildeexp('~/tmp/'); + $self->{_spool_dir} = $1 if $self->{_spool_dir} =~ /(.*)/; + + unless ($self->{_spool_dir}) { + $self->log(LOGERROR, "No spool dir configuration found"); + return undef; + } + unless (-d $self->{_spool_dir}) { + $self->log(LOGERROR, "Spool dir $self->{_spool_dir} does not exist"); + return undef; } + + $self->register_hook("data_post", "clam_scan"); + 1; } sub clam_scan { my ($self, $transaction) = @_; - my ($temp_fh, $filename) = tempfile(); - print $temp_fh $transaction->header->as_string; - print $temp_fh "\n"; + if ($transaction->body_size > $self->{_max_size}) { + $self->log(LOGWARN, 'Mail too large to scan ('. + $transaction->body_size . " vs $self->{_max_size})" ); + return (DECLINED); + } + + my ($temp_fh, $filename) = tempfile("qpsmtpd.clamav.$$.XXXXXX", + DIR => $self->{_spool_dir}); + unless ($temp_fh) { + $self->logerror("Couldn't open tempfile in $self->{_spool_dir}: $!"); + 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; @@ -46,21 +154,26 @@ $output =~ s/^.* (.*) FOUND$/$1 /mg; - $self->log(LOGDEBUG, "clamscan results: $output"); + $self->log(LOGINFO, "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"); + $self->log(LOGINFO, "Virus(es) found: $output"); + if ($self->{_action} eq 'add-header') { $transaction->header->add('X-Virus-Found', 'Yes'); $transaction->header->add('X-Virus-Details', $output); + } else { + return (DENY, "Virus Found: $output"); + } } elsif ($result) { - $self->log(LOGWARN, "ClamAV error: $result\n"); + $self->log(LOGERROR, "ClamAV error: $cmd: $result\n"); } - $transaction->header->add('X-Virus-Checked', 'Checked'); return (DECLINED); } + +1; +