3 # F*EX CGI for download
5 # Author: Ulli Horlacher <framstag@rus.uni-stuttgart.de>
8 BEGIN { ($ENV{PERLINIT}||'') =~ /(.+)/s and eval $1 }
11 use Fcntl qw':flock :seek';
18 ($FEXLIB) = $ENV{FEXLIB} =~ /(.+)/;
19 die "$0: no $FEXLIB\n" unless -d $FEXLIB;
21 our $error = 'F*EX download ERROR';
22 our $head = "$ENV{SERVER_NAME} F*EX download";
24 our ($spooldir,$tmpdir,@logdir,$skeydir,$dkeydir,$durl);
25 our ($bs,$fop_auth,$timeout,$keep_default,$nowarning);
26 our ($limited_download,$admin,$akey,$adlm,$amdl);
27 our (@file_link_dirs);
29 # load common code, local config : $HOME/lib/fex.ph
30 require "$FEXLIB/fex.pp" or die "$0: cannot load $FEXLIB/fex.pp - $!\n";
32 my $ra = $ENV{REMOTE_ADDR}||0;
33 if (@download_hosts and not ipin($ra,@download_hosts)) {
35 "Downloads from your host ($ra) are not allowed.",
36 "Contact $ENV{SERVER_ADMIN} for details."
42 # call localized fop if available
43 if ($0 !~ m{/locale/.*/fop} and my $lang = $ENV{HTTP_ACCEPT_LANGUAGE}) {
44 if ($lang =~ /^de/ and $0 =~ m{(.*)/cgi-bin/fop}) {
45 my $fop = "$1/locale/deutsch/cgi-bin/fop";
52 chdir $spooldir or die "$spooldir - $!\n";
54 my $http_client = $ENV{HTTP_USER_AGENT} || '';
56 $file = $ENV{PATH_INFO} || '';
57 http_die('no file name') unless $file;
58 $file =~ s:%3F:/?/:g; # escape '?' for URL-decoding
59 $file =~ s/%([\dA-F]{2})/unpack("a",pack("H2",$1))/ge;
60 $file =~ s:/\?/:%3F:g; # deescape '?'
61 $file =~ s:/\.\.:/__:g;
63 $file = untaint($file);
65 # secure mode with HTTP authorization?
68 if ($ENV{HTTP_AUTHORIZATION} and $ENV{HTTP_AUTHORIZATION} =~ /Basic\s+(.+)/) {
69 @http_auth = split(':',decode_b64($1));
71 if (@http_auth != 2) {
74 &check_auth($file,@http_auth);
77 # download-URL-scheme /$dkey/$file ?
78 if ($file =~ m:^([^/]+)/[^/]+$:) {
80 if ($link = readlink("$dkeydir/$dkey")) {
81 if ($link !~ s:^\.\./::) {
82 http_die("internal error on dkey for $link");
84 $file = untaint($link);
86 http_die("no such file $file");
89 # download-URL-scheme /$to/$from/$file
92 if ($ENV{REQUEST_METHOD} eq 'GET' and $file =~ m:.+/(.+)/.+:) {
94 if (-s "$from/\@ALLOWED_RECIPIENTS") {
95 http_die("$from is a restricted user");
99 # add mail-domain to addresses if necessary
100 if ($mdomain and $file =~ s:(.+)/(.+)/(.+):$3:) {
104 $to .= '@'.$hostname if $to eq 'anonymous';
105 $from .= '@'.$hostname if $from eq 'anonymous';
106 $to .= '@'.$mdomain if -d "$to\@$mdomain";
107 $from .= '@'.$mdomain if -d "$from\@$mdomain";
108 if ($ENV{REQUEST_METHOD} eq 'GET' and -s "$from/\@ALLOWED_RECIPIENTS") {
109 http_die("$from is a restricted user");
111 $file = "$to/$from/$file";
115 if ($file and $file =~ m:(.+)/(.+)/.+:) {
119 if ($from =~ s/^(anonymous).*/$1/) {
120 if (@anonymous_upload and ipin($ra,@anonymous_upload) or $dkey) {
123 http_header('403 Forbidden');
124 print html_header($head),
125 "You have no permission to request the URI $ENV{REQUEST_URI}\n",
131 http_die("unknown query format");
134 $data = "$file/data";
136 # open $file,$file; print Digest::MD5->new->addfile($file)->hexdigest;
138 # request with ?query-parameter ?
139 if ($qs = $ENV{QUERY_STRING}) {
141 http_die("\"$1\" is not allowed in URL") if $qs =~ /([<>\%\'\"])/;
143 # workaround for broken F*IX
144 $qs =~ s/&ID=skey:\w+//;
147 if ($qs =~ s/&*SKEY=([\w:]+)//i) {
150 if ($skey =~ s/^MD5H:(.+)/$1/) {
152 foreach my $s (glob "$skeydir/*") {
154 if ($skey eq md5_hex($s.$ENV{SID})) {
160 if (open $skey,'<',"$skeydir/$skey") {
163 $from = lc($1) if /^from=(.+)/;
164 $to = lc($1) if /^to=(.+)/;
168 $file =~ s:.*/:$to/$from/:;
170 http_die("INTERNAL ERROR: missing data in $skeydir/$skey");
173 debuglog("SKEY=$skey");
174 http_die("wrong SKEY authentification");
178 # group member with gkey?
179 if ($qs =~ s/&*GKEY=([\w:]+)//i) {
182 if ($gkey =~ s/^MD5H:(.+)/$1/) {
184 foreach my $g (glob "$gkeydir/*") {
186 if ($gkey eq md5_hex($g.$ENV{SID})) {
192 if (open $gkey,'<',"$gkeydir/$gkey") {
195 $from = lc($1) if /^from=(.+)/;
196 $group = lc($1) if /^to=\@(.+)/;
199 if ($from and $group and open $group,'<',"$from/\@GROUP/$group") {
205 $file =~ s:.*/:$to/$from/:;
211 http_die("INTERNAL ERROR: missing data in $gkeydir/$gkey");
214 debuglog("GKEY=$gkey");
215 http_die("wrong GKEY authentification");
219 # check for ID in query
220 elsif ($qs =~ s/\&*\bID=([^&]+)//i) {
224 if ($id eq 'PUBLIC') {
225 http_header('403 Forbidden');
229 if ($file =~ m:^(.+)/(.+)/(.+):) {
235 if ($mdomain and $from ne 'anonymous') {
236 $to .= '@'.$mdomain if $to !~ /@/;
237 $from .= '@'.$mdomain if $from !~ /@/;
242 http_die("unknown file query format");
245 # public or anonymous recipient? (needs no auth-ID for sender)
246 if ($anonymous or $id eq 'PUBLIC' and
247 @public_recipients and grep /^\Q$to\E$/i,@public_recipients) {
250 open my $idf,'<',"$from/@" or http_die("unknown user $from");
251 $rid = getline($idf);
253 $rid = sidhash($rid,$id);
256 unless ($id eq $rid) {
257 debuglog("real id=$rid, id sent by user=$id");
258 http_die("wrong auth-ID");
261 # set akey link for HTTP sessions
262 # (need original id for consistant non-moving akey)
263 if (-d $akeydir and open $idf,'<',"$from/@" and my $id = getline($idf)) {
264 $akey = untaint(md5_hex("$from:$id"));
265 unlink "$akeydir/$akey";
266 symlink "../$from","$akeydir/$akey";
270 COLLECTTO: foreach my $to (split(',',$to)) {
271 if ($to !~ /.@./ and open my $AB,'<',"$from/\@ADDRESS_BOOK") {
276 if (/^\s*([\S]+)\s+([\S]+)/) {
277 my ($alias,$address) = ($1,$2);
278 if ($to =~ /^\Q$alias\E$/i) {
279 foreach my $to (split(",",$address)) {
280 $to .= '@'.$mdomain if $mdomain and $to !~ /@/;
281 $to{$to} = lc $to; # ignore dupes
287 } elsif ($to =~ /^\@(.+)/) {
288 my $group = "$from/\@GROUP/$1";
289 if (not -l $group and open $group) {
293 if (/(.+\@[w.-]+):.+/) {
294 $to{$1} = lc $1; # ignore dupes
300 $to .= '@'.$mdomain if $mdomain and $to !~ /.@./;
301 $to{$to} = lc $to; # ignore dupes
304 foreach $to (keys %to) {
305 # if (-e "$to/\@CAPTIVE") { http_die("$to is CAPTIVE") }
306 unless (-d $to or checkaddress($to)) {
307 http_die("$to is not a legal e-mail address");
313 if ($qs =~ /\&?KEEP=(\d+)/i) {
315 $filename = filename($file);
316 check_captive($file);
319 if (symlink $keep,"$file/keep") {
320 http_header('200 OK');
321 print html_header($head),
322 "<h3>set keep=$keep for $filename</h3>\n",
325 http_header('599 internal error');
326 print html_header($head),
327 "<h3>$filename - $!</h3>\n",
331 http_header('404 File not found');
332 print html_header($head),
333 "<h3>$filename not found</h3>\n",
337 } elsif ($qs =~ s/\&?KEEP//i) {
338 check_captive($file);
342 if ($qs =~ s/\&?FILEID=(\w+)//i) { $fileid = $1 }
344 if ($qs =~ s/\&?IGNOREWARNING//i) { $ignorewarning = 1 }
347 http_header('200 OK','Content-Type: text/plain');
349 chdir $file and exec '/client/bin/l';
353 # copy file to yourself
355 unless (-f "$file/data") {
356 http_die("File not found.");
358 ($to,$from,$file) = split('/',$file);
360 # http_header('403 Forbidden');
361 # print html_header($head),
362 # "You have no permission to copy a file.\n",
363 # "</body></html>\n";
364 http_die("You have no permission to copy a file.");
366 if (-s "$to/\@ALLOWED_RECIPIENTS") {
367 http_die("You are a restricted user.");
369 if (-e "$to/$to/$file/data") {
370 # http_header('409 File Exists');
371 # print html_header($head),
372 # "File $file already exists in your outgoing spool.\n",
373 # "</body></html>\n";
374 http_die("File $file already exists in your outgoing spool.");
376 mkdirp("$to/$to/$file");
377 link "$to/$from/$file/data","$to/$to/$file/data"
378 or http_die("cannot link to $to/$to/$file/data - $!\n");
379 my $fkey = copy("$to/$from/$file/filename","$to/$to/$file/filename");
380 open my $notify,'>',"$to/$to/$file/notify";
382 my $dkey = randstring(8);
383 unlink "$to/$to/$file/dkey","$dkeydir/$dkey";
384 symlink "../$to/$to/$file","$dkeydir/$dkey";
385 symlink $dkey,"$to/$to/$file/dkey";
386 http_header('200 OK',"Location: $durl/$dkey/$fkey");
387 print html_header($head),
388 "File $file copied to yourself.\n",
394 if ($qs =~ s/(^|&)DELETE//i) {
396 $filename = filename($file);
397 if (open my $log,'>',"$file/error") {
398 printf {$log} "%s has been deleted by %s at %s\n",
399 $filename,$ENV{REMOTE_ADDR},isodate(time);
402 foreach my $logdir (@logdir) {
403 my $msg = sprintf "%s [%s_%s] %s %s deleted\n",
404 isodate(time),$$,$ENV{REQUESTCOUNT},$ra,encode_Q($file);
405 if (open $log,'>>',"$logdir/$log") {
410 http_header('200 OK',"X-File: $file");
411 print html_header($head),
412 "<h3>$filename deleted</h3>\n",
416 http_die("no such file");
421 # wipe out!? (for anonymous upload)
422 if ($qs =~ s/(^|&)PURGE//i) {
423 $filename = filename($file);
424 if (@anonymous_upload and ipin($ra,@anonymous_upload)) {
425 unlink "$dkeydir/$dkey" if $dkey;
427 foreach my $logdir (@logdir) {
428 my $msg = sprintf "%s [%s_%s] %s %s purged\n",
429 isodate(time),$$,$ENV{REQUESTCOUNT},$ra,encode_Q($file);
430 if (open $log,'>>',"$logdir/$log") {
435 http_header('200 OK',"X-File: $file");
436 print html_header($head),
437 "<h3>$filename purged</h3>\n",
440 http_die("no such file");
443 http_die("you are not allowed to purge $filename");
448 # request for file size?
451 # control back to fexsrv for further HTTP handling
457 http_die("unknown query format $qs");
462 unless ($id and $rid and $id eq $rid or $dkey or $anonymous) {
463 http_die("wrong parameter $file");
467 http_die("internal error: unknown recipient");
471 http_die("internal error: unknown sender");
474 &check_status($from);
476 # server based ip restrictions
477 if (@download_hosts and not ipin($ra,@download_hosts)) {
479 "Downloads from your host ($ra) are not allowed.",
480 "Contact $ENV{SERVER_ADMIN} for details."
484 # user based ip restrictions
485 unless (check_rhosts("$to/\@DOWNLOAD_HOSTS")) {
486 http_die("You are not allowed to download from IP $ra");
489 # file based ip restrictions
490 unless (check_rhosts("$file/restrictions")) {
491 http_die("Download of files from external user $from is restricted "
492 ."to internal hosts. Your IP $ra is not allowed.");
495 # set time mark for this access
496 if ($file =~ m:(.+?)/:) {
498 my $time = untaint(time);
499 utime $time,$time,$user;
503 if ($range = $ENV{HTTP_RANGE}) {
504 $seek = $1 if $range =~ /^bytes=(\d+)-/i;
505 $stop = $1 if $range =~ /^bytes=\d*-(\d+)/i;
511 if (not $autodelete or $autodelete ne 'NO') {
512 $autodelete = readlink "$file/autodelete" || 'YES';
515 if ($from and $file eq "$from/$from/ADDRESS_BOOK") {
516 if (open my $AB,'<',"$from/\@ADDRESS_BOOK") {
527 'Content-Length: ' . length($ab),
528 'Content-Type: text/plain',
534 'HTTP/1.1 404 No address book found',
539 # control back to fexsrv for further HTTP handling
544 # already downloaded?
545 if ($limited_download and $limited_download !~ /^n/i
546 and $from ne $to # fex to yourself is ok!
547 and $from !~ /^_?fexmail/ # fexmail is ok!
548 and $to !~ /^_?fexmail/ # fexmail is ok!
549 and $to !~ /^anonymous/ # anonymous fex is ok!
550 and $to !~ /$amdl/ # allowed multi download recipients
551 and $http_client !~ /$adlm/ # allowed download managers
552 and $file !~ /\/STDFEX$/ # xx is ok!
553 and (slurp("$file/comment")||'') !~ /^!\*!/ # multi download allow flag
554 and not($dkey and ($ENV{HTTP_COOKIE}||'') =~ /dkey=$dkey/)
555 and open $file,'<',"$file/download")
557 my $d1 = <$file> || ''; # first download
561 # allow downloads from same ip
562 $d1 = '' if $d1 =~ /\Q$ra/;
563 # allow downloads from sender ip
564 $d1 = '' if (readlink("$file/ip")||'') eq $ra;
566 if ($d1 and $d1 =~ s/(.+) ([\w.:]+)$/$2 at $1/) {
567 $file = filename($file);
568 http_die("$file has already been downloaded by $d1");
571 $sb = sendfile($file,$seek,$stop);
575 http_die("<code>$file</code> has been withdrawn");
576 } elsif (open $errf,'<',"$file/error" and $err = getline($errf)) {
577 fdlog($log,$file,0,0);
580 fdlog($log,$file,0,0);
581 if ($file =~ /^anonymous.*afex_\d+\.tar$/) {
582 # should be extra handled...
584 http_die("no such file $file");
587 debuglog(sprintf("%s %s %d %d %d",
588 isodate(time),$file,$sb||0,$seek,-s $data||0));
590 if ($sb+$seek == -s $data) {
592 # note successfull download
593 $download = "$file/download";
594 if (open $download,'>>',$download) {
595 printf {$download} "%s %s\n",isodate(time),$ENV{REMOTE_ADDR};
599 # delete file after grace period
600 if ($autodelete eq 'YES') {
601 $grace_time = 60 unless defined $grace_time;
603 my $utime = (stat $data)[8] || 0;
604 my $dtime = (stat $download)[8] || 0;
605 exit if $utime > $dtime;
606 last if time > $dtime+$grace_time;
610 my $error = "$file/error";
611 if (open $error,'>',$error) {
612 printf {$error} "%s has been autodeleted after download from %s at %s\n",
613 filename($file),$ENV{REMOTE_ADDR},isodate(time);
624 my ($file,$seek,$stop) = @_;
625 my ($filename,$size,$total_size,$fileid,$filetype);
626 my ($data,$download,$header,$buf,$range,$s,$b,$t0);
629 # swap to and from for special senders, see fup storage swap!
630 $file =~ s:^(_?anonymous_.*)/(anonymous.*)/:$2/$1/:;
631 $file =~ s:^(_?fexmail_.*)/(fexmail.*)/:$2/$1/:;
633 $data = $file.'/data';
634 $download = $file.'/download';
635 $header = $file.'/header';
637 # fallback defaults, should be set later with better values
638 $filename = filename($file);
639 $total_size = -s $data || 0;
643 unless (-f $data and -r $data) {
644 http_die("<code>$file</code> has been withdrawn");
646 $data = abs_path($data);
648 foreach (@file_link_dirs) {
649 my $dir = abs_path($_);
650 $fok = $data if $data =~ /^\Q$dir\//;
653 http_die("no permission to download <code>$file</code>");
656 unless (-f $data and -r $data) {
657 http_die("<code>$file</code> has gone");
661 if ($ENV{REQUEST_METHOD} eq 'GET') {
662 debuglog("Exp: FROM=\"$from\"","Exp: TO=\"$to\"");
663 open $data,$data and flock($data,LOCK_EX|LOCK_NB);
664 # security check: must be regular file after abs_path()
666 http_die("no permission to download <code>$file</code>");
668 # HTTP Range download suckers are already rejected by fexsrv
669 unless ($range = $ENV{HTTP_RANGE}) {
671 open $download,'>>',$download or die "$download - $!\n";
672 if ($file =~ m:(.+?)/(.+?)/: and $1 ne $2) {
673 # only one concurrent download is allowed if sender <> recipient
674 flock($download,LOCK_EX|LOCK_NB) or
675 http_die("$file locked: a download is already in progress");
678 $size = $total_size - $seek - ($stop ? $total_size-$stop-1 : 0);
679 } elsif ($ENV{REQUEST_METHOD} eq 'HEAD') {
680 $size = -s $data || 0;
682 http_die("unknown HTTP request method $ENV{REQUEST_METHOD}");
685 # read MIME entity header (what the client said)
686 if (open $header,'<',$header) {
688 if (/^Content-Type: (.+)/i) {
697 $fileid = readlink "$file/id" || '';
699 # determine own MIME entity header for download
701 $mime =~ s:/.*:/\@MIME:;
702 my $mt = $ENV{FEXHOME}.'/etc/mime.types';
703 if (($type =~ /x-mime/i or -e $mime) and open $mt,'<',$mt) {
704 $type = 'application/octet-stream';
705 MIMETYPES: while (<$mt>) {
709 my ($mt,@ft) = split;
710 foreach my $ft (@ft) {
711 if ($filename =~ /\.\Q$ft\E$/i) {
719 # reset to default MIME type
720 else { $type = 'application/octet-stream' }
722 # HTML is not allowed for security reasons! (embedded javascript, etc)
723 $type =~ s/html/plain/i;
725 debuglog("download with $http_client");
727 if ($seek or $stop) {
729 http_header('416 Requested Range Not Satisfiable');
733 $range = sprintf("bytes %s-%s/%s",$seek,$stop,$total_size);
735 $range = sprintf("bytes %s-%s/%s",$seek,$total_size-1,$total_size);
737 # RFC 7233 "Responses to a Range Request"
739 'HTTP/1.1 206 Partial Content',
740 "Content-Length: $size",
741 "Content-Range: $range",
742 "Content-Type: $type",
744 if ($http_client !~ /MSIE/) {
745 nvt_print("Cache-Control: no-cache");
746 if ($type eq 'application/octet-stream') {
747 nvt_print("Content-Disposition: attachment; filename=\"$filename\"");
752 # another stupid IE bug-workaround
753 # http://drupal.org/node/163445
754 # http://support.microsoft.com/kb/323308
755 if ($http_client =~ /MSIE/ and not $nowarning) {
756 # $type = 'application/x-msdownload';
757 if ($ignorewarning) {
758 $type .= "; filename=$filename";
761 "Content-Length: $size",
762 "Content-Type: $type",
763 # "Pragma: no-cache",
764 # "Cache-Control: no-store",
765 "Content-Disposition: attachment; filename=\"$filename\"",
768 # nvt_print('','HTTP/1.1 200 OK',"Content-Length: $size","Content-Type: $type"); exit;
769 nvt_print($_) foreach(@extra_header);
771 http_header('200 OK');
772 print html_header($head);
774 '<h2>Internet Explorer warning</h2>'
775 'Using Microsoft Internet Explorer for download will probably'
776 'lead to problems, because it is not Internet compatible (RFC 2616).'
778 'We recommend <a href="http://firefox.com">Firefox</a>'
780 'If you really want to continue with Internet Explorer, then'
781 '<a href="$ENV{REQUEST_URL}?IGNOREWARNING">'
782 'click here with your right mouse button and select "save as"'
785 'See also <a href="/FAQ/user.html">F*EX user FAQ</a>.'
793 "Content-Length: $size",
794 "Content-Type: $type",
795 "Cache-Control: no-cache",
798 if ($type eq 'application/octet-stream') {
799 nvt_print(qq'Content-Disposition: attachment; filename="$filename"');
801 nvt_print($_) foreach(@extra_header);
804 nvt_print("X-Size: $total_size");
805 nvt_print("X-File-ID: $fileid") if $fileid;
806 # if ((`file "$file/data" 2>/dev/null` || '') =~ m{.*/data:\s(.+)}) {
807 # nvt_print("X-File-Type: $1");
809 if ($dkey = $dkey||readlink "$file/dkey") {
810 my $ma = (readlink "$file/keep"||$keep_default)*60*60*24;
811 nvt_print("Set-Cookie: dkey=$dkey; Max-Age=$ma; Path=$ENV{REQUEST_URI}");
816 if ($ENV{REQUEST_METHOD} eq 'HEAD') {
817 # control back to fexsrv for further HTTP handling
821 if ($ENV{REQUEST_METHOD} eq 'GET') {
826 foreach (@throttle) {
830 # throttle ip address?
831 if ($throttle =~ /^[\d.-]+$/) {
832 if (ipin($ra,$throttle)) {
837 # throttle e-mail address?
839 # allow wildcard *, but not regexps
840 $throttle =~ quotemeta $throttle;
841 $throttle =~ s/\*/.*/g;
842 if ($to =~ /$throttle$/) {
851 foreach my $sig (keys %SIG) { local $SIG{$sig} = \&sigexit }
852 local $SIG{ALRM} = sub { die "TIMEOUT\n" };
859 # sysread/syswrite because of speed
860 while ($s < $size and $b = sysread($data,$buf,$bs)) {
861 # last chunk for HTTP Range?
862 if ($stop and $s+$b > $size) {
864 $buf = substr($buf,0,$b)
868 syswrite STDOUT,$buf or last; # client still alive?
871 sleep 1 while $s/(time-$t0||1)/1024 > $bwl;
878 fdlog($log,$file,$s,$size);
888 my ($file,$upload,$to,$from,$dkey);
893 ($to,$from,$file) = split('/',$path);
898 # swap to and from for special senders, see fup storage swap!
899 ($from,$to) = ($to,$from) if $from =~ /^(fexmail|anonymous)/;
901 $to .= '@'.$hostname if $to eq 'anonymous';
902 $from .= '@'.$hostname if $from eq 'anonymous';
904 $to .= '@'.$mdomain if -d "$to\@$mdomain";
905 $from .= '@'.$mdomain if -d "$from\@$mdomain";
907 $file =~ s/%([A-F0-9]{2})/chr(hex($1))/ge;
908 $file = urlencode($file);
910 if ($to eq '*' and $fileid) {
911 foreach my $fd (glob "*/$from/$file") {
913 and -l "$fd/id" and readlink "$fd/id" eq $fileid
914 and $dkey = readlink "$fd/dkey") {
920 } elsif ($to !~ /@/ and open my $AB,'<',"$from/\@ADDRESS_BOOK") {
924 my ($alias,$address) = split;
927 $address .= '@'.$mdomain if $mdomain and $address !~ /@/;
937 if (-f "$to/$from/$file/data") {
938 $dkey = readlink "$to/$from/$file/dkey";
939 $fkey = slurp("$to/$from/$file/filename")||$file;
942 $upload = -s "$to/$from/$file/upload" || -s "$to/$from/$file/data" || 0;
943 $size = readlink "$to/$from/$file/size" || 0;
944 $fileid = readlink "$to/$from/$file/id" || '';
946 nvt_print('HTTP/1.1 200 OK');
947 nvt_print("Server: fexsrv");
948 nvt_print("Content-Length: $upload");
949 nvt_print("X-Original-Recipient: $to");
950 if ($dkey and not -s "$from/\@ALLOWED_RECIPIENTS") {
951 nvt_print("X-DKEY: $dkey");
952 nvt_print("X-Location: $durl/$dkey/$fkey") if $fkey;
954 nvt_print("X-Size: $size");
955 nvt_print("X-File-ID: $fileid") if $fileid;
956 nvt_print("X-Features: $ENV{FEATURES}");
966 if (open $ipr,$ipr) {
971 if ($_ eq '@LOCAL_RHOSTS') {
972 push @hosts,@local_rhosts if @local_rhosts;
978 if (@hosts and not ipin($ra,@hosts)) {
988 '401 Authorization Required',
989 'WWW-Authenticate: Basic realm="'.$ENV{SERVER_NAME}.' F*EX download"',
992 # control back to fexsrv for further HTTP handling
998 my ($path,$user,$auth) = @_;
999 my ($to,$from,$file,$dkey);
1001 my ($subuser,$subid);
1005 if ($path =~ m:(.+)/(.+)/(.+):) {
1006 ($to,$from,$file) = ($1,$2,$3);
1007 } elsif ($path =~ m:(.+)/(.+):) {
1008 ($dkey,$file) = ($1,$2);
1009 $path = readlink "$dkeydir/$dkey" or http_die('no such file');
1010 (undef,$to,$from,$file) = split('/',$path);
1012 http_die("wrong URL format for download");
1015 $to .= '@'.$mdomain if $mdomain and $to !~ /@/;
1016 $from .= '@'.$mdomain if $mdomain and $from !~ /@/;
1021 # auth user match to in download URL?
1022 if ($to ne $user and "$to\@$mdomain" ne $user and $to ne "$user@$mdomain") {
1023 debuglog("mismatch: to=$to, auth user=$user");
1027 # check for real user
1028 if (open $idf,'<',"$to/@") {
1029 $id = getline($idf);
1031 unless ($id and $id eq $auth) {
1032 debuglog("$user mismatch: id=$id, auth=$auth");
1036 # check for sub user
1037 elsif (open $idf,'<',"$from/\@SUBUSER") {
1041 ($subuser,$subid) = split ':';
1042 if ($subid and $subid eq $auth
1043 and ($user eq $subuser
1044 or $subuser eq '*@*'
1045 or $subuser =~ /^\*\@(.+)/ and $user =~ /\@\Q$1\E$/i
1046 or $subuser =~ /(.+)\@\*$/ and $user =~ /^\Q$1\E\@/i)) {
1053 debuglog("no matching $user in $from/\@SUBUSER");
1057 debuglog("no $to/@ and no $from/@");
1067 $to .= '@'.$mdomain if $mdomain and -d "$to\@$mdomain";
1068 if (-e "$to/\@CAPTIVE") {
1069 http_die("$to is CAPTIVE - no URL parameters allowed");
1078 $msg = @_ ? "@_" : '???';
1082 errorlog("$file caught SIGNAL $msg");
1084 # sigpipe means: client has terminated
1085 # this event will be handled further by sendfile(), do not terminate here
1086 if ($sig ne 'PIPE') {
1088 if ($sig eq 'DIE') {
1092 die "SIGNAL $msg\n";