[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