#!/usr/bin/perl

# Checks a Solaris host patchlevel against requirements for DST 2007
# remediation.

# Annoyed?  Write your Congressman today and demand the repeal of DST
# entirely!  It's outdated, and causes far more trouble than it's worth.

# Written by J.D. Baldwin <baldwin@panix.com>, March 2007, v 1.1

# Redistribute this script freely.  If you modify it, note that in
# comments before redistributing it.

# Thanks to Sergey Galitskiy for pointing out a needed fix to account
# for 'i386' returned from uname -p

use warnings;
use strict;
use Data::Dumper;

my $SHOWREV = '/usr/bin/showrev -p';
my $UNAME_R = '/usr/bin/uname -r';
my $UNAME_P = '/usr/bin/uname -p';

my %this_sys_patches = ( );

my %superseding_patches = (

               # If one of these patches is found, it's the same as if
               # the referenced patch (at least) is found, too --
               # e.g., if 118833-17 is found, it obsoletes 119689-07.
                           
                           '118833' => '17,119689-07',
                           '118855' => '15,121208-03'
                           );

my %patches_of_interest = (
			   '109809' => 1,
			   '108993' => 1,
			   '109810' => 1,
			   '108994' => 1,
			   '113225' => 1,
			   '112874' => 1,
			   '116545' => 1,
			   '114432' => 1,
			   '122032' => 1,
			   '119689' => 1,
			   '122033' => 1,
			   '121208' => 1
		    );

# Patches are delineated SPARC / i386

my %best_patches = (
		    sparc_8  => '109809-06,108993-52',
		    i386_8    => '109810-06,108994-52',
		    sparc_9  => '113225-08,112874-33',
		    i386_9    => '116545-06,114432-23',
		    sparc_10 => '122032-04,119689-07',
		    i386_10   => '122033-04,121208-03'
		    );

my %us_patches = (
		  sparc_8  => '109809-02,108993-52',
		  i386_8    => '109810-02,108994-52',
		  sparc_9  => '113225-03,112874-33',
		  i386_9    => '116545-02,114432-23',
		  sparc_10 => '122032-01,119689-07',
		  i386_10   => '122033-01,121208-03'
		  );

my %canada_patches = (
		      sparc_8  => '109809-04',
		      i386_8    => '109810-04',
		      sparc_9  => '113225-05',
		      i386_9    => '116545-04',
		      sparc_10 => '122032-03',
		      i386_10   => '122033-03'
		      );

my %w_aus_patches = (
		     sparc_8  => '109809-05',
		     i386_8    => '109810-05',
 		     sparc_9  => '113225-07',
		     i386_9    => '116545-05',
		     sparc_10 => '122032-03',
		     i386_10   => '122033-03'
		     );

my %bahamas_patches = (
		       sparc_8  => '109809-06',
		       i386_8    => '109810-06',
		       sparc_9  => '113225-08',
		       i386_9    => '116545-06',
		       sparc_10 => '122032-04',
		       i386_10   => '122033-04'
		   );

sub max
{
    my ( $a, $b ) = @_;

    return $a if ( $a > $b );
    return $b;
}

sub check_patches
{
    my ( $t, $this_sys_hash_ref, $tz_of_interest_hash_ref, $tzoutstr ) = @_;

    my $reasons = '';

    my @required = split /,/, $$tz_of_interest_hash_ref{$t};
    
    foreach ( @required )
    {
	my ( $pnum, $prev ) = split /\-/;
	$prev =~ s/^0*//;

	if ( ! defined $$this_sys_hash_ref{$pnum} )
	{
	    $reasons .= sprintf "    missing patch %d, need revision %02d\n", 
                                $pnum, $prev;
	}
	elsif ( $prev > $$this_sys_hash_ref{$pnum} )
	{
	    $reasons .= sprintf "    need revision %02d of patch %d, found only %02d\n",
	                        $prev, $pnum, $$this_sys_hash_ref{$pnum};
	}
    }

    if ( length($reasons) > 0 )
    {
	print "This system is NOT patched for $tzoutstr, because:\n";
	print $reasons, "\n";
	return 0;
    }
    else
    {
	print "This system is patched for $tzoutstr\n\n";
	return 1;
    }
}
    


############### MAIN PROGRAM #############


open(PATCHES, "$SHOWREV |")
    or die "Could not open pipe from showrev binary: $!";
open(REV, "$UNAME_R |")
    or die "Could not open pipe from uname -r binary: $!";
open(ARCH, "$UNAME_P |")
    or die "Could not open pipe from uname -p binary: $!";

my $revision = <REV>;
chomp $revision;
my $arch = <ARCH>;
chomp $arch;

close REV;
close ARCH;

if ( $revision !~ m/^5\.[0-9]/ )
{
    die "Revision $revision is not a valid Solaris revision\n";
}

if ( $arch =~ m/^i386/i )
{
    $arch = 'i386';
}
elsif ( $arch =~ m/^x86/i )
{
    $arch = 'i386';
}
elsif ( $arch =~ m/^sparc$/i )
{
    $arch = 'sparc';
}
else
{
    die "Unknown architecture $arch, should be x86, i386 or sparc\n";
}

my ( $dummy, $oslevel ) = split /\./, $revision;

unless ( ( $oslevel >= 8 ) && ( $oslevel <= 10 ) )
{
    die "OS level $oslevel is not supported for DST\n";
}

while ( <PATCHES> )
{
    my $patch_id;
    ( $dummy, $patch_id ) = split;
    my ( $patchnum, $patchrev ) = split /\-/, $patch_id;
    $patchrev =~ s/^0*//;

    if ( defined $superseding_patches{$patchnum} )
    {
        my ( $ssp_rev, $patch_superseded ) = split /,/, $superseding_patches{$patchnum};
        my ( $pnum, $prev ) = split /\-/, $patch_superseded;
        $prev =~ s/^0*//;
        $ssp_rev =~ s/^0*//;

        if ( $patchrev >= $ssp_rev )
        {
            $this_sys_patches{$pnum} = 0 if ( ! defined $this_sys_patches{$pnum} );
            $this_sys_patches{$pnum} = max($prev, $this_sys_patches{$pnum});
        }
    }

    next if ( ! defined $patches_of_interest{$patchnum} );

    $this_sys_patches{$patchnum} = 0 if ( ! defined $this_sys_patches{$patchnum} );
    $this_sys_patches{$patchnum} = max($patchrev, $this_sys_patches{$patchnum});
}

close PATCHES;

my $this_sys = "${arch}_${oslevel}";

# Now check the found patches against what we hope to have:

if ( check_patches($this_sys, \%this_sys_patches, 
		   \%best_patches, "all known timezones") )
{
    exit 0;
}

check_patches($this_sys, \%this_sys_patches, \%us_patches, "all USA timezones");
check_patches($this_sys, \%this_sys_patches, \%canada_patches, "all Canadian timezones");
check_patches($this_sys, \%this_sys_patches, \%w_aus_patches, "all Western Australia timezones");
check_patches($this_sys, \%this_sys_patches, \%bahamas_patches, "the Bahamas timezone");
