#!/usr/bin/perl use strict; use warnings; use POSIX qw(strftime); use URI::Escape; use File::Copy qw( copy ); use File::Basename; use File::stat; use Digest::SHA qw(sha256_hex sha1); use MIME::Base64; use File::Spec; use CGI; use HTML::Entities; use IO::Handle; use Time::HiRes qw( usleep ualarm gettimeofday tv_interval ); my $log; my $transaction="unknown"; my $logfile = "/var/log/download.log"; my $datadir = "/var/www/server.example.com/public_html/download/"; my $secretfile="/etc/apache2/download.secret"; my $secret; my $sentheader = 0; my $maxdrift = 60; sub lprintf { if (defined($log)) { my $now = strftime "%a, %d %b %Y %T %z", localtime; print $log "$now: [$$][$transaction]: "; printf $log @_; } } sub closelog { close $log if defined($log); $log = undef; } sub openlog { closelog; open ($log, '>>', $logfile) || die ("Cannot open logfile $logfile: $!"); $log->autoflush; } sub qdie { my $err = shift @_; lprintf "ERROR: $err\n"; if (!$sentheader) { my $error = encode_entities( $err ); print "Status: 404 Not Found\n"; print "Content-type: text/html\n\n"; print "404 Not Found

Not Found

$error


Download Server
"; print "\n"; } closelog; die $err; } sub caughtsignal { my $signame = shift; qdie ("Received SIG$signame"); } sub sendfile { my $file = shift @_; my $name = basename($file); open my $fh, '<:raw', $file or qdie "Cannot open '$file': $!"; $sentheader = 1; print CGI::header( -type => 'application/octet-stream', -attachment => $name, ); binmode STDOUT, ':raw'; unless (copy $fh => \*STDOUT, 8192) { qdie "Cannot write to STDOUT"; } close $fh or qdie "Cannot close '$file': $!"; return; } sub gethash { return sha256_hex(shift @_); } sub decodeparams { my $query = CGI::url(-absolute=>1); my $clienttime = CGI::url_param('time'); my $clientid = CGI::url_param('id'); my $clienthash = CGI::url_param('hash'); my $clientfile = CGI::url_param('file'); $clientfile = "default" unless(defined($clientfile)); qdie ("Bad parameters") unless (defined($clienttime) && defined($clientid) && defined($clienthash) && ($clienttime=~/^[0-9]+$/)); my $drift = time()-$clienttime; qdie ("Client time has drifted - we have ".time()) if (($drift < -$maxdrift) || ($drift > $maxdrift)); qdie ("Bad ID") unless ($clientid=~/^[-+._\@a-zA-Z0-9]+$/); qdie ("Bad filename") unless ($clientfile=~/^[-+._a-zA-Z0-9]+$/); qdie ("Bad filename") if ($clientfile=~/^\./); my $hash = gethash($clienttime.":".$clientid.":".$clientfile.":".$secret); qdie ("Bad hash") unless ($hash eq $clienthash); my $fn = $datadir.$clientfile; $fn = File::Spec->rel2abs( readlink($fn) ) if (-l $fn); qdie ("File not found") unless ( -f $fn); $clientfile = basename ($fn); $transaction=$hash." ".$clientfile." ".$clientid; return $fn; } open (my $sfh, "<", $secretfile) || qdie("Can't open secret file $secretfile: $!"); chomp($secret=join("",<$sfh>)); close ($sfh); if (!defined($ENV{DOCUMENT_ROOT}) && !defined($ENV{SERVER_NAME})) { die ("Bad parameters") unless ($#ARGV == 1); my $t = time(); printf "id=%s&file=%s&time=%s&hash=%s",uri_escape($ARGV[0]),uri_escape($ARGV[1]),$t,gethash($t.":".$ARGV[0].":".$ARGV[1].":".$secret)."\n"; exit(0); } else { openlog; my $file = decodeparams; my $sb = stat($file); my $size = $sb->size; my $t0 = [gettimeofday]; lprintf("STARTING\n"); $SIG{INT} = \&caughtsignal; $SIG{QUIT} = \&caughtsignal; $SIG{PIPE} = \&caughtsignal; $SIG{HUP} = \&caughtsignal; $SIG{KILL} = \&caughtsignal; $SIG{TERM} = \&caughtsignal; sendfile($file); my $elapsed = tv_interval ( $t0, [gettimeofday]); lprintf("SUCCESS %d bytes %.3f MB/s\n", $size, $size/(1000000.0*(($elapsed<0.001)?0.001:$elapsed))); closelog; exit(0); }