#!/usr/bin/perl ############################################################################### # # # IPFire.org - A linux based firewall # # Copyright (C) 2007-2022 IPFire Team # # # # This program is free software: you can redistribute it and/or modify # # it under the terms of the GNU General Public License as published by # # the Free Software Foundation, either version 3 of the License, or # # (at your option) any later version. # # # # This program is distributed in the hope that it will be useful, # # but WITHOUT ANY WARRANTY; without even the implied warranty of # # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # # GNU General Public License for more details. # # # # You should have received a copy of the GNU General Public License # # along with this program. If not, see . # # # ############################################################################### use strict; use List::Util qw(any); use URI; # enable only the following on debugging purpose #use warnings; #use CGI::Carp 'fatalsToBrowser'; require '/var/ipfire/general-functions.pl'; require "${General::swroot}/lang.pl"; require "${General::swroot}/header.pl"; require "/opt/pakfire/lib/functions.pl"; my %cgiparams=(); my $errormessage = ''; my %color = (); my %pakfiresettings = (); my %mainsettings = (); # The page mode is used to explictly switch between user interface functions: my $PM_DEFAULT = 'default'; # Default user interface with command processing my $PM_LOGREAD = 'logread'; # Log messages viewer (ignores all commands) my $pagemode = $PM_DEFAULT; # Get Pakfire status my %pakfire_status = &Pakfire::status(); # Load general settings &General::readhash("${General::swroot}/main/settings", \%mainsettings); &General::readhash("${General::swroot}/pakfire/settings", \%pakfiresettings); &General::readhash("/srv/web/ipfire/html/themes/ipfire/include/colors.txt", \%color); # Get CGI POST request data $cgiparams{'ACTION'} = ''; $cgiparams{'FORCE'} = ''; $cgiparams{'INSPAKS'} = ''; $cgiparams{'DELPAKS'} = ''; &Header::getcgihash(\%cgiparams); # Get CGI GET request data (if available) if($ENV{'QUERY_STRING'}) { my $uri = URI->new($ENV{'REQUEST_URI'}); my %query = $uri->query_form; my $mode = lc($query{'mode'} // ''); if(($mode eq $PM_DEFAULT) || ($mode eq $PM_LOGREAD)) { $pagemode = $mode; # Limit to existing modes } } ### Process AJAX/JSON request ### if($cgiparams{'ACTION'} eq 'json-getstatus') { # Send HTTP headers _start_json_output(); # Read /var/log/messages backwards until a "Pakfire started" header is found, # to capture all messages of the last (i.e. current) Pakfire run my @messages = `tac /var/log/messages 2>/dev/null | sed -n '/pakfire:/{p;/Pakfire.*started/q}'`; # Test if the log contains an error message (fastest implementation, stops at first match) my $failure = any{ index($_, 'ERROR') != -1 } @messages; # Collect Pakfire status my %status = ( 'running' => &_is_pakfire_busy() || "0", 'running_since' => &General::age("$Pakfire::lockfile") || "0s", 'reboot' => ("$pakfire_status{'RebootRequired'}" eq "yes") || "0", 'failure' => $failure || "0" ); # Start JSON file print "{\n"; foreach my $key (keys %status) { my $value = $status{$key}; print qq{\t"$key": "$value",\n}; } # Print sanitized messages in reverse order to undo previous "tac" print qq{\t"messages": [\n}; for my $index (reverse (0 .. $#messages)) { my $line = $messages[$index]; $line =~ s/[[:cntrl:]<>&\\]+//g; print qq{\t\t"$line"}; print ",\n" unless $index < 1; } print "\n\t]\n"; # Finalize JSON file & stop print "}"; exit; } ### Process Pakfire install/update commands ### if(($cgiparams{'ACTION'} ne '') && ($pagemode eq $PM_DEFAULT)) { if(&_is_pakfire_busy()) { $errormessage = $Lang::tr{'pakfire already busy'}; $pagemode = $PM_LOGREAD; # Running Pakfire instance found, switch to log viewer mode } elsif(($cgiparams{'ACTION'} eq 'install') && ($cgiparams{'FORCE'} eq 'on')) { my @pkgs = split(/\|/, $cgiparams{'INSPAKS'}); &General::system_background("/usr/local/bin/pakfire", "install", "--non-interactive", "--no-colors", @pkgs); &_http_pagemode_redirect($PM_LOGREAD, 1); } elsif(($cgiparams{'ACTION'} eq 'remove') && ($cgiparams{'FORCE'} eq 'on')) { my @pkgs = split(/\|/, $cgiparams{'DELPAKS'}); &General::system_background("/usr/local/bin/pakfire", "remove", "--non-interactive", "--no-colors", @pkgs); &_http_pagemode_redirect($PM_LOGREAD, 1); } elsif($cgiparams{'ACTION'} eq 'update') { &General::system_background("/usr/local/bin/pakfire", "update", "--force", "--no-colors"); &_http_pagemode_redirect($PM_LOGREAD, 1); } elsif($cgiparams{'ACTION'} eq 'upgrade') { &General::system_background("/usr/local/bin/pakfire", "upgrade", "-y", "--no-colors"); &_http_pagemode_redirect($PM_LOGREAD, 1); } elsif($cgiparams{'ACTION'} eq $Lang::tr{'save'}) { $pakfiresettings{"TREE"} = $cgiparams{"TREE"}; # Check for valid input if ($pakfiresettings{"TREE"} !~ m/^(stable|testing|unstable)$/) { $errormessage .= $Lang::tr{'pakfire invalid tree'}; } unless ($errormessage) { &General::writehash("${General::swroot}/pakfire/settings", \%pakfiresettings); # Update lists &General::system_background("/usr/local/bin/pakfire", "update", "--force", "--no-colors"); &_http_pagemode_redirect($PM_LOGREAD, 1); } } } ### Start pakfire page ### &Header::showhttpheaders(); ###--- HTML HEAD ---### my $extraHead = < /* Main screen */ table#pfmain { width: 100%; border-style: hidden; table-layout: fixed; } #pfmain td { padding: 5px 20px 0; text-align: center; } #pfmain tr:not(:last-child) > td { padding-bottom: 1.5em; } #pfmain tr > td.heading { padding: 0; font-weight: bold; background-color: $color{'color20'}; } .pflist { width: 100%; text-align: left; margin-bottom: 0.8em; } /* Pakfire log viewer */ section#pflog-header { width: 100%; display: flex; text-align: left; align-items: center; column-gap: 20px; } #pflog-header > div:last-child { margin-left: auto; margin-right: 20px; } #pflog-header span { line-height: 1.3em; } #pflog-header span:empty::before { content: "\\200b"; /* zero width space */ } pre#pflog-messages { margin-top: 0.7em; padding-top: 0.7em; border-top: 0.5px solid $Header::bordercolour; text-align: left; min-height: 15em; overflow-x: auto; } END ; ###--- END HTML HEAD ---### &Header::openpage($Lang::tr{'pakfire configuration'}, 1, $extraHead); &Header::openbigbox('100%', 'left', '', $errormessage); # Show error message if ($errormessage) { &Header::openbox('100%', 'left', $Lang::tr{'error messages'}); print "$errormessage \n"; &Header::closebox(); } # Show only log output while Pakfire is running and stop afterwards if(($pagemode eq $PM_LOGREAD) || (&_is_pakfire_busy())) { &Header::openbox("100%", "center", "Pakfire"); print <
$Lang::tr{'active'}
$Lang::tr{'pakfire working'}

$Lang::tr{'refresh'}



END
;

	&Header::closebox();
	&Header::closebigbox();
	&Header::closepage();
	exit;
}

# Show Pakfire install/remove dependencies and confirm form
# (_is_pakfire_busy status was checked before and can be omitted)
if (($cgiparams{'ACTION'} eq 'install') && ($pagemode eq $PM_DEFAULT)) {
	&Header::openbox("100%", "center", $Lang::tr{'request'});

	my @pkgs = split(/\|/, $cgiparams{'INSPAKS'});
	my @output = &General::system_output("/usr/local/bin/pakfire", "resolvedeps", "--no-colors", @pkgs);
	print <

$Lang::tr{'pakfire install package'} @{pkgs}
$Lang::tr{'pakfire possible dependency'}

END
	foreach (@output) {
		$_ =~ s/\\[[0-1]\;[0-9]+m//g;
		print "$_\n";
	}
	print <
		$Lang::tr{'pakfire accept all'}
		 
		
END &Header::closebox(); &Header::closebigbox(); &Header::closepage(); exit; } elsif (($cgiparams{'ACTION'} eq 'remove') && ($pagemode eq $PM_DEFAULT)) { &Header::openbox("100%", "center", $Lang::tr{'request'}); my @pkgs = split(/\|/, $cgiparams{'DELPAKS'}); my @output = &General::system_output("/usr/local/bin/pakfire", "resolvedeps", "--no-colors", @pkgs); print <

$Lang::tr{'pakfire uninstall package'} @{pkgs}
$Lang::tr{'pakfire possible dependency'}

END
	foreach (@output) {
		$_ =~ s/\\[[0-1]\;[0-9]+m//g;
		print "$_\n";
	}
	print <
		$Lang::tr{'pakfire uninstall all'}
		 
		
END &Header::closebox(); &Header::closebigbox(); &Header::closepage(); exit; } # Show Pakfire main page my %selected=(); my %checked=(); $selected{"TREE"} = (); $selected{"TREE"}{"stable"} = ""; $selected{"TREE"}{"testing"} = ""; $selected{"TREE"}{"unstable"} = ""; $selected{"TREE"}{$pakfiresettings{"TREE"}} = "selected"; &Header::openbox("100%", "center", "Pakfire"); print < END if ("$pakfire_status{'RebootRequired'}" eq "yes") { print "\t\t$Lang::tr{'needreboot'}!\n"; } print <$Lang::tr{'pakfire system state'}: $Lang::tr{'available updates'}: $Lang::tr{'pakfire core update level'}: $pakfire_status{'Release'}
$Lang::tr{'pakfire last update'} $pakfire_status{'LastUpdate'} $Lang::tr{'pakfire ago'}
$Lang::tr{'pakfire last serverlist update'} $pakfire_status{'LastServerListUpdate'} $Lang::tr{'pakfire ago'}
$Lang::tr{'pakfire last core list update'} $pakfire_status{'LastCoreListUpdate'} $Lang::tr{'pakfire ago'}
$Lang::tr{'pakfire last package update'} $pakfire_status{'LastPakListUpdate'} $Lang::tr{'pakfire ago'}
$Lang::tr{'pakfire available addons'} $Lang::tr{'pakfire installed addons'}

$Lang::tr{'pakfire install description'}

$Lang::tr{'pakfire uninstall description'}

END &Header::closebox(); &Header::openbox("100%", "center", "$Lang::tr{'settings'}"); print <
$Lang::tr{'pakfire tree'}
 
END &Header::closebox(); &Header::closebigbox(); &Header::closepage(); ###--- Internal functions ---### # Check if pakfire is already running (extend test here if necessary) sub _is_pakfire_busy { # Return immediately if lockfile is present if(-e "$Pakfire::lockfile") { return 1; } # Check if a PID of a running pakfire instance is found # (The system backpipe command is safe, because no user input is computed.) my $pakfire_pid = `pidof -s /usr/local/bin/pakfire`; chomp($pakfire_pid); if($pakfire_pid) { return 1; } # Pakfire isn't running return 0; } # Send HTTP headers sub _start_json_output { print "Cache-Control: no-cache, no-store\n"; print "Content-Type: application/json\n"; print "\n"; # End of HTTP headers } # Send HTTP 303 redirect headers to change page mode # GET is always used to display the redirected page, which will remove already processed POST form data. # Note: Custom headers must be sent before the HTML output is started by &Header::showhttpheaders(). # If switch_mode is set to true, the global page mode variable ("$pagemode") is also updated immediately. sub _http_pagemode_redirect { my ($mode, $switch_mode) = @_; $mode //= $PM_DEFAULT; $switch_mode //= 0; # Send HTTP redirect with GET parameter my $location = "https://$ENV{'SERVER_NAME'}:$ENV{'SERVER_PORT'}$ENV{'SCRIPT_NAME'}?mode=${mode}"; print "Status: 303 See Other\n"; print "Location: $location\n"; # Change global page mode if($switch_mode) { $pagemode = $mode; } }