[Mimedefang] patches/support of off-server clamd implimentation? (fwd)
Tom Brown
wc-mimedefang at vmail.baremetal.com
Sun May 23 18:47:47 EDT 2010
On Wed, 21 Apr 2010, Dave O'Neill wrote:
> On Wed, Apr 21, 2010 at 10:34:37AM -0700, Tom Brown wrote:
> > The reason for this post is to ask if such patches would be likely to
> > make
> > it into the mainstream? Or possibly if others have already done this?
>
> At some point in the near future, I'd like to convert MIMEDefang to use
> File::VirusScan (see http://search.cpan.org/perldoc?File::VirusScan). This
> is essentially the existing MIMEDefang antivirus integration code pulled out
> and modularized into something a bit more maintainable.
>
> Patches against File::VirusScan::Engine::Daemon::ClamAV::Clamd to use a
> host/port will definitely be accepted, and might be enough of a spur to get
> David or I to finally finish integrating the module back into MIMEDefang.
>
> If you feel like implementing this, take a look at the other
> File::VirusScan::Engine::Daemon subclasses for inspiration, as some of them
> already handle taking a host/port instead of a UNIX socket.
File-Virus wants perl 5.008. The boxes that are short of ram are by definition
old boxes which aren't that current... and changing perl is a big change,
whereas clamd is only used by mimedefang... So I went back to patching the code
we have today.
oh, whoops, I seem to have a problem with zero byte messages. (fixed) I was
aware I was risking that, when I wrote the defensive code to return an error if
there was nothing to scan. Is there a variable somewhere I can use to see the
message length?
requiring this from /etc/mail/mimedefang-filter, with an appropriate setting
from ClamdSock seems to work just fine, e.g.
$ClamdSock = "scanner.example.com:3310";
and the file. It does cause a subroutine redefined warning, but
otherwise it seems quiet and effective.
-Tom
# rewrite of message_contains_virus_clamd from /usr/bin/mimedefang.pl
# need to rewrite it so we can support offsite clamd
use File::Find qw(find);
use IO::Socket;
sub clamd_sock($) { # we need to call this multiple times
my $clamd_sock = shift;
if ($clamd_sock =~ m/^(.*):(\d+)$/) {
my ($host,$port) = ($1, $2);
$sock = IO::Socket::INET->new(
PeerAddr => $host,
PeerPort => $port,
Proto => 'tcp',
Type => SOCK_STREAM,
Timeout => 10
) ;
if (! $sock) {
md_syslog('err', "$MsgID: cannot connect to clamd
($host:$port)");
return undef;
}
} else {
$sock = IO::Socket::UNIX->new(Peer => $clamd_sock);
}
$sock;
}
# NAH. returns undef on error, '' on no virus, else virus name
# returns '' on no virus, else virus name or error message
sub clamd_instream_scan($$) {
my ($sock, $filename) = @_;
return '' unless -f $filename;
open(FILE,"<$filename") or return "could not open $filename ($!)";
$sock->print("zINSTREAM\0")
or return "dead socket, can't send INSTREAM";
my $buf = '';
while (1) {
my $num = sysread(FILE,$buf, 128*1024);
$sock->print( pack("N", $num )) ; # length
$sock->print( $buf ) # data
or return "dead socket, can't send data for INSTREAM";
$sock->flush();
last if ($num <= 0); # we DO want to send the zero byte count to clamd
}
close FILE;
$buf = ();
while( defined( my $line = $sock->getline() )) {
$buf .= $line;
}
$sock->close(); # encapsulated and terminated, but one transaction per sock
return '' if ($buf eq "stream: OK\0"); # return '' if no virus
return $buf;
}
# ***********************************************************************
# % PROCEDURE: message_contains_virus_clamd
# % ARGUMENTS:
# clamd_sock (optional) -- clamd socket path
# %RETURNS:
# 1 if any file in the working directory contains a virus
# %DESCRIPTION:
# Invokes the clamd daemon (http://clamav.elektrapro.com/)
# on the entire message.
# ***********************************************************************
sub message_contains_virus_clamd (;$) {
my ($clamd_sock) = $ClamdSock;
$clamd_sock = shift if (@_ > 0);
$clamd_sock = "/var/spool/MIMEDefang/clamd.sock" if
(!defined($clamd_sock));
my ($output,$sock);
$sock = clamd_sock($clamd_sock); #
# PING/PONG test to make sure clamd is alive
if (defined $sock) {
$sock->print("nPING\n");
$sock->flush;
$sock->sysread($output,256);
chomp($output);
if (! defined($output) || $output ne "PONG") {
md_syslog('err', "$MsgID: clamd is not responding");
return (wantarray ? (999, 'cannot-execute', 'tempfail') : 999);
}
}
else {
md_syslog('err', "$MsgID: Could not connect to clamd daemon at
$clamd_sock");
return (wantarray ? (999, 'cannot-execute', 'tempfail') : 999);
}
# open up a socket and scan each file in ./Work
# $sock = clamd_sock($clamd_sock); #
if (1) {
my @files = ();
# snarfed from matt seargants clamd.pm
find(
sub {
if (-f $File::Find::name) {
push @files, $File::Find::name;
}
}, "$CWD/Work" );
# let us be defensive:
# nope, fails for zero byte messages
# return (wantarray ? (999, 'error no files to scan?', 'tempfail') : 999)
# unless @files;
$output = '';
foreach my $f (@files) {
$sock = clamd_sock($clamd_sock)
or return (wantarray ? (999, 'can\'t connect to clamd',
'tempfail') : 999);
$output = clamd_instream_scan( $sock, $f );
last if ($output);
}
if ($output =~ /: (.+) FOUND/) {
$VirusScannerMessages .= "clamd found the $1 virus.\n";
$VirusName = $1;
return (wantarray ? (1, 'virus', 'quarantine') : 1);
}
if ($output) {
# then we got an error...
return (wantarray ? (999, "error while clamd scanning: $output",
'tempfail') : 999)
}
}
# No errors, no infected files were found
return (wantarray ? (0, 'ok', 'ok') : 0);
}
1;
More information about the MIMEDefang
mailing list