develooper Front page | perl.cvs.qpsmtpd | Postings from March 2005

cvs commit: qpsmtpd/plugins/virus bitdefender hbedv clamav

From:
John Peacock
Date:
March 1, 2005 11:55
Subject:
cvs commit: qpsmtpd/plugins/virus bitdefender hbedv clamav
Message ID:
20050301195519.18471.qmail@x1.develooper.com
cvsuser     05/03/01 11:55:18

  Modified:    plugins/virus clamav
  Added:       plugins/virus bitdefender hbedv
  Log:
  *   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
  
  Revision  Changes    Path
  1.6       +5 -18     qpsmtpd/plugins/virus/clamav
  
  Index: clamav
  ===================================================================
  RCS file: /cvs/public/qpsmtpd/plugins/virus/clamav,v
  retrieving revision 1.5
  retrieving revision 1.6
  diff -u -r1.5 -r1.6
  --- clamav	25 Feb 2005 03:06:22 -0000	1.5
  +++ clamav	1 Mar 2005 19:55:18 -0000	1.6
  @@ -4,7 +4,7 @@
   
   clamav -- ClamAV antivirus plugin for qpsmtpd
   
  -$Id: clamav,v 1.5 2005/02/25 03:06:22 jpeacock Exp $
  +$Id: clamav,v 1.6 2005/03/01 19:55:18 jpeacock Exp $
   
   =head1 DESCRIPTION
   
  @@ -101,8 +101,6 @@
   
   =cut
    
  -use File::Temp qw(tempfile);
  -
   use strict;
   use warnings;
    
  @@ -156,28 +154,18 @@
    
   sub clam_scan {
     my ($self, $transaction) = @_;
  - 
  +
     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}: $!");
  +  my $filename = $transaction->body_filename;
  +  unless (defined $filename) {
  +        $self->log(LOGERROR, "didn't get a filename");
           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];
     if ( $mode & 07077  ) { # must be sharing spool directory with external app
         $self->log(LOGWARN,
  @@ -195,7 +183,6 @@
     my $result = ($? >> 8);
     my $signal = ($? & 127);
    
  -  unlink($filename);
     chomp($output);
    
     $output =~ s/^.* (.*) FOUND$/$1 /mg;
  
  
  
  1.1                  qpsmtpd/plugins/virus/bitdefender
  
  Index: bitdefender
  ===================================================================
  #!/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;
  
  
  
  
  1.1                  qpsmtpd/plugins/virus/hbedv
  
  Index: hbedv
  ===================================================================
  #!/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)
  }
  
  
  



nntp.perl.org: Perl Programming lists via nntp and http.
Comments to Ask Bjørn Hansen at ask@perl.org | Group listing | About