+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use POSIX qw(strftime);
+use URI::Escape;
+use File::Copy qw( copy );
+use File::Basename;
+use Digest::SHA qw(sha256_hex sha1);
+use MIME::Base64;
+use File::Spec;
+use CGI;
+use HTML::Entities;
+use IO::Handle;
+
+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 "<html><head><title>404 Not Found</title></head><body><h1>Not Found</h1><p>$error</p><hr><address>Download Server</address>";
+ print "</body></html>\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;
+ lprintf("STARTING\n");
+ $SIG{INT} = \&caughtsignal;
+ $SIG{QUIT} = \&caughtsignal;
+ $SIG{PIPE} = \&caughtsignal;
+ $SIG{HUP} = \&caughtsignal;
+ $SIG{KILL} = \&caughtsignal;
+ $SIG{TERM} = \&caughtsignal;
+ sendfile($file);
+ lprintf("SUCCESS\n");
+ closelog;
+
+ exit(0);
+}