Files
clamtk/lib/Results.pm
2016-09-21 14:59:54 -05:00

343 lines
9.7 KiB
Perl

# ClamTk, copyright (C) 2004-2016 Dave M
#
# This file is part of ClamTk (https://dave-theunsub.github.io/clamtk).
#
# ClamTk is free software; you can redistribute it and/or modify it
# under the terms of either:
#
# a) the GNU General Public License as published by the Free Software
# Foundation; either version 1, or (at your option) any later version, or
#
# b) the "Artistic License".
package ClamTk::Results;
use Glib 'TRUE', 'FALSE';
# use strict;
# use warnings;
$| = 1;
use POSIX 'locale_h';
use File::Basename 'basename';
use Locale::gettext;
use File::Copy 'move';
use Digest::SHA 'sha256_hex';
use Encode 'decode';
my $liststore;
my $dialog;
binmode( STDIN, ':utf8' );
binmode( STDOUT, ':utf8' );
sub show_window {
my ( $pkg_name, $hash, $parent ) = @_;
$dialog = Gtk2::Dialog->new( _( 'Results' ), $parent,
'destroy-with-parent', );
$dialog->set_size_request( 600, 200 );
my $sbox = Gtk2::VBox->new( FALSE, 0 );
# This scrolled window holds the slist
my $sw = Gtk2::ScrolledWindow->new;
$sbox->pack_start( $sw, TRUE, TRUE, 0 );
$dialog->get_content_area->add( $sbox );
$sw->set_shadow_type( 'etched_in' );
$sw->set_policy( 'never', 'automatic' );
use constant FILE => 0;
use constant STATUS => 1;
use constant ACTION_TAKEN => 2;
#<<<
$liststore = Gtk2::ListStore->new(
# FILE
'Glib::String',
# STATUS
'Glib::String',
# ACTION_TAKEN
'Glib::String',
);
my $tree = Gtk2::TreeView->new_with_model( $liststore );
$tree->set_rules_hint( TRUE );
$sw->add( $tree );
my $renderer = Gtk2::CellRendererText->new;
my $column
= Gtk2::TreeViewColumn->new_with_attributes(
_( 'File' ),
$renderer,
markup => FILE,
);
$column->set_sort_column_id( FILE );
$column->set_resizable( TRUE );
$column->set_sizing( 'fixed' );
$column->set_expand( TRUE );
$tree->append_column( $column );
$column = Gtk2::TreeViewColumn->new_with_attributes(
_( 'Status' ),
$renderer,
markup => STATUS,
);
$column->set_sort_column_id( STATUS );
$column->set_resizable( TRUE );
$column->set_sizing( 'fixed' );
$column->set_expand( TRUE );
$tree->append_column( $column );
$column = Gtk2::TreeViewColumn->new_with_attributes(
_( 'Action Taken' ),
$renderer,
markup => ACTION_TAKEN,
);
$column->set_sort_column_id( ACTION_TAKEN );
$column->set_resizable( TRUE );
$column->set_sizing( 'fixed' );
$column->set_expand( TRUE );
$tree->append_column( $column );
#<<<
my $i = 0;
while ( $i <= scalar keys %$hash ) {
# print 'name = ', $hash->{ $i }->{ name }, "\n";
my $iter = $liststore->append;
$liststore->set(
$iter,
0, decode( 'utf8', $hash->{ $i }->{ name }),
1, $hash->{ $i }->{ status },
2, $hash->{ $i }->{ action },
);
$i++;
last if ( $i >= scalar keys %$hash );
}
#>>>
my $hbox = Gtk2::Toolbar->new;
$hbox->set_style( 'both-horiz' );
$sbox->pack_start( $hbox, FALSE, FALSE, 5 );
my $image = Gtk2::Image->new_from_stock( 'gtk-refresh', 'menu' );
my $button = Gtk2::ToolButton->new( $image, _( 'Quarantine' ) );
$button->set_is_important( TRUE );
$hbox->insert( $button, -1 );
$button->signal_connect( clicked => \&action, $tree );
$image = Gtk2::Image->new_from_stock( 'gtk-delete', 'menu' );
$button = Gtk2::ToolButton->new( $image, _( 'Delete' ) );
$button->set_is_important( TRUE );
$hbox->insert( $button, -1 );
$button->signal_connect( clicked => \&action, $tree );
# Testing to see if we can add Analysis button.
# See ClamTk::Analysis->button_test for more
if ( ClamTk::Analysis->button_test ) {
$image = Gtk2::Image->new_from_stock( 'gtk-find', 'menu' );
$button = Gtk2::ToolButton->new( $image, _( 'Analysis' ) );
$button->set_is_important( TRUE );
$hbox->insert( $button, -1 );
$button->signal_connect( clicked => \&action, $tree );
}
my $sep = Gtk2::SeparatorToolItem->new;
$sep->set_draw( FALSE );
$sep->set_expand( TRUE );
$hbox->insert( $sep, -1 );
$image = Gtk2::Image->new_from_stock( 'gtk-close', 'menu' );
$button = Gtk2::ToolButton->new( $image, _( 'Close' ) );
$button->signal_connect(
clicked => sub {
$dialog->destroy;
}
);
$button->set_is_important( TRUE );
$hbox->insert( $button, -1 );
$sbox->show_all;
$dialog->run;
$dialog->destroy;
}
sub action {
my ( $button, $view ) = @_;
my $selected = $view->get_selection;
my ( $model, $iter ) = $selected->get_selected;
return unless $iter;
# These look like
# first_col_value = >/home/foo/mime.cache<
# second_col_value = >PUA.Win.Exploit.CVE_2012_0110<
my $first_col_value = $model->get_value( $iter, FILE );
my $second_col_value = $model->get_value( $iter, STATUS );
my $third_col_value = $model->get_value( $iter, ACTION_TAKEN );
# Don't mess with inboxes and empty values (!)
my $maildirs = get_maildirs();
if ( $first_col_value =~ /$maildirs/ || !-e $first_col_value ) {
color_out( $model, $iter );
return TRUE;
}
# Don't quarantine or delete PUAs outside of home directory
if ( $second_col_value =~ /^PUA/ ) {
if ( $first_col_value !~ m#^/home/# ) {
color_out( $model, $iter );
return TRUE;
}
}
# Return 1 or TRUE for successfully deleting
# or quarantining so we can "color_out" that row
# and change its status column
if ( $button->get_label eq _( 'Quarantine' ) ) {
my $ret = quarantine( $first_col_value );
if ( $ret ) {
color_out( $model, $iter, _( 'Quarantined' ) );
return TRUE;
} else {
return FALSE;
}
} elsif ( $button->get_label eq _( 'Delete' ) ) {
my $ret = delete_file( $first_col_value );
if ( $ret ) {
color_out( $model, $iter, _( 'Deleted' ) );
return TRUE;
}
return FALSE;
} elsif ( $button->get_label eq _( 'Analysis' ) ) {
ClamTk::Analysis->show_window( $first_col_value, $dialog );
return TRUE;
} else {
warn 'unable to ' . $button->get_label . " file >$first_col_value<\n";
}
return TRUE;
}
sub quarantine {
my ( $file ) = shift;
my $basename = basename( $file );
# This is where threats go
my $paths = ClamTk::App->get_path( 'viruses' );
if ( !-e $paths or !-d $paths ) {
warn "Unable to quarantine >$file<; no quarantine directory\n";
return FALSE;
}
# Get permissions
my $mode = ( stat( $file ) )[ 2 ];
my $perm = sprintf( "%03o", $mode & oct( 7777 ) );
# Update restore file by adding file, path and md5
ClamTk::Quarantine->add_hash( $file, $perm );
# Assign 600 permissions
chmod oct( 600 ), $file;
move( $file, "$paths/$basename" ) or do {
# When a 'mv' fails, it still probably did a 'cp'...
# 'mv' copies the file first, then unlinks the source.
# d'oh... so just to make sure, unlink the intended target
# and THEN return.
unlink( "$paths/$basename" )
or warn "unable to delete tmp file $paths/$basename\n: $!\n";
return FALSE;
};
}
sub delete_file {
my $file = shift;
my $basename = basename( $file );
# This is where threats go
my $paths = ClamTk::App->get_path( 'viruses' );
my $question
= sprintf( _( 'Really delete this file (%s) ?' ), $basename );
my $message
= Gtk2::MessageDialog->new( undef,
[ qw| modal destroy-with-parent | ],
'question', 'ok-cancel', $question, );
if ( 'ok' eq $message->run ) {
$message->destroy;
} else {
$message->destroy;
return FALSE;
}
unlink( $file ) or do {
warn "unable to delete >$file<: $!\n";
return FALSE;
};
# If it's in the trash, remove its associated information file
my $trash_info_path = ClamTk::App->get_path( 'trash_files_info' );
my $trash_info_file = $trash_info_path . '/' . $basename . '.trashinfo';
if ( $file =~ m#Trash# ) {
if ( -e $trash_info_file ) {
unlink( $trash_info_file )
or warn "Unable to delete trashinfo file for $file: $!\n";
}
}
return TRUE;
}
sub color_out {
# We optionally take a value to set the third
# column to (e.g., Quarantined, Deleted)
my ( $store, $iter, $third_value_change ) = @_;
if ( !$third_value_change ) {
$third_value_change = ' - ';
}
my $first_col_value
= "<span foreground = '#CCCCCC'>"
. $store->get_value( $iter, FILE )
. " </span>";
my $second_col_value
= "<span foreground='#CCCCCC'>"
. $store->get_value( $iter, STATUS )
. "</span > ";
my $third_col_value = "<span foreground = '#CCCCCC'>"
#. $store->get_value( $iter, ACTION_TAKEN )
. $third_value_change . "</span>";
#<<<
$store->set(
$iter,
0, $first_col_value,
1, $second_col_value,
2, $third_col_value,
);
#>>>
}
sub get_maildirs {
return join(
'|',
'.thunderbird', '.mozilla-thunderbird', 'evolution(?!/ tmp
) ',
' Mail ', ' kmail ', "\.pst"
);
}
sub get_hash {
my $file = shift;
my $slurp = do {
local $/ = undef;
open( my $f, ' < ', $file ) or do {
warn "unable to open >$file<: $!\n";
return;
};
binmode( $f );
<$f>;
};
return sha256_hex( $slurp );
}
1;