#!/usr/bin/perl -w use strict; use vars qw($opt_f); use Getopt::Std; my $VERSION = 1.01; =head1 NAME patcheck.pl - script to compare installed with recommended patches =head1 DESCRIPTION Compares the currently installed patches and their versions to the recommended or security patch list from Sun Microsystems =head1 README The script retrieves the currently installed patches and their versions by using the patchadd -p command. As a result, this script must be run as root; alternatively, it could be run as a non-root user through modification of the patchadd script by removal of the line that checks the UID. The currently installed patches are then compared to a flat file containing the recommended and security patches. The flat file must be of the format: =over 4 =item patch-version description =back The script displays to standard output (which can be re-directed to a file) the patches with a difference between the installed and the recommended version. It also displays those patches that are not installed on the system. =head1 PREREQUISITES strict vars Getopt::Std =head1 AUTHOR Sean C. DeZurik =head1 COPYRIGHT AND LICENSE Copyright (c) 2003, Sean C. DeZurik. All Rights Reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl. =begin comment =pod OSNAMES Solaris =pod SCRIPT CATEGORIES UNIX/System_administration =end comment =cut ##### Subroutine Prototypes ##### sub main(); # Arguments: None # Returns: None # Side effect: Main subroutine that drives the program execution sub checkArgs(); # Arguments: None # Returns: A scalar value containing the name of the input file # Side effects: Prompts the user to enter the name of the file if it is not # provided with the -f option. Exits from the program if the arguments # are incorrect or if the filename provided via argument does not exist # or cannot be opened. Calls getPatchFilename() to prompt the user. sub getPatchFilename(); # Arguments: None # Returns: A scalar value containing the name of the input file # Side effects: Prompts user to enter the name of the input file. Continues # to prompt the user if the file does not exist. Exits the program if the # user chooses to quit. sub getInstalledPatches($); # Arguments: A scalar value containing the absolute path to the patchadd script # Returns: A hash (list) containing the name value pairs of the base patch # number and the patch version number. sub comparePatchSets($%); # Arguments: 1) A scalar with the name of the file containing the numbers and # versions of the recommended or security patches # 2) A hash containing the name value pairs of the base patch # number and the patch version number of the patches already installed. # Returns: None # Side effects: Compares the currently installed patches and versions with the # recommended patches and versions. Calls screenDisplay() to show those # patches that are either missing or need to be updated. sub screenDisplay($$$$); # Arguments: 1) A scalar with the base patch number # 2) A scalar with the installed patch version # 3) A scalar with the recommended patch version # 4) A scalar with a description of what the patch fixes # Returns: None # Side effects: Creates a formatted output and writes it to the standard # output, showing the base patch number, the installed patch version, the # recommended patch version, and a description of the problem fixed. sub usage(); # Arguments: None # Returns: None # Side effects: Displays help on how to use the program ##### End of subroutine prototypes ##### # Call the subroutine serving as main() main(); ##### main ##### sub main() { my %patch = (); # Hash to hold patches and versions my $patchadd = "/usr/sbin/patchadd -p"; # patchadd script my $patchFile = ""; # User input recommended patches file # See how program invoked and retrieve input filename accordingly $patchFile = checkArgs(); # Populate hash with patches and version currently installed on system %patch = getInstalledPatches($patchadd); # Find any differences between what is installed and what is recommended comparePatchSets($patchFile, \%patch); } ##### End of main ##### ##### Subroutine definitions ##### sub checkArgs() { # Determine if there are any arguments given when the program was invoked. # If there are arguments, then the program will not be interactive. if (@ARGV) { # Retrieve the options and their arguments getopts('f:'); # Determine if any argument was given to the option if ("$opt_f" ne "" && -f "$opt_f") { # Valid filename return $opt_f; } else { # No argument given to the option so the program was called # incorrectly, or the argument was a file that does not exist. usage(); exit(); } } else { # No arguments so program is interactive. Call the function to ask the # user to enter the full path to the file. return getPatchFilename(); } } # End of checkArgs sub getPatchFilename() { my $filename = ""; # User input name of file with suggested patches # Get user input print "Enter the full path to the file with the recommended patches (q to quit):\n"; chomp($filename = ); # Make sure the file exists. If it does not, ask the user again but give # them a chance to exit by hitting q until (-f $filename || $filename eq "q") { print "Enter the full path to the file with the recommended patches (q to quit):\n"; chomp($filename = ); } # Check input and exit if the user chooses q if ($filename eq "q") { exit; } else { # File exists so return its full path return $filename; } } # End of getPatchFilename sub getInstalledPatches ($) { my $patchlist = $_[0]; # Command to list installed patches my %list = (); # Hash for installed patches and their versions # Separate each patch installed into base patch and version for (`$patchlist`) { # patch-version /^Patch: (\d*)-(\d*)/; # See if patch already in the list. If it is, compare the version and # if this version is more recent, replace the older version in the list if ($list{$1} && $list{$1} < $2) { $list{$1} = $2; } # Base patch is not in the list so add it else { $list{$1} = $2; } } # Send the list's values back return %list; } # End of getInstalledPatches sub comparePatchSets($%) { my $file = $_[0]; # File with recommended patches my $patchHash = $_[1]; # Flat list of installed patches and versions # Open file with recommended patches open(PATCHES, "$file") || die "Could not open file for reading"; # Loop through all recommended patches in the file while () { chomp; # Separate into patch, version, and description /(\d*)-(\d*)\s*(.*)/; # See if the recommended patch is installed. If it is, check version. if ($patchHash->{$1} && $patchHash->{$1} < $2) { # Recommended patch is newer screenDisplay($1, $patchHash->{$1}, $2, $3); } elsif ($patchHash->{$1} && $patchHash->{$1} >= $2) { #print "Patch $1 at $patchHash->{$1} is current with $2\n"; } else { # Patch is not present at all on the system screenDisplay($1, "Not installed", $2, $3); } } # Close the file handle close(PATCHES); } # End of comparePatchSets sub screenDisplay($$$$) { write(STDOUT); format STDOUT = ============================================================================== Patch Number: @<<<<<<<<<< $_[0] Installed Version: @<<<<<<<<<<<<<<< $_[1] Recommended Version: @<<<<< $_[2] Fixes: @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< $_[3] ============================================================================== . } # End of screenDisplay sub usage() { print "USAGE:\n"; print "patcheck.pl can be used interactively or be given an argument\n"; print "of a filename so that it can be used in scripts or cron jobs.\n"; print "SYNTAX:\n"; print "Interactive Mode: Simply type patcheck-$VERSION.pl and enter the full\n"; print " path to the file, when prompted, which contains the recommended\n"; print " or security patches from Sun Microsystems.\n"; print "Batch Mode: patcheck-$VERSION.pl -f \n"; } # End of usage ##### End of subroutine definitions #####