#!/usr/bin/perl
use File::stat;
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 File::Spec::Functions qw(rel2abs);
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 = dirname(rel2abs($0))."/";
my $secretfile="/etc/apache2/download.secret";
my $secret;
my $sentheader = 0;
my $maxdrift = 60;
my $remoteip;
my $t0 = [gettimeofday];
my $filesize;
sub lprintf
{
if (defined($log))
{
my $now = strftime "%a, %d %b %Y %T %z", localtime;
printf $log "$now: [$$:%s][$transaction]: ", defined($remoteip)?$remoteip:"unknown";
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 @_;
my $elapsed = tv_interval ( $t0, [gettimeofday]);
lprintf "ERROR: $err after %.3f secs\n", $elapsed;
if (!$sentheader)
{
my $error = encode_entities( $err );
print "Status: 404 Not Found\n";
print "Content-type: text/html\n\n";
print "
404 Not FoundNot 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',
-Content_length=> $filesize,
-attachment => $name,
);
binmode STDOUT, ':raw';
unless (copy $fh => \*STDOUT, 65536)
{
qdie "Cannot write to STDOUT";
}
close $fh
or qdie "Cannot close '$file': $!";
return;
}
sub gethash
{
return sha256_hex(shift @_);
}
sub getfile
{
my $fn = $datadir.(shift @_);
$fn = File::Spec->rel2abs( readlink($fn) ) if (-l $fn);
qdie ("File not found") unless ( -f $fn);
return $fn;
}
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 $now = time();
my $drift = $now-$clienttime;
qdie ("Client time has drifted - $now - $clienttime = $drift") 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 = getfile($clientfile);
$clientfile = basename ($fn);
$transaction=$hash." ".$clientfile." ".$clientid;
return $fn;
}
sub doinfo
{
my $clientfile = shift @_;
my $fn = getfile($clientfile);
$clientfile = basename ($fn);
my $size = "unknown";
my $sb = stat($fn);
$size = $sb->size if (defined($sb) && defined($sb->size));
my $md5sum = "unknown";
my $md5fn = $fn.".md5sum";
if ( -r $md5fn )
{
my $md5;
open $md5, "<", $md5fn || qdie ("Can't read md5sum");
while (<$md5>)
{
chomp;
$md5sum = $1 if (/^([a-f0-9]+)\b/);
}
close $md5;
}
$sentheader = 1;
print CGI::header(
-type => 'text/plain' );
print "$clientfile $size $md5sum\n";
}
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
{
my $info = CGI::url_param('info');
if (defined($info))
{
doinfo($info);
exit 0;
}
$remoteip = CGI::remote_addr();
openlog;
my $file = decodeparams;
my $sb = stat($file);
$filesize = $sb->size;
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]);
my $rate = $filesize/(1000000.0*(($elapsed<0.001)?0.001:$elapsed));
lprintf("SUCCESS %d bytes %.3f MB/s %.3f secs\n", $filesize, $rate, $elapsed);
closelog;
exit(0);
}