cvs commit: qpsmtpd/plugins/virus check_for_hi_virus clamav kavscanner klez_filter

[email protected] (Ask "Bj?rn" Hansen)
Newsgroups perl.cvs.qpsmtpd
Message-ID <[email protected]>
cvsuser     04/06/30 02:25:07

  Added:       plugins/virus check_for_hi_virus clamav kavscanner
                        klez_filter
  Log:
  add kavscanner plugin (thanks to Hanno Hecker)
  
  move clamav, check_for_hi_virus and klez_filter to virus/ (did anyone mentino
  subversion?)
  
  Revision  Changes    Path
  1.1                  qpsmtpd/plugins/virus/check_for_hi_virus
  
  Index: check_for_hi_virus
  ===================================================================
  #!/usr/bin/perl -w
  
  sub register {
      my $self = shift;
      $self->register_hook('data_post', 'check_for_hi_virus');
  }
  
  sub check_for_hi_virus {
      my ($self, $transaction) = @_;
  
      # make sure we read from the beginning;
      $transaction->body_resetpos;
  
      my $line_number = 0;
      my $seen_file = 0;
      my $ct_filename = '';
      my $cd_filename = '';
  
      while ($_ = $transaction->body_getline) {
          last if $line_number++ > 40;
          if (/^Content-Type: (.*)/) {
              my $val = $1;
              if ($val =~ /name="(.*)"/) {
                  $seen_file = 1;
                  $ct_filename = $1;
              }
          }
          if (/^Content-Disposition: (.*)/) {
              my $val = $1;
              if ($val =~ /filename="(.*)"/) {
                  $seen_file = 1;
                  $cd_filename = $1;
              }
          }
      }
  
      if ($seen_file and $ct_filename and $cd_filename) {
          if ($ct_filename ne $cd_filename) {
              return (DENY, "Probably the 'Hi' virus");
          }
      }
  
      return DECLINED;
  }
  
  
  
  1.1                  qpsmtpd/plugins/virus/clamav
  
  Index: clamav
  ===================================================================
  #!/usr/bin/perl -w
  # Clam-AV plugin.
   
  use File::Temp qw(tempfile);
   
  sub register {
    my ($self, $qp, @args) = @_;
    $self->register_hook("data_post", "clam_scan");
  
    if (@args > 0) {
      # Untaint scanner location
      if ($args[0] =~ /^(\/[\/\-\_\.a-z0-9A-Z]*)$/) {
        $self->{_clamscan_loc} = $1;
      } else {
        $self->log(LOGERROR, "FATAL ERROR: Unexpected characters in clamav argument 1");
        exit 3;
      }
      $self->log(LOGWARN, "WARNING: Ignoring additional arguments.") if (@args > 1);
    } else {
      $self->{_clamscan_loc} = "/usr/local/bin/clamscan";
    }
  }
   
  sub clam_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->{_clamscan_loc}." --stdout -i --max-recursion=50 --disable-summary $filename 2>&1";
    $self->log(LOGDEBUG, "Running: $cmd");
    my $output = `$cmd`;
   
    my $result = ($? >> 8);
    my $signal = ($? & 127);
   
    unlink($filename);
    chomp($output);
   
    $output =~ s/^.* (.*) FOUND$/$1 /mg;
   
    $self->log(LOGDEBUG, "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");
      $transaction->header->add('X-Virus-Found', 'Yes');
      $transaction->header->add('X-Virus-Details', $output);
    }
    elsif ($result) {
      $self->log(LOGWARN, "ClamAV error: $result\n");
    }
    $transaction->header->add('X-Virus-Checked', 'Checked');
    return (DECLINED);
  } 
  
  
  
  1.1                  qpsmtpd/plugins/virus/kavscanner
  
  Index: kavscanner
  ===================================================================
  #!/usr/bin/perl -w
  # Kasperski-AV plugin.
   
  =head1 NAME
  
  kavscanner - plugin for qpsmtpd which calls the Kasperski anti virus scanner
  
  =head1 DESCRIPTION
  
  Check a mail with the B<kavscanner> and deny if it matches a configured virus
  list.
  
  =head1 VERSION
  
  this is B<kavscanner> version 1.0
  
  =head1 CONFIGURATION
  
  Add (perl-)regexps to the F<kav_deny> configuration file, one per line for the
  virii you want to block, e.g.:
  
    I-Worm\.Sober\..*
    I-Worm\.NetSky\..*
  
  NOTE: untested and disabled currently, need volunteers :-)
  
  If this list does not match the virus found in the mail, you may set 
  I<bcc_virusadmin [email protected]> in the plugin config to send a 
  B<Bcc:> to the given mail address, i.e. the line 
  
    kavscanner bcc_virusadmin [email protected] 
  
  in the F<config/plugin> file instead of just
  
    kavscanner
  
  Set the location of the binary with 
  
    kavscanner kavscanner_bin /path/to/kavscanner
  
  (default: F</opt/AVP/kavscanner>), NOTE: this may be broken, you want to
  set B<kavscanner_bin> explicitly ;-)
  
  =head1 NOTES
  
  This is a merge of the clam_av plugin for qpsmtpd and qmail-scanner-queue.pl
  L<http://qmail-scanner.sourceforge.net/> with my own improvements ;-)
  Only tested with kavscanner 4.0.x, and bcc_virusadmin untested, as we have no
  use for it currently. I wait for an official change in Qpsmtpd::Transaction
  (reset/set the RCPT TO list) to activate and test the currently disabled 
  B<to_virusadmin> option.
  
  =cut
  
  use File::Temp qw(tempfile);
  use Mail::Address;
   
  sub register {
    my ($self, $qp, @args) = @_;
    $self->register_hook("data_post", "kav_scan");
  
    if (@args % 2) {
      warn "kavscanner: Wrong number of arguments";
      $self->{_kavscanner_bin} = "/opt/AVP/kavscanner";
    } else {
      my %args = @args;
      foreach my $key (keys %args) {
        my $arg = $key;
        $key =~ s/^/_/;
        $self->{$key} = $args{$arg};
      }
      # Untaint scanner location
      if (exists $self->{_kavscanner_bin} && 
          $self->{_kavscanner_bin} =~ /^(\/[\/\-\_\.a-z0-9A-Z]*)$/) {
        $self->{_kavscanner_bin} = $1;
      } else {
        $self->log(1, "FATAL ERROR: Unexpected characters in kavscanner argument");
        exit 3;
      }
    } 
  }
   
  sub kav_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->{_kavscanner_bin}." -Y -P -B -MP -MD -* $filename 2>&1";
    $self->log(1, "Running: $cmd");
    my @output = `$cmd`;
    chomp(@output);
   
    my $result = ($? >> 8);
    my $signal = ($? & 127);
   
    unlink($filename);
    close $temp_fh;
  
    if ($signal) {
      $self->log(1, "kavscanner exited with signal: $signal");
      return (DECLINED);
    }
  
    my $description = 'clean';
    my @infected    = ();
    my @suspicious  = ();
    if ($result > 0) {
      if ($result =~ /^(2|3|4|8)$/) {
        foreach (@output) {
          if (/^.* infected: (.*)$/) {
            # This covers the specific
            push @infected, $1;
          } elsif (/^\s*.* suspicion: (.*)$/) {
            # This covers the potential viruses
            push @suspicious, $1;
          }
        }
        $description = "infected by: ".join(", ",@infected)."; "
                      ."suspicions: ".join(", ", @suspicious);
        # else we may get a veeeery long X-Virus-Details: line or log entry
        $description = substr($description,0,60); 
        $self->log(1, "There be a virus! ($description)");
        ### Untested by now, need volunteers ;-)
        #if ($self->qp->config("kav_deny")) {
        #  foreach my $d (keys %{$self->qp->config("kav_deny", "map")}) {
        #    foreach my $v (@infected) {
        #      return(DENY, "Virus found: $description")
        #        if ($v =~ /^$d$/i); 
        #    }
        #    foreach my $s (@suspicious) {
        #      return(DENY, "Virus found: $description")
        #        if ($s =~ /^$d$/i); 
        #    }
        #  }
        #}
        $transaction->header->add('X-Virus-Found', 'Yes');
        $transaction->header->add('X-Virus-Details', $description);
        ### maybe the spamassassin plugin can skip this mail if a virus
        ### was found (and $transaction->notes('virus_flag') exists :))
        ### ...ok, works with our spamassassin plugin version 
        ###   -- hah
        $transaction->notes('virus', $description);
        $transaction->notes('virus_flag', 'Yes');
  
        #### requires modification of Qpsmtpd/Transaction.pm:
        # if ($self->{_to_virusadmin}) {
        #   my @addrs = ();
        #   foreach (@{$transaction->recipients}) {
        #     push @addr, $_->address;
        #   }
        #   $transaction->header->add('X-Virus-Orig-RcptTo', join(", ", @addrs));
        #   $transaction->set_recipients(@{ Mail::Address->parse($self->{_to_virusadmin}) });
        # } elsif ($self->{_bcc_virusadmin}) {
        if ($self->{_bcc_virusadmin}) {
          foreach ( @{ Mail::Address->parse($self->{_bcc_virusadmin}) } ) {
            $transaction->add_recipient($_->address);
          }
        }
      } else {
        $self->log(0, "corrupt or unknown Kaspersky scanner/resource problems - exit status $result");
      }
    }
    
    $self->log(1, "kavscanner results: $description");
   
    $transaction->header->add('X-Virus-Checked', 'Checked by '.$self->qp->config("me"));
    return (DECLINED);
  } 
  
  # vim: ts=2 sw=2 expandtab
  
  
  
  1.1                  qpsmtpd/plugins/virus/klez_filter
  
  Index: klez_filter
  ===================================================================
  sub register {
    my ($self, $qp) = @_;
    $self->register_hook("data_post", "check_klez");
  }
  
  sub check_klez {
    my ($self, $transaction) = @_;
  
    # klez files are always sorta big .. how big?  Dunno. 
    return (DECLINED)
      if $transaction->body_size < 60_000;
     #   220k was too little, so let's just disable the "big size check"
     #   or $transaction->body_size > 1_000_000;
  
    # maybe it would be worthwhile to add a check for
    # Content-Type: multipart/alternative; here?
  
    # make sure we read from the beginning;
    $transaction->body_resetpos;
    
    my $line_number = 0; 
    my $seen_klez_signature = 0;
  
    while ($_ = $transaction->body_getline) {
      last if $line_number++ > 40;
  
      m/^Content-type:.*(?:audio|application)/i
        and ++$seen_klez_signature and next;
  
      return (DENY, "Klez Virus Detected")
        if $seen_klez_signature 
  	and m!^TVqQAAMAAAAEAAAA//8AALgAAAAAAAAAQA!;
  
    }
  
    return (DECLINED);
  }
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.