On Thu, 7 Jan 1999, Ronan Waide wrote: > On January 6, spamhaterat_private said: > > Enclosed is a script that checks if your Solaris system has the > > latest security patches applied. > Funnily enough... :) > > I've a version of a similar program sitting on one of the Solaris > boxen here for the last few months[...] Even more hilarious: W. Joseph Shamblin posted somewhere an excellent program for this about a year ago. Uses perl, many options. Uses either "patchdiag.xref or" "Solaris2.x.PatchReport". -- Tired of the pretence, Paul Brunk, Workstation Support Droid "Hungry like the wolf" #!/usr/local/bin/perl # This program is intended to parse the patchdiag.xref file downloaded from # the sunsolve ftp site. It parses the file, and returns a list of patches that # need to be evalutated for installation. # @(#) PatchReport 2.8@(#) (Shamblin) 01/24/98 21:54:04 # Copyright (c) 1997 by W. Joseph Shamblin. All rights reserved. # Permission is granted to reproduce and distribute this program # with the following restrictions: # 1) This copyright notice and the author identification below # must be left intact in the program and in any copies. # 2) Any modifications to the program must be clearly identified # in the source file. # # UNIX Systems Administrator # Department of Computer Science # Duke University, Durham, NC # Phone: 919.660.6582 # Email: wjsat_private # # # THIS SOFTWARE IS PROVIDED AS IS AND ANY EXPRESS OR IMPLIED WARRANTIES, # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY # AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. YOU ARE RESPONSIBLE # FOR ANY DAMAGE THIS MIGHT DO TO YOUR MACHINES!!! IN NO EVENT SHALL THE # AUTHOR OF THIS PROGRAM BE LIABLE FOR DAMAGE THIS PROGRAM CAUSES. # Load all needed modules use English; use MD5; use Net::FTP; use FileHandle; use Getopt::Std; autoflush STDERR 1; autoflush STDOUT 1; getopts('Aa:cdE:e:Ff:g:hiL:N:np:Q:qRrS:s:vX:Z:'); # account name, only users with contract accounts at sunsolve can get # patchdiag.xref file. The account will be "ID/passwd". !defined $opt_a ? ($account = "PUT YOUR ACCOUNT HERE") : ($account = $opt_a ); #version number $version_number = "2.8"; # pase the options $usage_message = qq| USAGE: patchreport [-A] [-a "ID/passwd"] [-cd] [-E "/path/to/excluded_patches"] [-e "103594 104117 105408 105616"] [-Ffghi] [-N "/path/to/recommended_list"] [-n] [-p "/path/to/patches"] [-Rr] [-S "/path/to/CHECKSUMS"] [-s "message"] [-X "/path/to/patchdiag.xref"] -A Prompt for account information -a SunSolve "ID/passwd" -c Prints patches which are current (UP) -d Debugging option -E "/path/to/excluded_patches" -e Exclude patch IDs (e.g. 103594 sendmail patch for sparcs) -F Force patch installation without any questions -f Arguments to fastpatch, i.e. -f nsI for -n -s -I ( see fastpatch documentation ) Defaults for fast patch are the following: -n Never call installpatch (by default, fastpatch will fall back to installpatch when it can't find package matches) -s Save old files so the patch can be backed out. (Works for new style patches) -I Ignore backoutpatch failures (instead, the system state is updated as if the patch has been backed out) -g Grace period for shutdown (in seconds) -h Prints this message and exits -i Install patches -L "/path/to/file_with_list_of_patches" -N "/path/to/recommended_list" -n No contract support (use Recommended patch list) -p "/path/to/patches" (default: /var/tmp/patches) -Q "/path/to/fastpatch" -q Use Casper Dik's fastpatch program to install patches -R Remove compressed patches and directories after installation, but not if uncompressing to a different directory ( -Z option ). In that case just clean up the uncompressed directory and leave compressed patch in place. -r Retrieve patches -S "/path/to/CHECKSUMS" -s Shutdown with "Message" -v Version number -X "/path/to/patchdiag.xref" -Z "/path/to/uncompress_patches"\n |; # If the program is called with the -h option simply print # the usage message and exit. if (defined $opt_h){ print "$usage_message\n"; exit 0; } # If the program is called with the -v option simply print # the version, and the usage message and exit. if (defined $opt_v) { print "\n\tPatchReport version $version_number\n"; exit 0; } # If we are called with the -i (install option) make sure # that we have the appropriate permissions if ((defined $opt_i) and ($> or $< != 0)) { print "\n You must be root to install patches."; print "$usage_message\n"; exit 0; } # if the account is set to the default assume that we # need to print an error message asking for an account. if ($account eq "PUT YOUR ACCOUNT HERE" and !$opt_S and !$opt_X and !$opt_n and !$opt_A ) { print qq| You must have a SunSolve account to use this script. The -n option can be used to by-pass this check, and use the Recommended patch list instead of the patchdiag.xref file. You can hard code the account and ID into the script. |; print "$usage_message\n"; exit 0; } # Let's figure out where fastpatch is located if possible. If not just exit. if (defined $opt_q ) { if (defined $opt_Q) { $INSTALL_PATCH_PROG = $opt_Q; if ( ! -e $INSTALL_PATCH_PROG ) { print qq| Fastpatch was not found in that location. Please use -Q to the specify correct path to the fastpatch program.\n\n|; exit 1; } } elsif ( !defined $opt_Q) { $INSTALL_PATCH_PROG = `which fastpatch`; if ($INSTALL_PATCH_PROG =~ /no fastpatch in/) { print qq| Fastpatch program not found. Please use -Q to specify where the fastpatch program is located or add its directory to your path.\n\n|; exit 1; } } } else { $INSTALL_PATCH_PROG = "./installpatch"; } # When summomed with the -A option the user will be asked # for the account name and password for SunSolve's FTP site if ( defined $opt_A) { print qq| Please provide the account and password in the form "ID/passwd" \n\naccount/passwd? |; chomp($account = <STDIN>); } # The -p option allows for the output of the patches downloaded # to go into another directory. This is good for large sites that # share a common patch directory. if (!defined $opt_p) { $patch_dir = "/var/tmp/patches"; } elsif (defined $opt_p) { $patch_dir = "$opt_p"; } # Setp the arguments to fast patch. By default use -n -s -I # -n Never call installpatch (by default, fastpatch will # fall back to installpatch when it can't find package # matches) # -s Save old files so the patch can be backed out. # (Works for new style patches) # This should be optional for PatchReport # # -I Ignore backoutpatch failures # (instead, the system state is updated as if the patch # has been backed out) if ( defined $opt_f and defined $opt_q ) { map { $FAST_PATCH_ARGS = $FAST_PATCH_ARGS . "-$_ " } split(//,$opt_f); } elsif (defined $opt_q and !defined $opt_f ) { $FAST_PATCH_ARGS = "-n -s -I"; } # Get some information about who we are @uname = split ' ',`uname -a`; $uname[2] =~ s/^5/2/ or die "can't convert SunOS-Solaris version number"; if ($uname[5] eq "i386") { $os = "$uname[2]_x86";} else { $os = "$uname[2]";} # Print a nice little message to let the users know # what is going on when the script first starts up print qq|\n Analyzing needed patches on your machine, this might take a minute or two depending on the options you chose, and/or your net connection.\n\n|; ################################################ ############## File Retrieval ################## ################################################ # If the -X option or the -S options are not used this means that # the patchdiag.xref file and/or the CHECKSUMS file are not stored # locally. Since this is the case we have to get the files from the net. if ((!defined $opt_X or !defined $opt_S) and !$opt_n){ $ftp = Net::FTP->new("sunsolve.sun.com", Debug => $opt_d ? 1 : 0); $ftp->login("sunsolve","sunmicro","$account") ;#or warn "can't login"; # If the -n option is used then we need to go and get the Recommended # patch list. We need to do this in regular anonymous mode. } elsif (defined $opt_n) { $ftp = Net::FTP->new("sunsolve.sun.com", Debug => $opt_d ? 1 : 0); $ftp->login() ;#or warn "can't login, bad password perhaps\?"; } # If the -X option or the option is used this means that the # patchdiag.xref file are stored locally. Since this is the # case we do not have to get the files from the net. if (defined $opt_X and !$opt_n) { $xref_fd = new FileHandle "$opt_X", "r"; } elsif (!defined $opt_X and !defined $opt_n) { # -X was not used so we need to get the file from # Sunsolve's site $xref_fd = new FileHandle "/tmp/patchdiag_$$", "w+"; $ftp->get("patchdiag.xref", $xref_fd); } elsif (defined $opt_n and !defined $opt_N) { # If -n was used, and not -N then we need to retrieve the # file from the net. $recommended_fd = new FileHandle "/tmp/Recommended_$$", "w+"; $ftp->cwd("/pub/patches"); $ftp->get("$os\_Recommended.README", $recommended_fd); } elsif (defined $opt_n and defined $opt_N) { # if -n is used with -N then that means that the file is # stored locally. Set the file handle to the argument # given to -N. This should be the path to the # Recommended list. $recommended_fd = new FileHandle "$opt_N", "r"; } if (!$opt_n) { # Make sure that we are not working in non-contract mode # if we are not, and the -S option is defined the # the path to the checksums file should be the argument # given to -S if (defined $opt_S) { $checksum_fd = new FileHandle "$opt_S", "r"; } else { $checksum_fd = new FileHandle "/tmp/CHECKSUMS_$$", "w+"; $ftp->get("CHECKSUMS", $checksum_fd); } } # play it again sam # if the -n option was not used putting us into non-contract mode # then we likely retreived the files from the net. We have to # go to the beginning to read the entire contents of the file. if (!defined $opt_n) { seek $xref_fd,0,0; seek $checksum_fd,0,0; } elsif ($opt_n) { seek $recommended_fd,0,0; } ################################################ ############ Formatting and parsing ############ ############ for the needed patches ############ ################################################ format patch_top = Patch-ID Security Recommended ID Description --------- -------- ----------- -- ------------------------ . format patch_out = @<<<<<<<< @<<<<<<< @<<<<<<<<<< @< @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< "$x_id-$x_rev", $security, $recommended, $showrev{$x_id}, $x_desc . $^="patch_top"; $~="patch_out"; # We need to get the current patches on the machine. the command showrev -p # will get the desired information. The map function will take every occurance # found in the showrev -p and preform the block operation on it. In this case # the operation is to double split the output, and then create an associative # array. map {($s_id,$s_rev) = split '-',(split)[1];$showrev{$s_id} = $s_rev} `showrev -p|sort`; # Show patches taht apply to add-on programs like Disksuite and veritas if # we are called with the -o option # If we are in contract mode then we will need to take the output of the # patchdiag.xref file. We split the file, and then test to see if we have # the patch-id from the showrev -p array. We also do a little formatting # depending on whether or not the file is recommended or a security patch # or both. We also make use of the write function, to keep things formatted # nicely. if (!defined $opt_n) { while(<$xref_fd>){ map {($x_id,$x_rev,$x_rec,$x_sec,$x_os,$x_arch,$x_desc) = (split(/\|/,$_))[0,1,3,4,7,8,10]} $_; if ((!defined $showrev{$x_id} or $showrev{$x_id} < $x_rev) and ($x_arch =~ /$uname[5]\;|$uname[5]\.$uname[4]\;|all\;$uname[5]\;|all\;/) and ($x_os eq "$os")) { if ($x_rec eq "R") { local $recommended = "Recommended ";} else {local $recommended = " N/A ";} if ($x_sec eq "S") { local $security = "Security ";} else {local $security = " N/A ";} push @needed, "$x_id-$x_rev"; $patch_description{"$x_id-$x_rev"} = "$x_desc"; write; } # if we get a hit then that means we are current. So we should # print up in the patch revision place. elsif ((defined $showrev{$x_id} or $showrev{$x_id} = $x_rev) and ($x_arch =~ /$uname[5]\;|$uname[5]\.$uname[4]\;|all\;$uname[5]\;|all\;/) and ($x_os eq "$os") and defined $opt_c) { if ($x_rec eq "R") { local $recommended = "Recommended ";} else {local $recommended = " N/A ";} if ($x_sec eq "S") { local $security = "Security ";} else {local $security = " N/A ";} $showrev{$x_id} = "UP"; write; } } # If we are using the -n non-contract mode then don't do too much. # just parse the file, and get the basics like the patch-id } elsif (defined $opt_n) { while (<$recommended_fd>) { ($x_id,$x_rev) = map{split '-',(split)[0]} grep /^\d{6}/,$_ or next; ($junk,@x_desc) = split; if (!$showrev{$x_id} or $showrev{$x_id} < $x_rev) { push @needed, "$x_id-$x_rev"; $security = ""; $recommended = ""; $x_desc = join ' ', @x_desc; $patch_description{"$x_id-$x_rev"} = $x_desc; write; } # if we get a hit then that means we are current. So we should # print up in the patch revision place. elsif ((defined $showrev{$x_id} or $showrev{$x_id} = $x_rev) and defined $opt_c) { $showrev{$x_id} = "UP"; write; } } } ################################################ ############## MD5 checksum test ############### ################################################ # Now, if we weren't run with the -n option, we parse the checksums # file making an array of the values of patch-id to the actual # MD5 checksum as calculated by Sun. We need to go into the # multiline mode so we set the record separator (RS). if (!$opt_n) { $RS=''; map {($patch_checksum_id) = /^(\d{6}-\d{2}).tar.Z/m; ($patch_checksum) = /MD5: (.*)/; if ($patch_checksum_id ne "") { $actual_checksum{$patch_checksum_id} = $patch_checksum; } } <$checksum_fd>; $RS="\n"; } format get_top = Patch-ID Checksum status Description --------- ------------------ -------------------- . format get_out = @<<<<<<<<<< @<<<<<<<<<<<<<<<<<<<< @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< $get_status, $checksum_status,$patch_description{$_} . # If we are called with the -r switch get the patches, and check the checksums # for each file we will calculate our own checksums, and compare then. If they # match then they can be installed. If not print an error message. This will # be done for all of the needed patches from, as determined from above. $md5 = new MD5; if (defined @needed and defined $opt_r) { mkdir "$patch_dir",0755; print "\n**Retrieving Patches**\n"; print "Patch-ID Checksum status Description\n--------- ------------------ --------------------\n"; $^ = "get_top"; $~ = "get_out"; map { $get_status = "$_\t"; $ftp->binary; $~ = "get_out"; $ftp->get("$_.tar.Z","$patch_dir/$_.tar.Z"); if (!defined $opt_n) { $subject = new FileHandle "$patch_dir/$_.tar.Z"; if (!defined $opt_n) { $md5->reset(); $md5->addfile($subject); $retrieved_checksum = $md5->hexdigest(); $calculated_checksum{$_} = $retrieved_checksum; ($actual_checksum{$_} eq "$retrieved_checksum") ? ($checksum_status = "checksum match") : ($checksum_status = "*CHECKSUM FAILED*"); } } write } @needed; # We also need to check the checksum if the file is stored on # a local file system, hence called without the -r option. } elsif (defined @needed and defined $opt_i and !defined $opt_r) { map { $subject = new FileHandle "$patch_dir/$_.tar.Z"; if (!defined $opt_n) { $md5->reset(); $md5->addfile($subject); $retrieved_checksum = $md5->hexdigest(); $calculated_checksum{$_} = $retrieved_checksum; ($actual_checksum{$_} eq "$retrieved_checksum") ? ($checksum_status = "checksum match") : ($checksum_status = "*CHECKSUM FAILED*"); } } @needed; } # If we do not need patches, then you are pretty up on things. # print a nice message. if (!defined @needed) { print qq| Congratulations you do not need any patches installed. Send this note to your boss, and ask for a raise!!! |; } # This avoids an error if the ftp module was never opened. if (!defined $opt_X or !defined $opt_S){ $ftp->quit; } # Clean up the files that were downloaded from the net. if (!defined $opt_n) { unlink "/tmp/patchdiag_$$", "/tmp/CHECKSUMS_$$"; } else { unlink "/tmp/Recommended_$$"; } format install_top = Patch-ID Install status Description --------- --------------- -------------------- . format install_out = @<<<<<<<<<< @<<<<<<<<<<<<<<<<<<< @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< $installing ,$patch_install_status, $patch_description{$patch_to_install} . ################################################ ############# Patch installation ############### ################################################ if (defined @needed and defined $opt_i and !defined $opt_n) { &question_patch_install_sub; } elsif (defined $opt_n and defined $opt_i) { &question_patch_install_sub; } ################################################ ############# Shutdown message ################# ################################################ if ( defined $opt_s) { print "\n\n**SHUTTING DOWN WITH MESSAGE: $opt_s\n\n"; `/usr/sbin/shutdown -y -g$opt_g -i6 "$opt_s" &`; } sub question_patch_install_sub { if (!defined $opt_F) { # Print an ominous message to let the user know this might # be a bad idea. If they still want to do it, they probably # know what they are doing print qq| ** Installing all patches without checking them first ** ** can have negative consequences. I am assuming that ** ** you know this, and think that all of these patches ** ** are a good idea. Using the -F option will turn off ** ** this message. ** \n|; if (!defined $opt_L) { # Get a confirmation or a list of patches to install print "Which patches do you want to install (all/none/list of patches) "; chomp(local $answer_install_patch = <STDIN>); if ( $answer_install_patch eq "all") { &install_patch_sub; } elsif ($answer_install_patch eq "none") { print "\n\tExiting install procedure\n"; exit 0; } elsif ($answer_install_patch =~ /^10/) { @needed = split ' ',$answer_install_patch; &install_patch_sub; } else { print "\n\tCan't determine answer, aborting installation procedure\n"; exit 0; } } elsif ( defined $opt_L) { print "Would you like to install all patches listed in $opt_L? (yes/no)\n"; chomp(local $answer_install_patch = <STDIN>); if ($answer_install_patch =~ "n") { print "\n\tExiting install procedure\n"; exit 0; } elsif ($answer_install_patch =~ "y") { &install_patch_sub; } } # If called with the -F flag then skip the formality, and just install # the patches. } elsif (defined $opt_F) { &install_patch_sub; } } sub install_patch_sub { if (defined $opt_q ) { print "\n\n**Installing patches with fastpatch**\n"; } else { print "\n**Installing Patches (this can take a while)**\n"; } if (!defined $opt_q ) { print "\nPatch-ID Install status Description\n"; print "--------- --------------- -------------------- \n"; } # for all of the patches left in the @needed array we do a regulat old # installation. Just uncompress the patch, cd into the directory and run # the installpatch program. Also check the return code of the installpatch # program. If the return code is something other than 0 then grep the error # code from the installpatch program and print it on the install status # column of the output. $^ = "install_top"; $~ = "install_out"; if (defined $opt_E) { $excluded_patches_fd = new FileHandle "$opt_E", "r"; chomp(@excluded_patches = <$excluded_patches_fd>); } if (defined $opt_e) { push @excluded_patches,split ' ',$opt_e; } if (defined $opt_L) { $needed_patches_fd = new FileHandle "$opt_L", "r"; chomp(@needed = <$needed_patches_fd>); print "\n\nInstalling patches listed in $opt_L\n\n"; } foreach $patch_to_install (@needed) { # Make sure that we do not have any white space in the patch-id from the # possible input on the command line. $patch_to_install =~ s/\s//g; $skip_this_patch = 0; if (defined $opt_e or defined $opt_E) { map { (substr($patch_to_install,0,6) eq substr($_,0,6)) ? $skip_this_patch = 1 : ""} @excluded_patches; } if (($actual_checksum{$patch_to_install} eq $calculated_checksum{$patch_to_install}) and ($patch_to_install ne "") and ($skip_this_patch != 1)) { if (defined $opt_Z) { chdir "$opt_Z"; `/usr/bin/uncompress < $patch_dir/$patch_to_install.tar.Z | /bin/tar xf -`; chdir "$opt_Z/$patch_to_install"; } else { chdir "$patch_dir"; `/usr/bin/uncompress < $patch_to_install.tar.Z | /bin/tar xf -`; chdir "$patch_dir/$patch_to_install"; } $installing = "$patch_to_install"; if (defined $opt_q and defined $opt_Z) { exec `$INSTALL_PATCH_PROG -p $opt_Z $FAST_PATCH_ARGS $patch_to_install`; } elsif (defined $opt_q and !defined $opt_Z ) { exec `$INSTALL_PATCH_PROG -p $patch_dir $FAST_PATCH_ARGS $patch_to_install`; } elsif (!defined $opt_q) { `$INSTALL_PATCH_PROG .`; } if ($? != 0 and !defined $opt_q){ $error = $?/256; $installpatch_fd = new FileHandle "./installpatch", "r"; map { $patch_install_status = "$1" if /\#\t\t$error\t(.*)/} <$installpatch_fd>; } elsif ($? != 0 and defined $opt_q) { $patch_install_status = "*NOT INSTALLED*"; } else { $patch_install_status = "Patch installed\t"; if (defined $opt_R and !defined $opt_Z) { chdir "$patch_dir"; `rm -rf $patch_to_install`; unlink "$patch_to_install.tar.Z"; } elsif (defined $opt_R and defined $opt_Z) { chdir "$opt_Z"; `rm -rf $patch_to_install`; } } } elsif ($actual_checksum{$patch_to_install} ne $calculated_checksum{$patch_to_install} and ($skip_this_patch != 1) ) { $patch_install_status = "*NOT INSTALLED*"; $installing = "$patch_to_install"; } elsif ($skip_this_patch == 1) { $patch_install_status = "*EXCLUDED PATCH*"; $installing = "$patch_to_install"; } if (defined $opt_q and $skip_this_patch != 1){ print "Fastpatch messages for $patch_to_install:\n-----------------------------------------\n\n"; write; } elsif (!defined $opt_q) { write; } } }
This archive was generated by hypermail 2b30 : Fri Apr 13 2001 - 14:28:07 PDT