3 # F*EX CGI for download
 
   5 # Author: Ulli Horlacher <framstag@rus.uni-stuttgart.de>
 
   8 BEGIN { ($ENV{PERLINIT}||'') =~ /(.+)/s and eval $1 }
 
  10 use Fcntl               qw':flock :seek';
 
  17 ($FEXLIB) = $ENV{FEXLIB} =~ /(.+)/;
 
  18 die "$0: no $FEXLIB\n" unless -d $FEXLIB;
 
  20 our $error = 'F*EX download ERROR';
 
  21 our $head = "$ENV{SERVER_NAME} F*EX download";
 
  23 our ($spooldir,$tmpdir,@logdir,$skeydir,$dkeydir,$durl);
 
  24 our ($bs,$fop_auth,$timeout,$keep_default,$nowarning);
 
  25 our ($limited_download,$admin,$akey,$adlm,$amdl);
 
  26 our (@file_link_dirs);
 
  28 # load common code, local config : $HOME/lib/fex.ph
 
  29 require "$FEXLIB/fex.pp" or die "$0: cannot load $FEXLIB/fex.pp - $!\n";
 
  31 my $ra = $ENV{REMOTE_ADDR}||0;
 
  32 if (@download_hosts and not ipin($ra,@download_hosts)) {
 
  34     "Downloads from your host ($ra) are not allowed.",
 
  35     "Contact $ENV{SERVER_ADMIN} for details."
 
  41 # call localized fop if available
 
  42 if ($0 !~ m{/locale/.*/fop} and my $lang = $ENV{HTTP_ACCEPT_LANGUAGE}) {
 
  43   if ($lang =~ /^de/ and $0 =~ m{(.*)/cgi-bin/fop}) {
 
  44     my $fop = "$1/locale/deutsch/cgi-bin/fop";
 
  51 chdir $spooldir or die "$spooldir - $!\n";
 
  53 my $http_client = $ENV{HTTP_USER_AGENT} || '';
 
  55 $file = $ENV{PATH_INFO} || '';
 
  56 http_die('no file name') unless $file;
 
  57 $file =~ s:%3F:/?/:g; # escape '?' for URL-decoding
 
  58 $file =~ s/%([\dA-F]{2})/unpack("a",pack("H2",$1))/ge;
 
  59 $file =~ s:/\?/:%3F:g; # deescape '?'
 
  60 $file =~ s:/\.\.:/__:g;
 
  62 $file = untaint($file);
 
  64 # secure mode with HTTP authorization?
 
  67   if ($ENV{HTTP_AUTHORIZATION} and $ENV{HTTP_AUTHORIZATION} =~ /Basic\s+(.+)/) {
 
  68     @http_auth = split(':',decode_b64($1));
 
  70   if (@http_auth != 2) {
 
  73   &check_auth($file,@http_auth);
 
  76 # download-URL-scheme /$dkey/$file ?
 
  77 if ($file =~ m:^([^/]+)/[^/]+$:) {
 
  79   if ($link = readlink("$dkeydir/$dkey")) {
 
  80     if ($link !~ s:^\.\./::) {
 
  81       http_die("internal error on dkey for $link");
 
  83     $file = untaint($link);
 
  85     http_die("no such file $file");
 
  88   # download-URL-scheme /$to/$from/$file
 
  91   if ($ENV{REQUEST_METHOD} eq 'GET' and $file =~ m:.+/(.+)/.+:) {
 
  93     if (-s "$from/\@ALLOWED_RECIPIENTS") {
 
  94       http_die("$from is a restricted user");
 
  98   # add mail-domain to addresses if necessary
 
  99   if ($mdomain and $file =~ s:(.+)/(.+)/(.+):$3:) {
 
 102     $to   .= '@'.$hostname if $to   eq 'anonymous';
 
 103     $from .= '@'.$hostname if $from eq 'anonymous';
 
 104     $to   .= '@'.$mdomain if -d "$to\@$mdomain";
 
 105     $from .= '@'.$mdomain if -d "$from\@$mdomain";
 
 106     if ($ENV{REQUEST_METHOD} eq 'GET' and -s "$from/\@ALLOWED_RECIPIENTS") {
 
 107       http_die("$from is a restricted user");
 
 109     $file = "$to/$from/$file";
 
 113 if ($file and $file =~ m:(.+)/(.+)/.+:) {
 
 117   if ($from =~ s/^(anonymous).*/$1/) {
 
 118     if (@anonymous_upload and ipin($ra,@anonymous_upload) or $dkey) {
 
 121       http_header('403 Forbidden');
 
 122       print html_header($head),
 
 123         "You have no permission to request the URI $ENV{REQUEST_URI}\n",
 
 129   http_die("unknown query format");
 
 132 $data = "$file/data";
 
 134 # open $file,$file; print Digest::MD5->new->addfile($file)->hexdigest;
 
 136 # request with ?query-parameter ?
 
 137 if ($qs = $ENV{QUERY_STRING}) {
 
 139   http_die("\"$1\" is not allowed in URL") if $qs =~ /([<>\%\'\"])/;
 
 141   # workaround for broken F*IX
 
 142   $qs =~ s/&ID=skey:\w+//;
 
 145   if ($qs =~ s/&*SKEY=([\w:]+)//i) {
 
 148     if ($skey =~ s/^MD5H:(.+)/$1/) {
 
 150       foreach my $s (glob "$skeydir/*") {
 
 152         if ($skey eq md5_hex($s.$ENV{SID})) {
 
 158     if (open $skey,'<',"$skeydir/$skey") {
 
 161         $from = lc($1) if /^from=(.+)/;
 
 162         $to   = lc($1) if /^to=(.+)/;
 
 166         $file =~ s:.*/:$to/$from/:;
 
 168         http_die("INTERNAL ERROR: missing data in $skeydir/$skey");
 
 171       debuglog("SKEY=$skey");
 
 172       http_die("wrong SKEY authentification");
 
 176   # group member with gkey?
 
 177   if ($qs =~ s/&*GKEY=([\w:]+)//i) {
 
 180     if ($gkey =~ s/^MD5H:(.+)/$1/) {
 
 182       foreach my $g (glob "$gkeydir/*") {
 
 184         if ($gkey eq md5_hex($g.$ENV{SID})) {
 
 190     if (open $gkey,'<',"$gkeydir/$gkey") {
 
 193         $from  = lc($1) if /^from=(.+)/;
 
 194         $group = lc($1) if /^to=\@(.+)/;
 
 197       if ($from and $group and open $group,'<',"$from/\@GROUP/$group") {
 
 203             $file =~ s:.*/:$to/$from/:;
 
 209         http_die("INTERNAL ERROR: missing data in $gkeydir/$gkey");
 
 212       debuglog("GKEY=$gkey");
 
 213       http_die("wrong GKEY authentification");
 
 217   # check for ID in query
 
 218   elsif ($qs =~ s/\&*\bID=([^&]+)//i) {
 
 222     if ($id eq 'PUBLIC') {
 
 223       http_header('403 Forbidden');
 
 227     if ($file =~ m:^(.+)/(.+)/(.+):) {
 
 233       if ($mdomain and $from ne 'anonymous') {
 
 234         $to   .= '@'.$mdomain if $to   !~ /@/;
 
 235         $from .= '@'.$mdomain if $from !~ /@/;
 
 240       http_die("unknown file query format");
 
 243     # public or anonymous recipient? (needs no auth-ID for sender)
 
 244     if ($anonymous or $id eq 'PUBLIC' and
 
 245         @public_recipients and grep /^\Q$to\E$/i,@public_recipients) {
 
 248       open my $idf,'<',"$from/@" or http_die("unknown user $from");
 
 249       $rid = getline($idf);
 
 251       $rid = sidhash($rid,$id);
 
 254     unless ($id eq $rid) {
 
 255       debuglog("real id=$rid, id sent by user=$id");
 
 256       http_die("wrong auth-ID");
 
 259     # set akey link for HTTP sessions
 
 260     # (need original id for consistant non-moving akey)
 
 261     if (-d $akeydir and open $idf,'<',"$from/@" and my $id = getline($idf)) {
 
 262       $akey = untaint(md5_hex("$from:$id"));
 
 263       unlink "$akeydir/$akey";
 
 264       symlink "../$from","$akeydir/$akey";
 
 268     COLLECTTO: foreach my $to (split(',',$to)) {
 
 269       if ($to !~ /.@./ and open my $AB,'<',"$from/\@ADDRESS_BOOK") {
 
 274           if (/^\s*([\S]+)\s+([\S]+)/) {
 
 275             my ($alias,$address) = ($1,$2);
 
 276             if ($to =~ /^\Q$alias\E$/i) {
 
 277               foreach my $to (split(",",$address)) {
 
 278                 $to .= '@'.$mdomain if $mdomain and $to !~ /@/;
 
 279                 $to{$to} = lc $to; # ignore dupes
 
 285       } elsif ($to =~ /^\@(.+)/) {
 
 286         my $group = "$from/\@GROUP/$1";
 
 287         if (not -l $group and open $group) {
 
 291             if (/(.+\@[w.-]+):.+/) {
 
 292               $to{$1} = lc $1; # ignore dupes
 
 298         $to .= '@'.$mdomain if $mdomain and $to !~ /.@./;
 
 299         $to{$to} = lc $to; # ignore dupes
 
 302     foreach $to (keys %to) {
 
 303       # if (-e "$to/\@CAPTIVE") { http_die("$to is CAPTIVE") }
 
 304       unless (-d $to or checkaddress($to)) {
 
 305         http_die("$to is not a legal e-mail address");
 
 311   if ($qs =~ /\&?KEEP=(\d+)/i) {
 
 313     $filename = filename($file);
 
 314     check_captive($file);
 
 317       if (symlink $keep,"$file/keep") {
 
 318         http_header('200 OK');
 
 319         print html_header($head),
 
 320               "<h3>set keep=$keep for $filename</h3>\n",
 
 323         http_header('599 internal error');
 
 324         print html_header($head),
 
 325               "<h3>$filename - $!</h3>\n",
 
 329       http_header('404 File not found');
 
 330       print html_header($head),
 
 331             "<h3>$filename not found</h3>\n",
 
 335   } elsif ($qs =~ s/\&?KEEP//i) {
 
 336     check_captive($file);
 
 340   if ($qs =~ s/\&?FILEID=(\w+)//i) { $fileid = $1 }
 
 342   if ($qs =~ s/\&?IGNOREWARNING//i) { $ignorewarning = 1 }
 
 345     http_header('200 OK','Content-Type: text/plain');
 
 347     chdir $file and exec '/client/bin/l';
 
 351   # copy file to yourself
 
 353     unless (-f "$file/data") {
 
 354       http_die("File not found.");
 
 356     ($to,$from,$file) = split('/',$file);
 
 358       # http_header('403 Forbidden');
 
 359       # print html_header($head),
 
 360       #  "You have no permission to copy a file.\n",
 
 361       #  "</body></html>\n";
 
 362       http_die("You have no permission to copy a file.");
 
 364     if (-s "$to/\@ALLOWED_RECIPIENTS") {
 
 365       http_die("You are a restricted user.");
 
 367     if (-e "$to/$to/$file/data") {
 
 368       # http_header('409 File Exists');
 
 369       # print html_header($head),
 
 370       #   "File $file already exists in your outgoing spool.\n",
 
 371       #   "</body></html>\n";
 
 372       http_die("File $file already exists in your outgoing spool.");
 
 374     mkdirp("$to/$to/$file");
 
 375     link "$to/$from/$file/data","$to/$to/$file/data"
 
 376       or http_die("cannot link to $to/$to/$file/data - $!\n");
 
 377     my $fkey = copy("$to/$from/$file/filename","$to/$to/$file/filename");
 
 378     open my $notify,'>',"$to/$to/$file/notify";
 
 380     my $dkey = randstring(8);
 
 381     unlink "$to/$to/$file/dkey","$dkeydir/$dkey";
 
 382     symlink "../$to/$to/$file","$dkeydir/$dkey";
 
 383     symlink $dkey,"$to/$to/$file/dkey";
 
 384     http_header('200 OK',"Location: $durl/$dkey/$fkey");
 
 385     print html_header($head),
 
 386       "File $file copied to yourself.\n",
 
 392   if ($qs =~ s/(^|&)DELETE//i) {
 
 394       $filename = filename($file);
 
 395       if (open my $log,'>',"$file/error") {
 
 396         printf {$log} "%s has been deleted by %s at %s\n",
 
 397                       $filename,$ENV{REMOTE_ADDR},isodate(time);
 
 400       foreach my $logdir (@logdir) {
 
 401         my $msg = sprintf "%s [%s_%s] %s %s deleted\n",
 
 402                   isodate(time),$$,$ENV{REQUESTCOUNT},$ra,encode_Q($file);
 
 403         if (open $log,'>>',"$logdir/$log") {
 
 408       http_header('200 OK',"X-File: $file");
 
 409       print html_header($head),
 
 410             "<h3>$filename deleted</h3>\n",
 
 414       http_die("no such file");
 
 419   # wipe out!? (for anonymous upload)
 
 420   if ($qs =~ s/(^|&)PURGE//i) {
 
 421     $filename = filename($file);
 
 422     if (@anonymous_upload and ipin($ra,@anonymous_upload)) {
 
 423       unlink "$dkeydir/$dkey" if $dkey;
 
 425         foreach my $logdir (@logdir) {
 
 426           my $msg = sprintf "%s [%s_%s] %s %s purged\n",
 
 427                     isodate(time),$$,$ENV{REQUESTCOUNT},$ra,encode_Q($file);
 
 428           if (open $log,'>>',"$logdir/$log") {
 
 433         http_header('200 OK',"X-File: $file");
 
 434         print html_header($head),
 
 435           "<h3>$filename purged</h3>\n",
 
 438         http_die("no such file");
 
 441       http_die("you are not allowed to purge $filename");
 
 446   # request for file size?
 
 449     # control back to fexsrv for further HTTP handling
 
 455     http_die("unknown query format $qs");
 
 460 unless ($id and $rid and $id eq $rid or $dkey or $anonymous) {
 
 461   http_die("wrong parameter $file");
 
 465   http_die("internal error: unknown recipient");
 
 469   http_die("internal error: unknown sender");
 
 472 &check_status($from);
 
 474 # server based ip restrictions
 
 475 if (@download_hosts and not ipin($ra,@download_hosts)) {
 
 477     "Downloads from your host ($ra) are not allowed.",
 
 478     "Contact $ENV{SERVER_ADMIN} for details."
 
 482 # user based ip restrictions
 
 483 unless (check_rhosts("$to/\@DOWNLOAD_HOSTS")) {
 
 484   http_die("You are not allowed to download from IP $ra");
 
 487 # file based ip restrictions
 
 488 unless (check_rhosts("$file/restrictions")) {
 
 489   http_die("Download of files from external user $from is restricted "
 
 490           ."to internal hosts. Your IP $ra is not allowed.");
 
 493 # set time mark for this access
 
 494 if ($file =~ m:(.+?)/:) {
 
 496   my $time = untaint(time);
 
 497   utime $time,$time,$user;
 
 501 if ($range = $ENV{HTTP_RANGE}) {
 
 502   $seek = $1 if $range =~ /^bytes=(\d+)-/i;
 
 503   $stop = $1 if $range =~ /^bytes=\d*-(\d+)/i;
 
 509 if (not $autodelete or $autodelete ne 'NO') {
 
 510   $autodelete = readlink "$file/autodelete" || 'YES';
 
 513 if ($from and $file eq "$from/$from/ADDRESS_BOOK") {
 
 514   if (open my $AB,'<',"$from/\@ADDRESS_BOOK") {
 
 525       'Content-Length: ' . length($ab),
 
 526       'Content-Type: text/plain',
 
 532       'HTTP/1.1 404 No address book found',
 
 537   # control back to fexsrv for further HTTP handling
 
 542   # already downloaded?
 
 543   if ($limited_download and $limited_download !~ /^n/i
 
 544       and $from ne $to                    # fex to yourself is ok!
 
 545       and $from !~ /^_?fexmail/           # fexmail is ok!
 
 546       and $to !~ /^_?fexmail/             # fexmail is ok!
 
 547       and $to !~ /^anonymous/             # anonymous fex is ok!
 
 548       and $to !~ /$amdl/                  # allowed multi download recipients
 
 549       and $http_client !~ /$adlm/         # allowed download managers
 
 550       and $file !~ /\/STDFEX$/            # xx is ok!
 
 551       and (slurp("$file/comment")||'') !~ /^!\*!/ # multi download allow flag
 
 552       and not($dkey and ($ENV{HTTP_COOKIE}||'') =~ /dkey=$dkey/)
 
 553       and open $file,'<',"$file/download")
 
 559       # allow downloads from same ip
 
 561       # allow downloads from sender ip
 
 562       $_ = '' if (readlink("$file/ip")||'') eq $ra;
 
 565       s/(.+) ([\w.:]+)$/by $2 at $1/;
 
 566       $file = filename($file);
 
 567       http_die("$file has already been downloaded $_");
 
 570   $sb = sendfile($file,$seek,$stop);
 
 574   http_die("<code>$file</code> has been withdrawn");
 
 575 } elsif (open $errf,'<',"$file/error" and $err = getline($errf)) {
 
 576   fdlog($log,$file,0,0);
 
 579   fdlog($log,$file,0,0);
 
 580   if ($file =~ /^anonymous.*afex_\d+\.tar$/) {
 
 581     # should be extra handled...
 
 583   http_die("no such file $file");
 
 586 debuglog(sprintf("%s %s %d %d %d",
 
 587          isodate(time),$file,$sb||0,$seek,-s $data||0));
 
 589 if ($sb+$seek == -s $data) {
 
 591   # note successfull download
 
 592   $download = "$file/download";
 
 593   if (open $download,'>>',$download) {
 
 594     printf {$download} "%s %s\n",isodate(time),$ENV{REMOTE_ADDR};
 
 598   # delete file after grace period
 
 599   if ($autodelete eq 'YES') {
 
 600     $grace_time = 60 unless defined $grace_time;
 
 602       my $utime = (stat $data)[8] || 0;
 
 603       my $dtime = (stat $download)[8] || 0;
 
 604       exit if $utime > $dtime;
 
 605       last if time > $dtime+$grace_time;
 
 609     my $error = "$file/error";
 
 610     if (open $error,'>',$error) {
 
 611       printf {$error} "%s has been autodeleted after download from %s at %s\n",
 
 612                       filename($file),$ENV{REMOTE_ADDR},isodate(time);
 
 623   my ($file,$seek,$stop) = @_;
 
 624   my ($filename,$size,$total_size,$fileid,$filetype);
 
 625   my ($data,$download,$header,$buf,$range,$s,$b,$t0);
 
 628   # swap to and from for special senders, see fup storage swap!
 
 629   $file =~ s:^(_?anonymous_.*)/(anonymous.*)/:$2/$1/:;
 
 630   $file =~ s:^(_?fexmail_.*)/(fexmail.*)/:$2/$1/:;
 
 632   $data     = $file.'/data';
 
 633   $download = $file.'/download';
 
 634   $header   = $file.'/header';
 
 636   # fallback defaults, should be set later with better values
 
 637   $filename = filename($file);
 
 638   $total_size = -s $data || 0;
 
 642     unless (-f $data and -r $data) {
 
 643       http_die("<code>$file</code> has been withdrawn");
 
 645     $data = abs_path($data);
 
 647     foreach (@file_link_dirs) {
 
 648       my $dir = abs_path($_);
 
 649       $fok = $data if $data =~ /^\Q$dir\//;
 
 652       http_die("no permission to download <code>$file</code>");
 
 655     unless (-f $data and -r $data) {
 
 656       http_die("<code>$file</code> has gone");
 
 660   if ($ENV{REQUEST_METHOD} eq 'GET') {
 
 661     debuglog("Exp: FROM=\"$from\"","Exp: TO=\"$to\"");
 
 662     open $data,$data and flock($data,LOCK_EX|LOCK_NB);
 
 663     # security check: must be regular file after abs_path()
 
 665       http_die("no permission to download <code>$file</code>");
 
 667     # HTTP Range download suckers are already rejected by fexsrv
 
 668     unless ($range = $ENV{HTTP_RANGE}) {
 
 670       open $download,'>>',$download or die "$download - $!\n";
 
 671       if ($file =~ m:(.+?)/(.+?)/: and $1 ne $2) {
 
 672         # only one concurrent download is allowed if sender <> recipient
 
 673         flock($download,LOCK_EX|LOCK_NB) or
 
 674           http_die("$file locked: a download is already in progress");
 
 677     $size = $total_size - $seek - ($stop ? $total_size-$stop-1 : 0);
 
 678   } elsif ($ENV{REQUEST_METHOD} eq 'HEAD') {
 
 679     $size = -s $data || 0;
 
 681     http_die("unknown HTTP request method $ENV{REQUEST_METHOD}");
 
 684   # read MIME entity header (what the client said)
 
 685   if (open $header,'<',$header) {
 
 687       if (/^Content-Type: (.+)/i) {
 
 696   $fileid = readlink "$file/id" || '';
 
 698   # determine own MIME entity header for download
 
 700   $mime =~ s:/.*:/\@MIME:;
 
 701   my $mt = $ENV{FEXHOME}.'/etc/mime.types';
 
 702   if (($type =~ /x-mime/i or -e $mime) and open $mt,'<',$mt) {
 
 703     $type = 'application/octet-stream';
 
 704     MIMETYPES: while (<$mt>) {
 
 708       my ($mt,@ft) = split;
 
 709       foreach my $ft (@ft) {
 
 710         if ($filename =~ /\.\Q$ft\E$/i) {
 
 718   # reset to default MIME type
 
 719   else { $type = 'application/octet-stream' }
 
 721   # HTML is not allowed for security reasons! (embedded javascript, etc)
 
 722   $type =~ s/html/plain/i;
 
 724   debuglog("download with $http_client");
 
 726   if ($seek or $stop) {
 
 728       http_header('416 Requested Range Not Satisfiable');
 
 732       $range = sprintf("bytes %s-%s/%s",$seek,$stop,$total_size);
 
 734       $range = sprintf("bytes %s-%s/%s",$seek,$total_size-1,$total_size);
 
 737       'HTTP/1.1 206 Partial Content',
 
 738       "Content-Length: $size",
 
 739       "Content-Range: $range",
 
 740       "Content-Type: $type",
 
 742     if ($http_client !~ /MSIE/) {
 
 743       nvt_print("Cache-Control: no-cache");
 
 744       if ($type eq 'application/octet-stream') {
 
 745         nvt_print("Content-Disposition: attachment; filename=\"$filename\"");
 
 750     # another stupid IE bug-workaround
 
 751     # http://drupal.org/node/163445
 
 752     # http://support.microsoft.com/kb/323308
 
 753     if ($http_client =~ /MSIE/ and not $nowarning) {
 
 754       # $type = 'application/x-msdownload';
 
 755       if ($ignorewarning) {
 
 756         $type .= "; filename=$filename";
 
 759           "Content-Length: $size",
 
 760           "Content-Type: $type",
 
 761 #         "Pragma: no-cache",
 
 762 #         "Cache-Control: no-store",
 
 763           "Content-Disposition: attachment; filename=\"$filename\"",
 
 766 #        nvt_print('','HTTP/1.1 200 OK',"Content-Length: $size","Content-Type: $type"); exit;
 
 768         http_header('200 OK');
 
 769         print html_header($head);
 
 771           '<h2>Internet Explorer warning</h2>'
 
 772           'Using Microsoft Internet Explorer for download will probably'
 
 773           'lead to problems, because it is not Internet compatible (RFC 2616).'
 
 775           'We recommend <a href="http://firefox.com">Firefox</a>'
 
 777           'If you really want to continue with Internet Explorer, then'
 
 778           '<a href="$ENV{REQUEST_URL}?IGNOREWARNING">'
 
 779           'click here with your right mouse button and select "save as"'
 
 782           'See also <a href="/FAQ/user.html">F*EX user FAQ</a>.'
 
 790         "Content-Length: $size",
 
 791         "Content-Type: $type",
 
 792         "Cache-Control: no-cache",
 
 795       if ($type eq 'application/octet-stream') {
 
 796         nvt_print(qq'Content-Disposition: attachment; filename="$filename"');
 
 800     nvt_print("X-Size: $total_size");
 
 801     nvt_print("X-File-ID: $fileid") if $fileid;
 
 802     # if ((`file "$file/data" 2>/dev/null` || '') =~ m{.*/data:\s(.+)}) {
 
 803     #  nvt_print("X-File-Type: $1");
 
 805     if ($dkey = $dkey||readlink "$file/dkey") {
 
 806       my $ma = (readlink "$file/keep"||$keep_default)*60*60*24;
 
 807       nvt_print("Set-Cookie: dkey=$dkey; Max-Age=$ma; Path=$ENV{REQUEST_URI}");
 
 812   if ($ENV{REQUEST_METHOD} eq 'HEAD') {
 
 813     # control back to fexsrv for further HTTP handling
 
 817   if ($ENV{REQUEST_METHOD} eq 'GET') {
 
 822       foreach (@throttle) {
 
 826           # throttle ip address?
 
 827           if ($throttle =~ /^[\d.-]+$/) {
 
 828             if (ipin($ra,$throttle)) {
 
 833           # throttle e-mail address?
 
 835             # allow wildcard *, but not regexps
 
 836             $throttle =~ quotemeta $throttle;
 
 837             $throttle =~ s/\*/.*/g;
 
 838             if ($to =~ /$throttle$/) {
 
 847     foreach my $sig (keys %SIG) { local $SIG{$sig} = \&sigexit }
 
 848     local $SIG{ALRM} = sub { die "TIMEOUT\n" };
 
 855     # sysread/syswrite because of speed
 
 856     while ($s < $size and $b = sysread($data,$buf,$bs)) {
 
 857       # last chunk for HTTP Range?
 
 858       if ($stop and $s+$b > $size) {
 
 860         $buf = substr($buf,0,$b)
 
 864       syswrite STDOUT,$buf or last; # client still alive?
 
 867         sleep 1 while $s/(time-$t0||1)/1024 > $bwl;
 
 874     fdlog($log,$file,$s,$size);
 
 884   my ($file,$upload,$to,$from,$dkey);
 
 889   ($to,$from,$file) = split('/',$path);
 
 894   # swap to and from for special senders, see fup storage swap!
 
 895   ($from,$to) = ($to,$from) if $from =~ /^(fexmail|anonymous)/;
 
 897   $to   .= '@'.$hostname if $to   eq 'anonymous';
 
 898   $from .= '@'.$hostname if $from eq 'anonymous';
 
 900   $to   .= '@'.$mdomain if -d "$to\@$mdomain";
 
 901   $from .= '@'.$mdomain if -d "$from\@$mdomain";
 
 903   $file =~ s/%([A-F0-9]{2})/chr(hex($1))/ge;
 
 904   $file = urlencode($file);
 
 906   if ($to eq '*' and $fileid) {
 
 907     foreach my $fd (glob "*/$from/$file") {
 
 909           and -l "$fd/id" and readlink "$fd/id" eq $fileid
 
 910           and $dkey = readlink "$fd/dkey") {
 
 916   } elsif ($to !~ /@/ and open my $AB,'<',"$from/\@ADDRESS_BOOK") {
 
 920       my ($alias,$address) = split;
 
 923         $address .= '@'.$mdomain if $mdomain and $address !~ /@/;
 
 933   if (-f "$to/$from/$file/data") {
 
 934     $dkey = readlink "$to/$from/$file/dkey";
 
 935     $fkey = slurp("$to/$from/$file/filename")||$file;
 
 938   $upload = -s "$to/$from/$file/upload" || -s "$to/$from/$file/data" || 0;
 
 939   $size = readlink "$to/$from/$file/size" || 0;
 
 940   $fileid = readlink "$to/$from/$file/id" || '';
 
 942   nvt_print('HTTP/1.1 200 OK');
 
 943   nvt_print("Server: fexsrv");
 
 944   nvt_print("Content-Length: $upload");
 
 945   nvt_print("X-Original-Recipient: $to");
 
 946   if ($dkey and not -s "$from/\@ALLOWED_RECIPIENTS") {
 
 947     nvt_print("X-DKEY: $dkey");
 
 948     nvt_print("X-Location: $durl/$dkey/$fkey") if $fkey;
 
 950   nvt_print("X-Size: $size");
 
 951   nvt_print("X-File-ID: $fileid") if $fileid;
 
 952   nvt_print("X-Features: $ENV{FEATURES}");
 
 962   if (open $ipr,$ipr) {
 
 967       if ($_ eq '@LOCAL_RHOSTS') {
 
 968         push @hosts,@local_rhosts if @local_rhosts;
 
 974     if (@hosts and not ipin($ra,@hosts)) {
 
 984     '401 Authorization Required',
 
 985     'WWW-Authenticate: Basic realm="'.$ENV{SERVER_NAME}.' F*EX download"',
 
 988   # control back to fexsrv for further HTTP handling
 
 994   my ($path,$user,$auth) = @_;
 
 995   my ($to,$from,$file,$dkey);
 
 997   my ($subuser,$subid);
 
1001   if ($path =~ m:(.+)/(.+)/(.+):) {
 
1002     ($to,$from,$file) = ($1,$2,$3);
 
1003   } elsif ($path =~ m:(.+)/(.+):) {
 
1004     ($dkey,$file) = ($1,$2);
 
1005     $path = readlink "$dkeydir/$dkey" or http_die('no such file');
 
1006     (undef,$to,$from,$file) = split('/',$path);
 
1008     http_die("wrong URL format for download");
 
1011   $to   .= '@'.$mdomain if $mdomain and $to   !~ /@/;
 
1012   $from .= '@'.$mdomain if $mdomain and $from !~ /@/;
 
1017   # auth user match to in download URL?
 
1018   if ($to ne $user and "$to\@$mdomain" ne $user and $to ne "$user@$mdomain") {
 
1019     debuglog("mismatch: to=$to, auth user=$user");
 
1023   # check for real user
 
1024   if (open $idf,'<',"$to/@") {
 
1025     $id = getline($idf);
 
1027     unless ($id and $id eq $auth) {
 
1028       debuglog("$user mismatch: id=$id, auth=$auth");
 
1032   # check for sub user
 
1033   elsif (open $idf,'<',"$from/\@SUBUSER") {
 
1037       ($subuser,$subid) = split ':';
 
1038       if ($subid and $subid eq $auth
 
1039           and ($user eq $subuser
 
1040                or $subuser eq '*@*'
 
1041                or $subuser =~ /^\*\@(.+)/ and $user =~ /\@\Q$1\E$/i
 
1042                or $subuser =~ /(.+)\@\*$/ and $user =~ /^\Q$1\E\@/i)) {
 
1049       debuglog("no matching $user in $from/\@SUBUSER");
 
1053     debuglog("no $to/@ and no $from/@");
 
1063   $to .= '@'.$mdomain if $mdomain and -d "$to\@$mdomain";
 
1064   if (-e "$to/\@CAPTIVE") {
 
1065     http_die("$to is CAPTIVE - no URL parameters allowed");
 
1074   $msg = @_ ? "@_" : '???';
 
1078   errorlog("$file caught SIGNAL $msg");
 
1080   # sigpipe means: client has terminated
 
1081   # this event will be handled further by sendfile(), do not terminate here
 
1082   if ($sig ne 'PIPE') {
 
1084     if ($sig eq 'DIE') {
 
1088       die "SIGNAL $msg\n";