#!/usr/bin/perl

# apt-dater - terminal-based remote package update manager
#
# Authors:
#   Andre Ellguth <ellguth@ibh.de>
#   Thomas Liske <liske@ibh.de>
#
# Copyright Holder:
#   2008-2014 (C) IBH IT-Service GmbH [http://www.ibh.de/apt-dater/]
#
# License:
#   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 2 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 package; if not, write to the Free Software
#   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
#

use strict;
use warnings;

my $CFGFILE = '/etc/apt-dater-host.conf';
my $GETROOT = 'sudo';
my $FORBID_REFRESH = 0;
my $FORBID_UPGRADE = 0;
my $FORBID_INSTALL = 0;
my $UUIDFILE = '/etc/apt-dater-host.uuid';
my @CLUSTERS;
my $NEEDRESTART;
my $RUNNEEDREST;

my $CMD = shift;
my $ADPROTO = '0.7';

$ENV{'LC_ALL'} = 'C';

if (-r $CFGFILE) {
    eval `cat "$CFGFILE"` ;

    if($@ ne '') {
	print "ADPROTO: $ADPROTO\n";
	print "ADPERR: Invalid config $CFGFILE: $@\n";
	exit;
    }
}

$GETROOT = '' if($> == 0);

die "Don't call this script directly!\n" unless (defined($CMD));
if($CMD eq 'sshkey') {
    die "Sorry, no shell access allowed!\n"
      unless(defined($ENV{'SSH_ORIGINAL_COMMAND'}));

    @ARGV = split(' ', $ENV{'SSH_ORIGINAL_COMMAND'});

    shift;
    $CMD = shift;
}
die "Invalid command '$CMD'!\n" unless ($CMD=~/^(refresh|status|upgrade|install|kernel)$/);

if ($CMD eq 'refresh') {
    print "ADPROTO: $ADPROTO\n";
    if ($FORBID_REFRESH) {
	print STDERR "\n\n** Sorry, apt-dater based refreshs on this host are disabled! **\n\n"
    }
    else {
	&do_refresh;
    }
    &do_status;
    &do_kernel;
}
elsif ($CMD eq 'status') {
    print "ADPROTO: $ADPROTO\n";
    &do_status;
    &do_kernel;
}
elsif ($CMD eq 'upgrade') {
    if ($FORBID_UPGRADE) {
	print STDERR "\n\n** Sorry, apt-dater based upgrades on this host are disabled! **\n\n"
    }
    else {
	&do_upgrade;
	system(($GETROOT ? $GETROOT : ()), $NEEDRESTART) if($RUNNEEDREST);
    }
}
elsif ($CMD eq 'install') {
    if ($FORBID_INSTALL) {
	print STDERR "\n\n** Sorry, apt-dater based installations on this host are disabled! **\n\n"
    }
    else {
	&do_install(@ARGV);
	system(($GETROOT ? $GETROOT : ()), $NEEDRESTART) if($RUNNEEDREST);
    }
}
elsif ($CMD eq 'kernel') {
    print "ADPROTO: $ADPROTO\n";
    &do_kernel;
}
else {
    die "Internal error!\n";
}

sub do_refresh() {
    if(system("$GETROOT zypper refresh")) {
	print "\nADPERR: Failed to execute '$GETROOT zypper refresh' ($?).\n";
	exit(1);
    }
}

sub get_virt() {
    return "Unknown" unless (-x '/usr/bin/imvirt');

    my $imvirt;
    chomp($imvirt = `/usr/bin/imvirt`);

    return $imvirt;
}

sub get_uname() {
    my $kernel;
    my $machine;

    chomp($kernel = `uname -s`);
    chomp($machine = `uname -m`);
    return "$kernel|$machine";
}

sub do_status() {
    # retrieve lsb informations
    unless(open(HLSB, "lsb_release -a 2> /dev/null |")) {
	print "\nADPERR: Failed to execute 'lsb_release -a' ($!).\n";
	exit(1);
    }

    my %lsb;
    while(<HLSB>) {
	chomp;

	$lsb{$1}=$2 if (/^(Distributor ID|Release|Codename):\s+(\S.*)$/);
    }
    close(HLSB);
    if($?) {
	print "\nADPERR: Error executing 'lsb_release -a' ($?).\n";
	exit(1);
    }

    print "LSBREL: $lsb{'Distributor ID'}|$lsb{'Release'}|$lsb{'Codename'}\n";

    # retrieve virtualization informations
    print "VIRT: ".&get_virt."\n";

    # retrieve uname informations
    print "UNAME: ".&get_uname."\n";

    # calculate forbid mask
    my $mask = 0;
    $mask |= 1 if ($FORBID_REFRESH);
    $mask |= 2 if ($FORBID_UPGRADE);
    $mask |= 4 if ($FORBID_INSTALL);
    print "FORBID: $mask\n";

    # add installation UUID if available
    if(-r $UUIDFILE && -s $UUIDFILE) {
	print "UUID: ", `head -n 1 "$UUIDFILE"`;
    }

    # add cluster name if available
    foreach my $CLUSTER (@CLUSTERS) {
	print "CLUSTER: $CLUSTER\n";
    }

    # add needrestart batch output
    system(($GETROOT ? $GETROOT : ()), $NEEDRESTART, '-b') if($NEEDRESTART);

    # get packages which might be upgraded
    my %updates;
    unless(open(HAPT, "$GETROOT zypper --terse list-updates -a |")) {
	print "\nADPERR: Failed to execute '$GETROOT zypper --terse list-updates -a' ($!).\n";
	exit(1);
    };
    while(<HAPT>) {
	chomp;
	my @line = split(/\s*\|\s*/);
	$updates{$line[2]} = $line[4] if($#line >= 4);
    }
    close(HAPT);
    if($?) {
	print "\nADPERR: Error executing '$GETROOT zypper --terse list-updates -a' ($?).\n";
	exit(1);
    }

    # get version of installed packages
    my %installed;
    my %status;
    unless(open(HDPKG, "rpm -qa --qf '%{NAME}|%{VERSION}-%{RELEASE}|\n' |")) {
	print "\nADPERR: Failed to execute \"rpm -qa --qf '%{NAME}|%{VERSION}-%{RELEASE}|\\n'\" ($!).\n";
	exit(1);
    };

    while(<HDPKG>) {
	chomp;

	next unless (/^(\S+)\|\S+\|/);

	print "STATUS: ";
	print;
	  
	if($updates{$1}) {
	    print 'u=', $updates{$1};
	}
	else {
	    print 'i';
	}

	print "\n";
    }
    close(HDPKG);
    if($?) {
	print "\nADPERR: Error executing \"rpm -qa --qf '%{NAME}|%{VERSION}-%{RELEASE}|\\n'\" ($?).\n";
	exit(1);
    };

}

sub do_upgrade() {
    system("$GETROOT zypper -r update");
}

sub do_install() {
    if($GETROOT) {
	system($GETROOT, 'zypper', '-r', 'install', @_);
    }
    else {
	system('rug', '-r', 'install', @_);
    }
}

sub do_kernel() {
    my $infostr = 'KERNELINFO:';
    my $version = `uname -r`;
    chomp($version);

    my $add = '';
    $add = $1 if($version =~ /(-[a-z]+)$/);

    my $pos = 0;

    my $kinstalled;
    my %distri;
    unless(open(HKERNEL, "rpm -q --whatprovides kernel --qf '%{NAME}|%{VERSION}-%{RELEASE}\n' |")) {
	print "$infostr 9 $version\n";
	return;
    }
    while(<HKERNEL>) {
	chomp;

	if(/^kernel$add\|(.+)/) {
	    my $ver = $1;
	    $distri{$ver.$add} = 1;
	    
	    if(!$kinstalled) {
	        $kinstalled = $ver;
	    }
	    else {
	        $kinstalled = $ver if(&versioncmp($kinstalled, $ver) < 0);
	    }
	}
    }
    close(HKERNEL);
    if($?) {
	print "$infostr 9 $version\n";
	return;
    }

    unless($distri{$version}) {
	print "$infostr 2 $version\n";
	return;
    }

    unless($kinstalled.$add cmp $version) {
	print "$infostr 0 $version\n";
	return;
    }
    print "$infostr 1 $version\n";
}

##
# Taken from Sort::Versions 1.4
# Copyright (c) 1996, Kenneth J. Albanowski.
##
sub versioncmp() {
    my @A = ($_[0] =~ /([-.]|\d+|[^-.\d]+)/g);
    my @B = ($_[1] =~ /([-.]|\d+|[^-.\d]+)/g);

    my ($A, $B);
    while (@A and @B) {
	$A = shift @A;
	$B = shift @B;
	if ($A eq '-' and $B eq '-') {
	    next;
	} elsif ( $A eq '-' ) {
	    return -1;
	} elsif ( $B eq '-') {
	    return 1;
	} elsif ($A eq '.' and $B eq '.') {
	    next;
	} elsif ( $A eq '.' ) {
	    return -1;
	} elsif ( $B eq '.' ) {
	    return 1;
	} elsif ($A =~ /^\d+$/ and $B =~ /^\d+$/) {
	    if ($A =~ /^0/ || $B =~ /^0/) {
		return $A cmp $B if $A cmp $B;
	    } else {
		return $A <=> $B if $A <=> $B;
	    }
	} else {
	    $A = uc $A;
	    $B = uc $B;
	    return $A cmp $B if $A cmp $B;
	}	
    }
    @A <=> @B;
}
