Hi all,

this plugin has been in production for quite a while here, so I decided
it's worth to release to public ;) 

Perldoc is included in the plugin. 
When the "full message in spool dir" patch is in CVS I'll update this
plugin.

        Hanno
#!/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.0

=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

This plugin started life as a copy of the B<clamav> plugin.

=head1 LICENCE

Written by Hanno Hecker E<lt>[EMAIL PROTECTED]<gt>. 

The B<hbedv> plugin is published under the same licence as qpsmtpd itself.

=cut 
 
use File::Temp qw(tempfile);
 
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 ($temp_fh, $filename) = tempfile();
  print $temp_fh $transaction->header->as_string;
  print $temp_fh "\n";
  $transaction->body_resetpos;
  while (my $line = $transaction->body_getline) {
    print $temp_fh $line;
  }
  seek($temp_fh, 0, 0);
 
  # 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);
 
  unlink($filename);
  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, "hbedv results: ".join("//",@output));
 
  if ($signal) {
    $self->log(LOGWARN, "hbedv 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-H+BEDV-Virus-Found', 'Yes', 0);
    $transaction->header->add('X-H+BEDV-Virus-Details', $output, 0);
  }
  elsif ($result) {
    $self->log(LOGWARN, "H+BEDV error: $result\n");
  }
  $transaction->header->add('X-H+BEDV-Virus-Checked', 'Checked', 0);
  if (@virii && $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)
}

Reply via email to