#!/usr/bin/perl -w # dlkern v41, created by Darxus@ChaosReigns.com. # # 100% PURE FEATURE BLOAT # # Queries ftp.kernel.org for current Linux kernel versions, via finger, # and then downloads the requested versions (stable/beta/pre-patch) as # specified by commandline flags, from ftp.kernel.org using ncftpget or # wget, and will automatically verify signitures if GnuPG.pm # (http://indev.insu.com/GnuPG/gnupg.html & http://www.gnupg.org/) is # present. # # Execute "dlkern -h" for usage. # # Example: # # dlkern -s -c us # # will download the current stable (-s) version from the United States (-c us) # mirror. It is strongly recommended that you use -c to specify your 2 letter # country code, from the list at http://www.kernel.org/mirrors/ but it is not # manditory. # # This program is is released under the GNU GPL # (http://www.gnu.org/copyleft/gpl.html) # # Requirements: # * Perl # # Requirements for signiture verification: # * GnuPG.pm # * gpg # * The kernel archive public key must be added to your keyring # # To add the Kernel Archive public key to your keyring, execute: # wget http://www.kernel.org/signature.html && gpg --import signature.html # # Net::Finger may be obtained from: # Debian package: libnet-finger-perl # RedHat package: perl-Net-Finger # Others: http://search.cpan.org/search?dist=Net-Finger # # TODO: # * Verbose signiture output. # * Allow specification of mirror to download from. # * Use netselect to select mirror. # * Config file to store parameters. # * handle case where GnuPG.pm is present, but gpm is not in the path. # * Detect presence of downloaders, set default accordingly. # * If no downloader found, use Net::FTP # * Make more cron friendly ? # * Use uname & if_exist output_file to see if it actually needs to be downloaded. # * Debian package. # * RedHat package. # * use strict. # # For mct to do: # * Hostname & filename expanders for -d # * Handle directory names being passed to -d (old versions used -d # for output directory, which is now -o) # # v15 Jan 9 20:28 EST 1st release. # v16 Jan 20 01:09 EST Changed finger address from "@linux.kernel.org" (not # working) to "@ftp.kernel.org". # v17 Jan 21 14:43 EST Code cleanup & documentation. # v18 Jan 21 15:19 EST wget support. # v19 Jan 21 15:29 EST Handle prepatch version "none". # v20 Jan 21 15:59 EST Handle failed finger. # v21 Jan 21 16:18 EST Output directory argument. # v22 Jan 21 16:47 EST Added support for The Linux Kernel Archive Mirror System. # v23 Jan 21 16:54 EST Allow specification of filetype (.gz/.bz2). # v23 Jan 21 17:00 EST -h (usage) flag. # v24 Jan 21 17:18 EST Fixed a couple minor warnings. # v25 Jan 21 18:34 EST Automatically download signitures (inebrieated). # v26 Jun 13 16:32 EDT Removed beginnings of signiture verification so # signiture download could work cleanly. # v27 Jun 14 18:02 EDT Automatic signiture verification. # v28 Jun 14 18:21 EDT Cleanup. # v29 Jun 17 14:17 EDT Compensate for pre-patch location change. # v30 Jun 17 14:54 EDT Clarified signiture verification failure output. # v31 Jun 17 15:29 EDT Unbroke pre-patch signiture verification. # v32 Jun 18 21:43 EDT Added -- option to specify arguments to downloader w/ # help from michael@toren.org. # v33 Jun 18 22:48 EDT Allow specification of any downloader via -d (Suggested by # mct). Changed output directory flag to -o. Clearer # output when proper flags are not specified for downloaders # & versions. # v34 Jun 18 23:25 EDT Put back default downloader (ncftpget) removed in v33. # v35 Jun 19 09:19 EDT use strict. Aren't you all proud ? # v36 Jun 19 09:25 EDT Default to downloading stable, if none specified. # v37 Jun 19 09:59 EDT -u: only display URLs. Stopped displaying args. # v38 Jun 19 10:16 EDT -h displays URLs. # v39 Jun 19 13:59 EDT Nolonger dependant on Net::Finger. # v40 Jun 19 14:38 EDT Output error if call to downloader fails. Added -w. # v41 Jun 20 17:08 EDT Handle absence of downloaders more gracefully. # # I decided I don't like standard version numbers. The version numbers for this # program started at "1", and have been incremented every time I made a change. # Numbers before 15 were not fit for public consumption. use strict; use vars qw(@new $i @dl_args $usage $opt_s $opt_b $opt_p %dl $opt_c $country $opt_d $downloader $opt_e $ext $opt_h $opt_o $dir $gnupg $gpg %trust @trees $tree_count $tree $dl_count $opt_w $opt_n @lines $line $desc $ver %version $first $second %url %file $date $sig $opt_u $host $port $handle $opt_n); # Load the Net::Finger module so I don't have to do any work to get the version # info via finger. #use Net::Finger; # Nolonger dependant on Net::Finger. use IO::Socket; # Create a list of version types (is "trees" an appropriate name?) @trees = ("stable", "beta", "prepatch"); ########################################## #Grab downloader args, mostly by mct BEGIN while ($i = shift @ARGV) { if ($i eq "--") { @dl_args = @ARGV; last; } push @new, $i; } @ARGV = @new; # For verbose output: #print "dlkern args:\n", join("\n", @ARGV), "\n"; #print "downloader args:\n", join("\n", @dl_args), "\n"; #Grab downloader args END ######################### ########################################### #Handle arguments (not to downloader) BEGIN # Load getopts to read commandline options. require 'getopts.pl'; # Read commandline options. Colon goes after flags that take arguments. &Getopts('usbpwnhd:c:e:o:'); sub usage { ################################################################################ print " Usage: dlkern [-sbp] [-nw | -d ] [-o ] [-c ] [-e ] -- -s Download stable version of Linux kernel. -b Download beta version of Linux kernel. -p Download prepatch version of Linux kernel. -n Use ncftpget as downloader (default). -w Use wget as downloader. -d Specify another downloader. -o Specify output directory. -c Specify 2 letter country code from http://www.kernel.org/mirrors/ -e Specify extension (gz is default, bz2 is more compressed). -h Show this usage information. -u Only display URLs. May use -c & -e. -- Specify aruments to the downloader. Must be last. " unless ($usage); $usage=1; } # If there are no commandline options, display usage. Normally you'd exit after # displaying usage, but I keep going to display the URLs. # Nolonger using this as of v36, defaulting to stable. #unless ($opt_s or $opt_b or $opt_p) #{ # &usage; #} # Check kernel version flags, store info in the #dl hash, with the version # type as the key. # opt_e & opt_c must come before opt_u & opt_h. if ($opt_e) { $ext = $opt_e; } else { $ext = "gz"; } if ($opt_c) { $country = ".$opt_c"; } else { $country = ""; } if ($opt_u) { &get_urls; exit(1); } if ($opt_h) { &usage; &get_urls; exit; } if ($opt_b) { $dl{"beta"}=1; } if ($opt_d) { $downloader = $opt_d; } if ($opt_o) { $dir = $opt_o; } if ($opt_p) { $dl{"prepatch"}=1; } if ($opt_s) { $dl{"stable"}=1; } #Handle arguments (not to downloader) END ######################################### #################### #Load GnuPG.pm BEGIN if (!eval "require 'GnuPG.pm';") { print "No GnuPG.pm (http://indev.insu.com/GnuPG), cannot verify signitures.\n"; $gnupg=0; } else { print "GnuPG.pm loaded.\n"; $gnupg=1; $gpg = new GnuPG(); %trust = (-1, "UNDEFINED", 0, "NEVER", 1, "MARGINAL", 2, "FULLY", 3, "ULTIMATE"); } #Load GnuPG.pm END ################## # Use info from the commandline to list the versions that were requested. $tree_count = 0; print "Requested:"; foreach $tree (@trees) { if ($dl{$tree}) { print " $tree"; $tree_count++; } } unless ($tree_count) { print " None. Downloading stable.\n"; $dl{"stable"}=1; } else { print ".\n"; } # Check downloader flags. $dl_count = defined($opt_w) + defined($opt_n) + defined($downloader); if ($dl_count > 1) { &usage; print "You specified $dl_count downloaders. Only specify 1.\n"; } elsif ($downloader) { # This space left intentionally blank. I want an elsunless :) } elsif ($opt_w) { $downloader="wget"; } elsif ($opt_n) { $downloader="ncftpget"; } unless (&chkexe($downloader)) { print "$downloader does not appear to be installed.\n"; $downloader = ""; } unless ($downloader) { if (&chkexe("ncftpget")) { $downloader="ncftpget"; } elsif (&chkexe("wget")) { $downloader="wget"; } else { print "No downloader was found. Please install ncftpget or wget, or specify \nanother downloader via -d.\n\n"; &get_urls; exit(1); } } print "Downloader: $downloader.\n\n"; # Parse finger data into URLs. Store them in the hash #url, using the tree # name for the key. &get_urls; sub get_urls { print "Retrieving version numbers:\n"; # Query finger data. #@lines = finger('@ftp.kernel.org', 1) or die "Failed to obtain version info by fingering \@ftp.kernel.org.\n"; $host = "ftp.kernel.org"; $port = 79; unless ($handle = IO::Socket::INET->new(Proto => 'tcp', PeerAddr => $host, PeerPort => $port)) { print "Failed to make finger connection to $host:$port.\n"; } print $handle "\n\r"; while (<$handle>) { s/(\n|\r)+/\n/g; #Net::Finger seemed to think this should be here. push @lines, $_; } #print "@lines\n"; foreach $line (@lines) { #Split each line into the description & version, using a ":" as a delimiter. ($desc, $ver) = split /:/, $line; if (defined($ver)) { #Strip spaces out of $ver. $ver =~ tr/" \n"//d; foreach $tree (@trees) { if ($desc =~ $tree) { $version{$tree} = $ver; print "$tree: $version{$tree} "; if ($tree eq "stable" or $tree eq "beta") { # For kernel version 2.3.40, set $first equal to "2", and $second equal # to "3", so this thing still works when 2.4.x or 3.x.x happens, as long # as their path naming conventions stay the same. ($first, $second) = split /\./, $version{$tree}; $url{$tree} = "ftp://ftp$country.kernel.org/pub/linux/kernel/v$first.$second/linux-$version{$tree}.tar.$ext"; $file{$tree} = "linux-$version{$tree}.tar.$ext"; } elsif ($ver eq "none") { $url{$tree} = ""; } else { (undef,$date) = split(/\//, $version{$tree}); $url{$tree} = "ftp://ftp$country.kernel.org/pub/linux/kernel/testing/pre$date.$ext"; $file{$tree} = "pre$date.$ext"; } print "$url{$tree}\n"; } } } } } if ($dir) { print "Changing to output directory: $dir\n"; chdir ($dir) or die "Can't cd to $dir $!\n"; } # Call ncftpget for each requested tree. foreach $tree (@trees) { if ($dl{$tree}) { if ($url{$tree}) { print "\nDownloading $tree by calling \'$downloader \"",join('" "',@dl_args),"\" $url{$tree}\'\n"; system ($downloader, @dl_args, "$url{$tree}") == 0 or print "Download failed: ",$? >> 8,"\n"; print "Downloading $tree signiture by calling \'$downloader \"",join('" "',@dl_args),"\" $url{$tree}.sign\'\n"; system ($downloader, @dl_args, "$url{$tree}.sign") == 0 or print "Download failed: ",$? >> 8,"\n"; #linux-2.2.14.tar.gz.sign #$sig = $gpg->verify( signature => "linux-$version{$tree}.tar.$ext.sign", file => "linux-$version{$tree}.tar.$ext" ); if ($gnupg) { print "Verifying $tree tarball $file{$tree} with signiture $file{$tree}.sign:\n"; if (eval { $sig = $gpg->verify( signature => "$file{$tree}.sign", file => "$file{$tree}" );}) { print "Signiture passed verification.\n"; # For verbose output: #print "Signiture info:\n"; #for $key (sort(keys(%$sig))) #{ # print "$key: $$sig{$key}\n"; #} print "Signiture timestamp: ",scalar(localtime($$sig{"timestamp"})),"\n"; print "Trust level for this signiture is: $trust{$$sig{trust}}\n"; } else { print "SIGNITURE DID NOT PASS VERIFICATION. Possible reasons:\n"; print " * Kernel source has been maliciously modified.\n"; print " * Kernel tarball or signiture was corrupted during download.\n"; print " * You did not insert the kernel archive public key into your keyring via:\n"; print " wget http://www.kernel.org/signature.html && gpg --import signature.html\n"; } } } else { print "There is currently no $tree version.\n"; } } } sub chkexe { return (system ("which $_[0] > /dev/null") == 0); }