Files
clamtk/lib/Update.pm
2024-01-06 06:13:02 -06:00

402 lines
12 KiB
Perl

# ClamTk, copyright (C) 2004-2024 Dave M
#
# This file is part of ClamTk
# https://github.com/dave-theunsub/clamtk/
# https://gitlab.com/dave_m/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::Update;
use Glib 'TRUE', 'FALSE';
# use strict;
# use warnings;
$| = 1;
use LWP::UserAgent;
use Locale::gettext;
# Keeping these global for easy messaging.
my $infobar; # InfoBar for status
my $pb; # ProgressBar for ... showing progress
my $liststore; # Information on current and remote versions
my $iter_hash; # Must be global to update sig area
my $updated = 0;
sub show_window {
my $box = Gtk3::Box->new( vertical, 5 );
$box->set_homogeneous( FALSE );
my $top_box = Gtk3::Box->new( vertical, 5 );
$top_box->set_homogeneous( FALSE );
$box->pack_start( $top_box, TRUE, TRUE, 0 );
my $scrolled = Gtk3::ScrolledWindow->new( undef, undef );
$scrolled->set_policy( 'never', 'never' );
$scrolled->set_shadow_type( 'etched-out' );
$top_box->pack_start( $scrolled, FALSE, TRUE, 2 );
# update available images:
# gtk-yes = yes
# gtk-no = no
# gtk-dialog-error = unknown
$liststore = Gtk3::ListStore->new(
# product, local version,
'Glib::String', 'Glib::String', 'Glib::String',
);
# Product column
my $view = Gtk3::TreeView->new_with_model( $liststore );
$view->set_can_focus( FALSE );
$scrolled->add( $view );
my $column = Gtk3::TreeViewColumn->new_with_attributes(
_( 'Product' ),
Gtk3::CellRendererText->new,
text => 0,
);
$column->set_alignment( 0.5 );
$view->append_column( $column );
# Installed version column
$column = Gtk3::TreeViewColumn->new_with_attributes(
_( 'Installed' ),
Gtk3::CellRendererText->new,
text => 1,
);
$column->set_alignment( 0.5 );
$view->append_column( $column );
# Date of signatures
$column = Gtk3::TreeViewColumn->new_with_attributes(
_( 'Date' ),
Gtk3::CellRendererText->new,
text => 2,
);
$column->set_alignment( 0.5 );
$view->append_column( $column );
# Get local information
my $local_sig_version = ClamTk::App->get_local_sig_version();
# Get date of signatures
my $av_date = ClamTk::App->get_sigtool_info( 'date' );
#<<<
my @data = (
{
product => _( 'Antivirus signatures' ),
local => $local_sig_version,
date => $av_date,
},
);
for my $item ( @data ) {
my $iter = $liststore->append;
# make a copy for updating
$iter_hash = $iter;
$liststore->set( $iter,
0, $item->{ product },
1, $item->{ local },
2, $item->{ date },
);
}
#>>>
$infobar = Gtk3::InfoBar->new;
$infobar->set_message_type( 'other' );
my $text = '';
if ( ClamTk::Prefs->get_preference( 'Update' ) eq 'shared' ) {
my $label = Gtk3::Label->new;
$label->set_text(
_( 'You are configured to automatically receive updates' ) );
$infobar->get_content_area()->add( $label );
} else {
$text = _( 'Check for updates' );
$infobar->add_button( $text, -5 );
$infobar->signal_connect(
response => sub {
Gtk3::main_iteration while Gtk3::events_pending;
update_signatures();
Gtk3::main_iteration while Gtk3::events_pending;
}
);
}
$box->pack_start( Gtk3::VBox->new, TRUE, TRUE, 5 );
$pb = Gtk3::ProgressBar->new;
$box->pack_start( $infobar, FALSE, FALSE, 0 );
$box->pack_start( $pb, FALSE, FALSE, 0 );
$view->columns_autosize();
$box->show_all;
$pb->hide;
return $box;
}
sub get_remote_TK_version {
my $url
= 'https://raw.githubusercontent.com/dave-theunsub/clamtk/master/latest';
$ENV{ HTTPS_DEBUG } = 1;
my $ua = add_ua_proxy();
my $response = $ua->get( $url );
if ( $response->is_success ) {
my $content = $response->content;
chomp( $content );
# warn "remote tk version = >$content<\n";
return $content;
} else {
warn "failed remote tk check >", $response->status_line, "<\n";
return '';
}
return '';
}
sub update_signatures {
$pb->{ timer } = Glib::Timeout->add( 100, \&progress_timeout, $pb );
$pb->show;
$pb->set_text( _( 'Please wait...' ) );
my $freshclam = get_freshclam_path();
if ( ClamTk::Prefs->get_preference( 'Update' ) eq 'single' ) {
my $dbpath = ClamTk::App->get_path( 'localfreshclamconf' );
if ( -e $dbpath ) {
$freshclam .= " --config-file=$dbpath";
}
}
# The mirrors can be slow sometimes and may return/die
# 'failed' despite that the update is still in progress.
# my $update = file_handle
# my $update_sig_pid = process ID for $update
my $update;
my $update_sig_pid;
eval {
local $SIG{ ALRM } = sub {
die "failed updating signatures (timeout)\n";
};
alarm 100;
$update_sig_pid = open( $update, '-|', "$freshclam --stdout" );
defined( $update_sig_pid )
or do {
set_infobar_text( 'error',
_( 'Error updating: try again later' ) );
return 0;
};
alarm 0;
};
if ( $@ && $@ eq "failed\n" ) {
set_infobar_text( 'error', _( 'Error updating: try again later' ) );
return 0;
}
# We don't want to print out the following lines beginning with:
# my $do_not_print = "DON'T|WARNING|ClamAV update process";
# We can't just print stuff out; that's bad for non-English
# speaking users. So, we'll grab the first couple words
# and try to sum it up.
#
# logg("!Database update process failed: %s (%d)\n"
# Downloading database patch # 25961..
while ( defined( my $line = <$update> ) ) {
Gtk3::main_iteration while Gtk3::events_pending;
$pb->set_text( _( 'Downloading...' ) );
chomp( $line );
if ( $line =~ /failed/ ) {
# Print these out to terminal window for now
warn $line, "\n";
} elsif ( $line =~ /Database test passed./ ) {
warn "Database test passed.\n";
} elsif ( $line =~ /^Downloading daily-(\d+).*?$/ ) {
# This one should probably be removed;
# was probably changed to the next elsif
my $new_daily = $1;
$liststore->set( $iter_hash, 0, _( 'Antivirus signatures' ),
1, $new_daily, );
} elsif ( $line
=~ q#^Retrieving https://database.clamav.net/daily-(\d+).cdiff# )
{
my $new_daily = $1;
$liststore->set( $iter_hash, 0, _( 'Antivirus signatures' ),
1, $new_daily, );
} elsif ( $line =~ /^Testing database/ ) {
$pb->set_text( _( 'Testing database' ) );
} elsif ( $line =~ /^Downloading database patch # (\d+).*?$/ ) {
my $new_daily = $1;
$liststore->set( $iter_hash, 0, _( 'Antivirus signatures' ),
1, $new_daily, );
} elsif ( $line =~ /Database updated/ ) {
$pb->set_fraction( 1.0 );
} elsif (
# bytecode appears to be last
$line =~ /.*?bytecode.*?$/ && ( $line =~ /.*?up-to-date\.$/
|| $line =~ /.*?up to date .*?/
|| $line =~ /.*?updated\.$/ )
)
{
$pb->set_fraction( 1.0 );
} else {
# warn "skipping line: >$line<\n";
next;
}
Gtk3::main_iteration while Gtk3::events_pending;
}
# Get local information. It would probably be okay to just
# keep the same number we saw during the update, but this
# gives the "for sure" sig version installed:
my $local_sig_version = ClamTk::App->get_local_sig_version();
$liststore->set( $iter_hash, 0, _( 'Antivirus signatures' ),
1, $local_sig_version, );
Glib::Source->remove( $pb->{ timer } );
$pb->set_fraction( 1.0 );
$pb->set_text( _( 'Complete' ) );
# Update infobar type and text; remove button
set_infobar_text( 'info', _( 'Complete' ) );
ClamTk::GUI::set_infobar_mode( 'info', '' );
$pb->hide;
destroy_button();
return TRUE;
}
sub get_freshclam_path {
my $paths = ClamTk::App->get_path( 'all' );
my $command = $paths->{ freshclam };
# If the user will update the signatures manually,
# append the appropriate paths
if ( ClamTk::Prefs->get_preference( 'Update' ) eq 'single' ) {
$command
.= " --datadir=$paths->{db} --log=$paths->{db}/freshclam.log";
}
# Add verbosity
$command .= " --verbose";
# Was the proxy option set?
if ( ClamTk::Prefs->get_preference( 'HTTPProxy' ) ) {
if ( ClamTk::Prefs->get_preference( 'HTTPProxy' ) == 2 ) {
if ( -e $paths->{ localfreshclamconf } ) {
$command .= " --config-file=$paths->{ localfreshclamconf }";
}
}
}
return $command;
}
sub set_infobar_text {
my ( $type, $text ) = @_;
$infobar->set_message_type( $type );
for my $child ( $infobar->get_content_area->get_children ) {
if ( $child->isa( 'Gtk3::Label' ) ) {
$child->set_text( $text );
$infobar->queue_draw;
}
}
}
sub set_infobar_button {
my ( $stock_icon, $signal ) = @_;
if ( !$infobar->get_action_area->get_children ) {
$infobar->add_button( $stock_icon, $signal );
} else {
for my $child ( $infobar->get_action_area->get_children ) {
if ( $child->isa( 'Gtk3::Button' ) ) {
$child->set_label( $stock_icon );
}
}
}
}
sub destroy_button {
# Remove button from $infobar
for my $child ( $infobar->get_action_area->get_children ) {
if ( $child->isa( 'Gtk3::Button' ) ) {
$child->destroy;
}
}
}
sub progress_timeout {
$pb->pulse;
return TRUE;
}
sub add_ua_proxy {
my $agent = LWP::UserAgent->new( ssl_opts => { verify_hostname => 1 } );
$agent->timeout( 20 );
$agent->protocols_allowed( [ 'http', 'https' ] );
if ( ClamTk::Prefs->get_preference( 'HTTPProxy' ) ) {
if ( ClamTk::Prefs->get_preference( 'HTTPProxy' ) == 1 ) {
$agent->env_proxy;
} elsif ( ClamTk::Prefs->get_preference( 'HTTPProxy' ) == 2 ) {
my $path = ClamTk::App->get_path( 'db' );
$path .= '/local.conf';
my ( $url, $port );
if ( -e $path ) {
if ( open( my $FH, '<', $path ) ) {
while ( <$FH> ) {
if ( /HTTPProxyServer\s+(.*?)$/ ) {
$url = $1;
}
last
if ( !$url );
if ( /HTTPProxyPort\s+(\d+)$/ ) {
$port = $1;
}
}
close( $FH );
$ENV{ HTTPS_PROXY } = "$url:$port";
$ENV{ HTTP_PROXY } = "$url:$port";
$ENV{ PERL_LWP_SSL_VERIFY_HOSTNAME } = 0;
$ENV{ HTTPS_DEBUG } = 1;
$agent->proxy( http => "$url:$port" );
$agent->proxy( https => "$url:$port" );
}
}
}
}
return $agent;
}
1;