#!/usr/bin/perl
###############################################################################
#                                                                             #
# IPFire.org - A linux based firewall                                         #
# Copyright (C) 2007-2021   IPFire Team   <info@ipfire.org>                   #
#                                                                             #
# 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 <http://www.gnu.org/licenses/>.       #
#                                                                             #
###############################################################################

	### Clean up our environment
	#
	delete @ENV{qw(IFS CDPATH ENV BASH_ENV PATH)};
	$< = $>;

	# Store keys here
	$ENV{"GNUPGHOME"} = "/opt/pakfire/etc/.gnupg";

	require "/opt/pakfire/lib/functions.pl";

	my $interactive = 1;
	my $force = "noforce";
	my $locked;

	&Pakfire::logger("PAKFIRE INFO: IPFire Pakfire $Conf::version started!");

	### Check if we are running as root
	#
	my $user = qx(whoami);
	chomp($user);
	unless ( "$user" eq "root" ) {
	  &Pakfire::message("PAKFIRE ERROR: You must run pakfire as user root!");
	  exit 1;
	}

	unless ( -e "/var/ipfire/red/active" ) {
		&Pakfire::message("PAKFIRE ERROR: You need to be online to run pakfire!");
		exit 2;
	}

	# Check if a lockfile already exists.
	if (-e "$Pakfire::lockfile") {
		&Pakfire::message("PAKFIRE ERROR: Another instance of pakfire is already running!");
		exit 1;
	}

	# Write lockfile.
	open(LOCK, ">$Pakfire::lockfile");

	# Pakfire has locked in this session set locket to "1".
	$locked = "1";

	# Close filehandle.
	close(LOCK);

	### Check if we are started by another name
	#
	if ( $0 =~ /pakfire-update$/ ) {
		&Pakfire::message("CRON INFO: Running an update");
		my $random = int(rand(60));
		&Pakfire::logger("CRON INFO: Waiting for $random seconds.");
		sleep($random);
		$ARGV[0] = "update";
		$interactive = 0;
	} elsif ( $0 =~ /pakfire-upgrade$/ ) {
		&Pakfire::message("CRON INFO: Running an upgrade");
		my $random = int(rand(3600));
		&Pakfire::logger("CRON INFO: Waiting for $random seconds.");
		sleep($random);
		$ARGV[0] = "upgrade";
		$interactive = 0;
	}

	unless (@ARGV) {
		&Pakfire::usage;
	}

	foreach (@ARGV) {
		if ("$_" =~ "^-") {
			# Turn off interactive mode
			$interactive = 0 if ("$_" eq "--non-interactive");
			$interactive = 0 if ("$_" eq "-y");

			# Turn off shell colors - Bad for displaying in webinterface
			$Pakfire::enable_colors = 0 if ("$_" eq "--no-colors");

			# Turn on force mode
			$force = "force" if ("$_" eq "-f" );
			$force = "force" if ("$_" eq "--force" );
		}
	}

	if ("$ARGV[0]" eq "install") {
		shift;

		### Make sure that the list is not outdated.
		&Pakfire::dbgetlist("noforce");

		my %paklist = &Pakfire::dblist("all");

		my $dep;
		my @deps;
		my $pak;
		my @paks;
		my @temp;
		my $return;
		my @all;
		foreach $pak (@ARGV) {
			unless ("$pak" =~ "^-") {
				if (defined $paklist{$pak}) {
					if ("$paklist{$pak}{'Installed'}" eq "yes") {
						&Pakfire::message("PAKFIRE INFO: $pak is already installed");
						next;
					}
					push(@paks,$pak);
					push(@all,$pak);
					@temp = &Pakfire::resolvedeps("$pak");
					foreach $dep (@temp) {
						push(@deps,$dep) if $dep;
						push(@all,$dep) if $dep;
					}
				} else {
					&Pakfire::message("");
					&Pakfire::message("PAKFIRE WARN: The pak \"$pak\" is not known. Please try running \"pakfire update\".");
				}
			}
		}

		unless (@paks) {
			&Pakfire::message("PAKFIRE ERROR: No packages to install. Exiting...");
			exit 1;
		}

		if (@deps) {
			my %sort = map{ $_, 1 } @deps;
			@deps = keys %sort;
			&Pakfire::message("");
			&Pakfire::message("PAKFIRE INFO: Packages to install for dependencies:");
		}
		foreach $dep (@deps) {
		  my $size = &Pakfire::getsize("$dep");
			$size = &Pakfire::beautifysize($size);
		  &Pakfire::message("PAKFIRE INFO: $dep \t - $size");
		}

		&Pakfire::message("");
		&Pakfire::message("");
		&Pakfire::message("PAKFIRE INFO: Packages to install:");
		foreach $pak (@paks) {
		  my $size = &Pakfire::getsize("$pak");
			$size = &Pakfire::beautifysize($size);
		  &Pakfire::message("PAKFIRE INFO: $pak \t - $size");
		}

		my $totalsize;
		foreach $pak (@all) {
			$totalsize = ($totalsize + &Pakfire::getsize("$pak"));
		}
		$totalsize = &Pakfire::beautifysize($totalsize);
		&Pakfire::message("");
		&Pakfire::message("PAKFIRE INFO: Total size: \t ~ $totalsize");
		&Pakfire::message("");

		if ($interactive) {
		  &Pakfire::message("PAKFIRE INFO: Is this okay? [y/N]");
			my $ret = <STDIN>;
			chomp($ret);
			&Pakfire::logger("PAKFIRE INFO: Answer: $ret");
			if ( $ret ne "y" ) {
			  &Pakfire::message("PAKFIRE ERROR: Installation aborted.");
			  exit 1;
			}
		} else {
			&Pakfire::logger("PAKFIRE INFO: Interaction skipped.");
		}

#		my %sort = map{ $_, 1 } @all;
#		@all = sort keys %sort;

		### Download first
		foreach $pak (@all) {
			&Pakfire::getpak("$pak", "");
		}

		&Pakfire::message("");

		foreach $pak (@deps) {
			&Pakfire::setuppak("$pak") if ($pak ne "");
		}


		foreach $pak (@paks) {
			&Pakfire::setuppak("$pak") if ($pak ne "");
		}


	} elsif ("$ARGV[0]" eq "remove") {
		shift;

		my @paks;
		my $pak;
		foreach $pak (@ARGV) {
			unless ("$pak" =~ "^-") {
				$return = &Pakfire::isinstalled($pak);
				if ($return ne 0) {
					&Pakfire::message("PAKFIRE WARN: $pak is not installed");
					next;
				}
				push(@paks, $pak);
			}
		}

		unless (@paks) {
			&Pakfire::message("PAKFIRE ERROR: No packages to remove. Exiting...");
			exit 1;
		}

		&Pakfire::message("");
		&Pakfire::message("");
		&Pakfire::message("PAKFIRE INFO: Packages to remove:");
		foreach $pak (sort @paks) {
		  my $size = &Pakfire::getsize("$pak");
			$size = &Pakfire::beautifysize($size);
		  &Pakfire::message("PAKFIRE INFO: $pak \t - $size");
		}

		if ($interactive) {
		  &Pakfire::message("PAKFIRE INFO: Is this okay? [y/N]");
			my $ret = <STDIN>;
			chomp($ret);
			&Pakfire::logger("PAKFIRE INFO: Answer: $ret");
			if ( $ret ne "y" ) {
			  &Pakfire::message("PAKFIRE ERROR: Installation aborted.");
			  exit 1;
			}
		}

		foreach $pak (@paks) {
			&Pakfire::removepak("$pak");
		}


	} elsif ("$ARGV[0]" eq "update") {
		&Pakfire::makeuuid();
		&Pakfire::getmirrors("$force");
		&Pakfire::dbgetlist("$force");
		&Pakfire::getcoredb("$force");

	} elsif ("$ARGV[0]" eq "upgrade") {
		my $use_color = "";
		my $reset_color = "";

		if ("$Pakfire::enable_colors" eq "1") {
			$reset_color = "$Pakfire::color{'normal'}";
			$use_color = "$Pakfire::color{'lightpurple'}";
		}

		&Pakfire::message("CORE INFO: Checking for Core-Updates...");

		### Make sure that the core db is not outdated. 
		&Pakfire::getcoredb("noforce");
		my %coredb = &Pakfire::coredbinfo();

		if (defined $coredb{'AvailableRelease'}) {
			&Pakfire::upgradecore();
		} else {
			&Pakfire::message("CORE INFO: No new Core-Updates available. You are on release ".$coredb{'Release'});
		}

		&Pakfire::message("PAKFIRE INFO: Checking for package updates...");
		### Make sure that the package list is not outdated. 
		&Pakfire::dbgetlist("noforce");
		
		my @deps = ();
		if (my %upgradepaks = &Pakfire::dblist("upgrade")) {
			# Resolve the dependencies of the to be upgraded packages
			@deps = &Pakfire::resolvedeps_recursive(keys %upgradepaks);

			foreach $pak (sort keys %upgradepaks) {
				print "${use_color}Update: $pak\nVersion: $upgradepaks{$pak}{'ProgVersion'} -> $upgradepaks{$pak}{'AvailableProgVersion'}\n";
				print "Release: $upgradepaks{$pak}{'Release'} -> $upgradepaks{$pak}{'AvailableRelease'}${reset_color}\n";
			}
			&Pakfire::message("");
			&Pakfire::message("PAKFIRE UPGR: We are going to install all packages listed above.");
			if ($interactive) {
			  &Pakfire::message("PAKFIRE INFO: Is this okay? [y/N]");
				my $ret = <STDIN>;
				chomp($ret);
				&Pakfire::logger("PAKFIRE INFO: Answer: $ret");
				if ( $ret ne "y" ) {
				  &Pakfire::message("PAKFIRE ERROR: Installation aborted.");
				  exit 1;
				}
			}
		
			# Download packages
			foreach $pak (sort keys %upgradepaks) {
				&Pakfire::getpak("$pak", "");
			}

			# Download dependencies
			foreach $pak (@deps) {
				&Pakfire::getpak("$pak", "");
			}

			# Install dependencies first
			foreach $pak (@deps) {
				&Pakfire::setuppak("$pak");
			}

			# Install all upgrades
			foreach $pak (sort keys %upgradepaks) {
				&Pakfire::upgradepak("$pak");
			}
		} else {
			&Pakfire::message("PAKFIRE WARN: No new package upgrades available.");
		}

	} elsif ("$ARGV[0]" eq "list") {
		my $count;
		my $coreupdate = 0;
		my $use_color = "";
		my $reset_color = "";
		my $filter = "all";

		shift if ("$ARGV[1]" =~ "^-"); 

		if ("$ARGV[1]" =~ /installed|notinstalled|upgrade/) {
			$filter = "$ARGV[1]";
		} elsif ($ARGV[1]) {
			&Pakfire::message("PAKFIRE ERROR: Not a known option $ARGV[1]"); 
			exit 1;
		}

		my $pak;
		my %paklist = &Pakfire::dblist($filter);

		if ("$Pakfire::enable_colors" eq "1") {
			$reset_color = "$Pakfire::color{'normal'}";
			$use_color = "$Pakfire::color{'lightgreen'}";
		}

  		# Check for available core upgrade first if list of upgrades is requested
		if ("$filter" eq "upgrade") {
			my %coredb = &Pakfire::coredbinfo();

			if (defined $coredb{'AvailableRelease'}) {
				print "${use_color}Core-Update $coredb{'CoreVersion'}\n";
				print "Release: $coredb{'Release'} -> $coredb{'AvailableRelease'}${reset_color}\n\n";
				$coreupdate = 1;
			}
		}

		foreach $pak (sort keys %paklist) {
			if ("$Pakfire::enable_colors" eq "1") {
				if ("$paklist{$pak}{'Installed'}" eq "yes") {
					if (defined $paklist{$pak}{'AvailableProgVersion'}) {
						$use_color = "$Pakfire::color{'lightgreen'}";
					} else {
						$use_color = "$Pakfire::color{'green'}";
					}
				} else {
					$use_color = "$Pakfire::color{'red'}"; 
				}
			}

			print "${use_color}Name: $pak\nProgVersion: $paklist{$pak}{'ProgVersion'}\n";
			print "Release: $paklist{$pak}{'Release'}\nInstalled: $paklist{$pak}{'Installed'}\n";
			if (defined $paklist{$pak}{'AvailableProgVersion'}) {
				print "Update available:\n Version: $paklist{$pak}{'ProgVersion'} -> $paklist{$pak}{'AvailableProgVersion'}\n Release: $paklist{$pak}{'Release'} -> $paklist{$pak}{'AvailableRelease'}\n";
			}
			print "${reset_color}\n";
			
		}

		$count = keys %paklist;
		if ($count > 0) {
			print "$count packages total.\n";
		} else {
			if (! $coreupdate) {
				&Pakfire::message("PAKFIRE WARN: No packages where found using filter $filter.");
				exit 1;
			}
		}
	} elsif ("$ARGV[0]" eq "info") {
		shift;

		my @paks;
		my $pak;
		foreach $pak (@ARGV) {
			unless ("$pak" =~ "^-") {
				push(@paks,$pak);
			}
		}

		unless ("@paks") {
			Pakfire::message("PAKFIRE ERROR: missing package name");
			Pakfire::usage;
			exit 1;
		}

		foreach $pak (@paks) {
			my %metadata = Pakfire::getmetadata($pak, "latest");

			### Check if pakfile was actually found
			if ($metadata{'Installed'} eq "no" && $metadata{'Available'} eq "no") {
				Pakfire::message("PAKFIRE WARN: Pak '$pak' not found.");
				last;
			}

			unless (defined $metadata{'Available'}) {
				Pakfire::message("PAKFIRE WARN: Unable to retrieve latest metadata for $pak. Information may be outdated.")
			}

			### Printout metadata in a user friendly format
			print "Name: $metadata{'Name'}\n";
			print "Summary: $metadata{'Summary'}\n";
			if ($metadata{'Available'} eq "yes") {
				print "Version: $metadata{'AvailableProgVersion'}-$metadata{'AvailableRelease'}\n";
			} else {
				print "Version: $metadata{'ProgVersion'}-$metadata{'Release'}\n";
			}
			print "Size: " . Pakfire::beautifysize("$metadata{'Size'}") . "\n";
			print "Dependencies: $metadata{'Dependencies'}\n";
			print "Pakfile: $metadata{'File'}\n";
			print "Service InitScripts: $metadata{'Services'}\n";
			print "Installed: $metadata{'Installed'}\n";
			### Generate a pak status message
			if (! defined $metadata{'Available'}) {
				print "Status: unknown (an error occured retrieving latest pak metadata)";
			} elsif ($metadata{'Available'} eq "no") {
				print "Status: obsolete (version $metadata{'ProgVersion'}-$metadata{'Release'} is installed)\n";
			} elsif ($metadata{'Installed'} eq "yes" && "$metadata{'Release'}" < "$metadata{'AvailableRelease'}") {
				print "Status: outdated (version $metadata{'ProgVersion'}-$metadata{'Release'} is installed)\n";
			} elsif ($metadata{'Installed'} eq "yes") {
				print "Status: up-to-date\n";
			} else {
				print "Status: not installed\n";
			}
			print "\n";
		}

	} elsif ("$ARGV[0]" eq "resolvedeps") {
		foreach (@ARGV) {
			next if ("$_" eq "resolvedeps");
			next if ("$_" =~ "^-");
			&Pakfire::resolvedeps("$_");
		}
	} elsif ("$ARGV[0]" eq "enable") {
		if ("$ARGV[1]" eq "updates") {
			system("ln -s ../../opt/pakfire/pakfire /etc/fcron.daily/pakfire-update");
		} elsif ("$ARGV[1]" eq "upgrades") {
			system("ln -s ../../opt/pakfire/pakfire /etc/fcron.daily/pakfire-upgrade");
		}
	} elsif ("$ARGV[0]" eq "disable") {
		if ("$ARGV[1]" eq "updates") {
			system("rm -f /etc/fcron.daily/pakfire-update");
		} elsif ("$ARGV[1]" eq "upgrades") {
			system("rm -f /etc/fcron.daily/pakfire-upgrade");
		}
	} elsif ("$ARGV[0]" eq "status") {
		my $exitcode = 0;
		my %status = &Pakfire::status;

		print "Core-Version: $status{'CoreVersion'}\n";
		print "Core-Update-Level: $status{'Release'}\n";
		print "Last update: $status{'LastUpdate'} ago\n";
		print "Last core-list update: $status{'LastCoreListUpdate'} ago\n";
		print "Last server-list update: $status{'LastServerListUpdate'} ago\n";
		print "Last packages-list update: $status{'LastPakListUpdate'} ago\n";
		print "Core-Update available: $status{'CoreUpdateAvailable'}";
		print " ($status{'AvailableRelease'})" if ("$status{'CoreUpdateAvailable'}" eq "yes");
		print "\nPackage-Updates available: $status{'PakUpdatesAvailable'}\n";
		print "Reboot required: $status{'RebootRequired'}\n";

		$exitcode += 2 if ($status{'CoreUpdateAvailable'} eq "yes");
		$exitcode += 3 if ($status{'PakUpdatesAvailable'} eq "yes");
		$exitcode += 4 if ($status{'RebootRequired'} eq "yes");
		exit $exitcode;
	} else {
		&Pakfire::usage;
	}

	END {
		&Pakfire::logger("PAKFIRE INFO: Pakfire has finished. Closing.");

		# Check if pakfire has been locked in this session.
		if ($locked) {
			# Remove lockfile.
			unlink($Pakfire::lockfile);
		}
	}

	exit 0;
