#!/usr/bin/perl
##############################################################################################
##
## File:         fetch_esx_patches
## Description:  Pull ESX patches from vmware.com, build a conf file used by esx_patcher
## Author:       Lee Mayes   ( email leem@hp.com )
## Created:      11 Sep 2007
## Language:     perl
## Package:      LinuxCOE
##
###############################################################################################
## © Copyright 2000-2009 Hewlett-Packard Development Company, L.P
##
## 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 program;
## if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
###############################################################################################
# Crude hack, need to do this better

#  EDIT THESE BASED ON YOUR PATCH SERVER/WAYSTATION
my $destdir = "/var/opt/LinuxCOE/VMWare-updates/ESX";		# Where do downloaded patches land
my $temp_patch_file = "/tmp/vi3_patches.html";			# Scratch File
#my $http_proxy = "http://web-proxy.example.org:8088/";		# Define proxy if needed
#  END OF USER EDITS

# Look at these ESX versions and corrosponding file
my %patch_urls = (
 '3.0.0' => 'http://www.vmware.com/download/vi/vi3_patches_300.html',
 '3.0.1' => 'http://www.vmware.com/download/vi/vi3_patches_301.html',
 '3.0.2' => 'http://www.vmware.com/download/vi/vi3_patches_302.html'
);

use Digest::MD5;
use LWP::Simple;
my $debug = 0;									# Debug info on STDERR
###############
# MAIN
###############

my (%dates,%patch_db);
foreach my $esx_ver (sort(keys(%patch_urls))) {
  &fetch_patch_info($esx_ver);				# Snag a list of new patches
  my $count = keys(%patch_db);
  &parse_patch_info;					# Parse out a list of patches
  $count = keys(%patch_db);
  &fetch_patches($esx_ver,%patch_db);			# Fetch the patches if needed
  &make_plist($esx_ver);				# Make the patch list file
  open(SRC,">$destdir/$esx_ver/SOURCE.txt");
  print SRC "The patches here are referenced and explained at:\n\n";
  print SRC "$patch_urls{$esx_ver}\n";
  close(SRC);
  %patch_db = (); undef(%patch_db);
  %dates = (); undef(%dates);
}

###############
# End of MAIN
###############

sub db_dump  {
# Dump the DBs (for debugging)

  foreach my $date (sort(keys(%dates))) {
    print "\$dates{$date} = $dates{$date}\n";
    my @patches = split(' ',$dates{$date});
    foreach my $patch(@patches) {
      print "Patch ID $patch\n";
      print " URL = $patch_db{$patch}{url}\n";
      print " md5sum = $patch_db{$patch}{md5sum}\n";
      print " type = $patch_db{$patch}{type}\n";
      print " size = $patch_db{$patch}{size}\n";
      print " desc = $patch_db{$patch}{desc}\n";
    }
  }

}

sub make_plist {

# Make a list the clients can pull and parse 

  my $ver = shift;
  my $outfile = "$destdir/$ver/patches.txt";
  my $datestr = localtime(time);
  open(OUT,">$outfile") || die "cannot open $outfile for writing : $!\n";
  print OUT qq[##########################################################################
# VMware ESX patch list for versin $ver
# Created by LinuxCOE's parse_esx_patch at $datestr
# Source URL: $patch_urls{$ver}
# File used by LinuxCOE's esx_patcher for automated patching
# 
# Format is comma delimited with the following fields:
# 0 - Install Order
# 1 - Patch ID
# 2 - Patch Type (Critical/General)
# 3 - md5sum of patch file
# 4 - size
# 5 - Patch description summary
##########################################################################
];
  my $count = 0;
  foreach my $date (sort(keys(%dates))) {
    print OUT "\n# Patches released by VMWare on $date\n\n";
    my @patch_ids = split(' ',$dates{$date});
    foreach my $id (@patch_ids) {
      my $ord = sprintf("%4.4d",$count++);
      my $type = $patch_db{$id}{type};
      my $size = $patch_db{$id}{size};
      my $md5sum = $patch_db{$id}{md5sum};
      my $desc = $patch_db{$id}{desc};
      print OUT "$ord,$id,$type,$md5sum,$size,$desc\n";
    }
  }
  if ( -r "$destdir/$ver/bundles.txt" ) {
    print OUT "\n\n# Patch Bundles\n\n";
    open(IN,"$destdir/$ver/bundles.txt");
    while(<IN>) {
      next if /^#/;
      print OUT;
    }
  }
  close(OUT);
  close(IN);

}

sub check_sum {

# Check sum against expected
  my ($file,$sum) = @_;
  my $ctx = Digest::MD5->new;
  open(MD5FILE,$patch_file);
  $ctx->addfile(*MD5FILE);
  my $digest = $ctx->hexdigest;
  close(MD5FILE);
  print "$file has sum of \n>$digest< I expected \n>$sum<\n" if $debug;
  if ( $digest eq $sum ) { return 1 }
  return 0;

}

sub fetch_patches {

# Fetch the patches if a) we don't have it or b) md5sum is bogus

  my ($ver,%patch_db) = @_;
  foreach my $patch_id (sort(keys(%patch_db))) {
    print "Checking out patch_id $patch_id\n" if $debug;
    my $patch_url = $patch_db{$patch_id}{url};
    next unless ($patch_url);
    $patch_file = "$destdir/$ver/${patch_id}.tgz";
    if ( -f "$patch_file" ) { 
      if ( &check_sum($patch_file,$patch_db{$patch_id}{md5sum}) ) {
        print "Already got $patch_file, skipping!\n" if $debug;
        next;
      }
    }
    print "Fetching $patch_url -> $patch_file - $patch_db{$patch_id}{size}\n" if $debug;
    my $rc = getstore($patch_url,$patch_file);
    print "Snagging $patch_url -> $patch_file, LWP::Simple returned $rc\n" if $debug;
    if ( $rc ne '200' ) {
      print "Failed to fetch $patch_url\nExiting...\n";
      exit 1;
    }
    unless ( &check_sum($patch_file,$patch_db{$patch_id}{md5sum}) ) {
      print "Patch $patch_id fails checksum, Outta here before I do more damage!\n";
      exit 1;
    }
  }

}

sub parse_patch_info {

  %patch_db = ();
  %dates = ();
  open(IN,"$temp_patch_file") || die "Cannot open just downloaded $temp_patch_file, too odd!";
  my $date;
  my %mons = ( 'Jan' => 1, 'Feb' => 2, 'Mar' => 3, 'Apr' => 4, 'May' => 5, 'Jun' => 6,
               'Jul' => 7, 'Aug' => 8, 'Sep' => 9, 'Oct' => 10, 'Nov' => 11, 'Dec' => 12 );
  while(my $buf = <IN>) {
    chomp;
    if ( $buf =~ /<h2>/ ) {				# It's a date header
      print "Found date header of $buf" if $debug;
# <h2>Patches for July 9, 2007</h2>
      $buf =~ s/<\/h2>//;
      my ($a,$b,$mon,$day,$year) = split(' ',$buf);
      chop($day);		# drop the comma
      if ( length($mon) > 3 ) {
        my @lets = split('',$mon);
        $mon = "$lets[0]"."$lets[1]"."$lets[2]";
      }
      $date = sprintf("%4.4d-%2.2d-%2.2d",$year,$mons{$mon},$day);
      print " - condensed to $date\n" if $debug;

    }
    if ( $buf =~ /[0-9].tgz/ ) {   # It's a patch entry
      print "Found patch $buf" if $debug;
#     <a href="http://download3.vmware.com/software/vi/ESX-9287937.tgz"><img src="vi3_patches_301_files/button_new_download.gif" align="absmiddle" border="0"></a><br>md5sum: 5a7995dd8d3bbe42fc836f51b03ef095</td>
      my ($c,$patch_url,@rest) = split('"',$buf);

      my @data = split('/',$patch_url);
      my $patch_name = pop(@data);
      $patch_name =~ s/.tgz$//;

      my ($d,$e,$md5sum) = split(' ',pop(@rest));
      $md5sum = &detag($md5sum);
      $md5sum =~ tr/0-9a-f//cd;
#     <td>7.9 MB</td>
      my $size = &detag($buf = <IN>);
#     <td>LSI MPT SAS Driver update</td>
      my $desc = &detag($buf = <IN>);
#     <td>VM Shutdown &amp; Host Reboot</td>
      $buf = <IN>;  # Slurp out implace
#     <td>ESX-7302867</td>
      $buf = <IN>;  # Slurp out dep
#     <td><span class="rd">Critical</span></td>
      my $patch_type = &detag($buf = <IN>);
      $patch_type = 'Critical' if ($patch_type =~ /Crit/);
      print "Pushing $patch_name onto \$dates{$date}\n" if $debug;
      $dates{$date} .= "$patch_name ";   		# Add an entry for the patch on this date
      $patch_db{$patch_name}{url}=$patch_url; 		# %Populate the DB
      $patch_db{$patch_name}{size}=$size;
      $patch_db{$patch_name}{desc}=$desc;
      $patch_db{$patch_name}{type}=$patch_type;
      $patch_db{$patch_name}{md5sum}=$md5sum;
    } 
  }
   
}

sub detag {

# Rip HTML off the entry
  my $data = shift(@_);
  $data =~ s/<\/td>//;
  $data =~ s/<td>//;
  $data =~ s/^\ *//;  # Chop leading spaces
  $data =~ s/\ *$//;  # Chop trailing spaces
  $data =~ s/
//;	# drop DOS stuff
  chomp($data);
  return($data);
 
}

sub fetch_patch_info {

  my $ver = shift(@_);
  my $patch_url = $patch_urls{$ver};
# Out with the old
  if ( -f "$temp_patch_file" ) { unlink "$temp_patch_file" }
  if ( -f "$temp_patch_file" ) { die "Cannot unlink $temp_patch_file, check perms!\n" }
  $ENV{'http_proxy'} = $http_proxy if ($http_proxy); 
  print "Fetching $patch_url -> $temp_patch_file for ESX $ver patches\n" if $debug;
  my $rc = getstore($patch_url,$temp_patch_file);
  print "Snagging $patch_url -> $temp_patch_file, LWP::Simple returned $rc\n" if $debug;
  if ( $rc ne '200' ) {
    print "Failed to fetch $patch_url\nExiting...\n";
    exit 1;
  }

}
