#!/usr/bin/perl -wT # # Copyright (c) 2000-2016 University of Utah and the Flux Group. # # {{{EMULAB-LICENSE # # This file is part of the Emulab network testbed software. # # This file is free software: you can redistribute it and/or modify it # under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or (at # your option) any later version. # # This file 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 Affero General Public # License for more details. # # You should have received a copy of the GNU Affero General Public License # along with this file. If not, see . # # }}} # use strict; use English; use Getopt::Std; use POSIX qw(mktime strftime); # # Spew a tar/rpm file to stdout. # # The script is setuid and run from the webserver. # sub usage() { print STDERR "Usage: spewimage [-h] [-t timestamp] [-e] -k access_key \n". "Spew an image file to a (widearea) node.\n"; exit(-1); } my $optlist = "t:k:hsde"; my $debug = 0; my $headonly = 0; my $sigfile = 0; my $delta = 0; my $access_key; my $timestamp; # GM Time. # # Exit codes are important; they tell the web page what has happened so # it can say something useful to the user. Fatal errors are mostly done # with die(), but expected errors use this routine. At some point we will # use the DB to communicate the actual error. # # $status < 0 - Fatal error. Something went wrong we did not expect. # $status = 0 - Proceeding. # $status > 0 - Expected error. No such file, not modified, etc. # 1. File could not be verified. # 2. File has not changed since timestamp. # 3. File could not be opened for reading. # # Configure variables # my $TB = "@prefix@"; my $TBOPS = "@TBOPSEMAIL@"; my $TBLOGS = "@TBLOGSEMAIL@"; # # Load the Testbed support stuff. # use lib "@prefix@/lib"; use libdb; use libtestbed; use OSImage; # Locals my $bytelen = 0; # Protos sub SpewImage(); sub VerifyImage(); sub fatal($); # un-taint path $ENV{'PATH'} = '/bin:/usr/bin:/usr/local/bin'; delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'}; # Turn off line buffering on output $| = 1; # # Parse command arguments. Once we return from getopts, all that should be # left are the required arguments. # my %options = (); if (! getopts($optlist, \%options)) { usage(); } if (defined($options{"h"})) { $headonly = 1; } if (defined($options{"d"})) { $debug = 1; } if (defined($options{"e"})) { $delta = 1; } if (defined($options{"s"})) { $sigfile = 1; } if (defined($options{"k"})) { $access_key = $options{"k"}; if ($access_key =~ /^([\w]+)$/) { $access_key = $1; } else { die("*** Bad data in access_key: $access_key\n"); } } if (defined($options{"t"})) { $timestamp = $options{"t"}; if ($timestamp =~ /^([\d]+)$/) { $timestamp = $1; } else { die("*** Bad data in timestamp: $timestamp\n"); } } if (@ARGV != 1 || !defined($access_key)) { usage(); } my $imageid = $ARGV[0]; my $image = OSImage->Lookup($ARGV[0]); if (!defined($image)) { die("*** $0:\n". " $imageid does not exist!\n"); } if (my $retval = VerifyImage()) { exit($retval); } exit(3) if (SpewImage() != 0); exit(0); # # Spew out a file. # sub SpewImage() { my $file = ($delta ? ($sigfile ? $image->DeltaImageSigFile() : $image->DeltaImageFile()) : ($sigfile ? $image->FullImageSigFile() : $image->FullImageFile())); open(FD, "< $file") or fatal("Could not open $file!\n"); # # Deal with NFS read failures # my $foffset = 0; my $retries = 5; my $buf; while ($bytelen) { my $rlen = sysread(FD, $buf, 8192); if (! defined($rlen)) { # # Retry a few times on error to avoid the # changing-exports-file server problem. # if ($retries > 0 && sysseek(FD, $foffset, 0)) { $retries--; sleep(1); next; } fatal("Error reading $file: $!"); } if ($rlen == 0) { last; } if (! syswrite(STDOUT, $buf, $rlen)) { fatal("Error writing file to stdout: $!"); } $foffset += $rlen; $bytelen -= $rlen; $retries = 5; } if ($bytelen) { fatal("Did not get the entire file! $bytelen bytes left."); } close(FD); return 0; } # # Verify that we can return this file, return error if not allowed. # Otherwise return 0 for okay. # sub VerifyImage() { # # Some images are not allowed to be exported. # if ($image->noexport()) { if ($debug) { print STDERR "Not allowed to export this image!\n"; } return 1; } # # The current user needs to be able to read the image file and must # have provided proper access key. # my $file = ($delta ? ($sigfile ? $image->DeltaImageSigFile() : $image->DeltaImageFile()) : ($sigfile ? $image->FullImageSigFile() : $image->FullImageFile())); if (! -r $file) { if ($debug) { print STDERR "Cannot read $file\n"; } return 1; } if (!defined($image->access_key())) { if ($debug) { print STDERR "$image does not have an access key set!\n"; } return 1; } if ($access_key ne $image->access_key()) { if ($debug) { print STDERR "Invalid access key!\n"; } return 1; } # # Stat the file to get the bytelen for spewing. # my (undef,undef,undef,undef,undef,undef,undef,$length, undef,$mtime) = stat($file); if ($headonly) { print "Content-Length: $length\n"; print "Last-Modified: " . strftime("%a, %d %b %Y %H:%M:%S GMT", gmtime($mtime)) . "\n"; exit(0); } # # Check timestamp if supplied. Remember, we get GM timestamps, so # must convert the local stamp. # if (defined($timestamp)) { $mtime = mktime(gmtime($mtime)); return 2 if ($timestamp >= $mtime); } $bytelen = $length; return 0; } sub fatal($) { my ($msg) = @_; SENDMAIL($TBOPS, "spewimage:$imageid", $msg); die("*** $0:\n". " $msg\n"); }