]> git.treefish.org Git - fex.git/blob - cgi-bin/fup
Original release 20150729
[fex.git] / cgi-bin / fup
1 #!/usr/bin/perl -wT
2
3 # F*EX CGI for upload
4 #
5 # Author: Ulli Horlacher <framstag@rus.uni-stuttgart.de>
6 #
7 # Contribs:
8 #       Sebastian Zaiser <szcode@arcor.de> (upload status)
9 #
10
11 BEGIN { ($ENV{PERLINIT}||'') =~ /(.+)/s and eval $1 }
12
13 use Encode;
14 use Fcntl               qw':flock :seek :mode';
15 use IO::Handle;
16 use Digest::MD5         qw'md5_hex';
17 use Cwd                 qw'abs_path';
18
19 # add fex lib
20 (our $FEXLIB) = $ENV{FEXLIB} =~ /(.+)/;
21
22 $| = 1;
23
24 our $debug;
25 our $ndata = 0;
26 our $error = 'F*EX upload ERROR';
27 our $head = "$ENV{SERVER_NAME} F*EX upload";
28 our $autodelete = 'YES';
29 our $locale;
30
31 # import from fex.ph
32 our (@locales,@throttle,$bcc,$keep_max,$nomail,$nostore,$overwrite);
33 our (@local_domains,@local_rdomains,@local_hosts,@local_rhosts,);
34 our (@registration_hosts,@demo,@file_link_dirs);
35
36 # import from fex.pp
37 our ($FEXHOME);
38 our ($spooldir,$durl,$tmpdir,@logdir,$logdir,$docdir,$hostname,$admin,$fra);
39 our ($keep_default,$recipient_quota,$sender_quota,$fex_yourself);
40 our ($sendmail,$mdomain,$fop_auth,$mail_auth,$faillog,$amdl);
41 our ($dkeydir,$ukeydir,$akeydir,$skeydir,$gkeydir,$xkeydir);
42 our ($MB,$DS);
43 our $RB;                # read POST bytes (total)
44 our $akey = '';
45 our $dkey = '';
46 our $skey = '';
47 our $gkey = '';
48
49 our $seek = 0;          # already sent bytes (from previous upload)
50 our $filesize = 0;      # total file size
51 our $fpsize = 0;        # file part size (MIME-part)
52
53 my $data;
54 my $boundary;
55 my $rid = '';           # real ID
56 my @header;             # HTTP entity header
57 my $fileid;             # file ID
58 my $captive;
59 my $muser;              # main user fur sub or group user
60   
61 # load common code, local config: $FEXLIB/fex.ph
62 require "$FEXLIB/fex.pp";
63
64 # load fup local config
65 our ($info_1,$info_2,$info_login);
66
67 $locale = $ENV{LOCALE} || 'english';
68 foreach (
69   "/var/lib/fex/locale/$locale/lib/fup.pl", 
70   "$FEXLIB/fup.pl",
71 ) {
72   if (-f) {
73     require;
74     last;
75   }
76 }
77
78 &check_camel unless $sid;
79
80 chdir $spooldir or http_die("$spooldir - $!\n");
81
82 my $log = 'fup.log';
83
84 my $http_client = $ENV{HTTP_USER_AGENT} || '';
85 my $cl = $ENV{X_CONTENT_LENGTH} || $ENV{CONTENT_LENGTH} || 0;
86
87 $fra .= '/'.$ENV{HTTP_X_FORWARDED_FOR} if $ENV{HTTP_X_FORWARDED_FOR};
88
89 $from = $to = $id = $file = $fkey = $comment = $command = $bwlimit = '';
90 $filename = $okey = $addto = $replyto = $submit = '';
91 @to = ();
92 $data = '';
93 $locale = untaint($ENV{LOCALE}||'');
94
95 my $ra = $ENV{REMOTE_ADDR}||0;
96 if (@upload_hosts and not ipin($ra,@upload_hosts)) {
97   http_die(
98     "Uploads from your host ($ra) are not allowed.",
99     "Contact $ENV{SERVER_ADMIN} for details."
100   );
101 }
102
103 &check_maint;
104
105 &parse_request; # showstatus will not come back!
106
107 if ($addto) {
108   my %to;
109   foreach $to (@to) { $to{$to} = 1 }
110   push @to,$addto unless $to{$addto};
111   if ($submit and @to == 1) { $addto = '' }
112 }
113
114 $to = join(',',@to);
115
116 if ($from eq $to and $fex_yourself =~ /^no|0$/i) {
117   http_die("fexing to yourself is not allowed");
118 }
119
120 $uid = randstring(8) unless $uid; # upload ID
121
122 # user requests for forgotten ID
123 $id_forgotten = $id if $id =~ /^"?\?"?$/;
124 if ($from and $id_forgotten and $mail_authid and not ($fop_auth or $nomail)) {
125   &check_status($from);
126   &id_forgotten;
127   exit;
128 }
129
130 # public recipients? (needs no auth-ID for sender)
131 if ($to and $id and $id eq 'PUBLIC' and @public_recipients) {
132   
133   unless ($from) {
134     http_die("missing sender e-mail address");
135   }
136   # must use $param{FROM} for checking because $from is expanded with $mdomain
137   unless (checkaddress(despace($param{FROM}))) {
138     http_die("<code>$param{FROM}</code> is not a valid e-mail address");
139   }
140   foreach my $to (@to) {
141     unless (grep /^\Q$to\E$/i,@public_recipients) {
142       http_die("<code>$to</code> is not a valid recipient");
143     }
144   }
145   $restricted = $public = $rid = $id;
146 }
147
148 # anonymous upload from enabled IP?
149 if ($from =~ /^anonymous@/ and 
150     @anonymous_upload and ipin($ra,@anonymous_upload)) {
151   $id = $rid = $anonymous = 'anonymous';
152   if ($to =~ /^anonymous/) {
153     @to = ($to);
154     $autodelete{$to} = $autodelete = 'NO'; 
155   }
156   $nomail = $anonymous;
157 }
158
159 $comment = 'NOMAIL' if $nomail and not $comment;
160
161 # one time token
162 if ($okey) {
163   $to = "@to" or http_die("no recipient specified");
164   $from = readlink "$to/\@OKEY/$okey" 
165     or http_die("no upload key \"<code>$okey</code>\" - ".
166                 "request another one from <code>$to</code>");
167   $from = untaint($from);
168 }
169
170 &check_status($from) if $from;
171
172 # look for regular sender ID
173 if ($id and $from and not ($public or $anonymous or $okey)) {
174   if (open $from,'<',"$from/\@") {
175     # chomp($rid = <$from> || '');
176     $rid = getline($from);
177     close $from;
178     $rid = sidhash($rid,$id);
179     # set time mark for successfull access
180     if ($id eq $rid) {
181       my $time = untaint(time);
182       utime $time,$time,$from;
183     }
184   } else {
185     my $error = $!;
186     # if recipient (to) is specified, we have to look for subusers later, too
187     unless (@to) {
188       fuplog("ERROR: $spooldir/$from/\@ $error");
189       debuglog("cannot open $spooldir/$from/\@ : $error");
190       faillog("user $from, id $id");
191       http_die("wrong user or auth-ID");
192     }
193   }
194 }
195
196 # check regular ID
197 if ($from and $id and not ($gkey or $skey or $public or $okey)) {
198   if ($rid and $rid eq $id) {
199     # set akey link for HTTP sessions
200     # (need original id for consistant non-moving akey)
201     if (-d $akeydir and open $idf,'<',"$from/@" and my $id = getline($idf)) {
202       $akey = untaint(md5_hex("$from:$id"));
203       mksymlink("$akeydir/$akey","../$from");
204       # show URL from fexsend
205       if ($from eq $to and $comment eq '*') {
206         mksymlink("$akeydir/$akey","../$from");
207       }
208     }
209     $captive = -e "$from/\@CAPTIVE";
210   } else {
211     fuplog("ERROR: wrong auth-ID for $from");
212     debuglog("id sent by user $from=$id, real id=$rid");
213     faillog("user $from, id $id");
214     http_die("Wrong user or auth-ID");
215   }
216 }
217
218 # optional $auth_hook() in fup.pl
219 if ($auth_hook and ($akey or $skey or $gkey) and $from and -d $from) {
220   &$auth_hook;
221 }
222
223 # forward a copy of a file to another recipient
224 if ($akey and $dkey and $command eq 'FORWARD') {
225   my $file = untaint(readlink "$dkeydir/$dkey"||'');
226   http_die("unknown dkey <code>$dkey></code>") unless $file;
227   $file =~ s:^\.\./::;
228   forward($file);
229   exit;
230 }
231
232 # modify file parameter
233 if ($akey and $dkey and $command eq 'MODIFY') {
234   my $file = untaint(readlink "$dkeydir/$dkey"||'');
235   http_die("unknown dkey <code>$dkey</code>") unless $file;
236   $file =~ s:^\.\./::;
237   modify($file);
238   exit;
239 }
240
241 # copy file from incoming to outgoing spool
242 if ($akey and $dkey and $command eq 'COPY') {
243   unless ($file = readlink "$dkeydir/$dkey") {
244     http_die("No such file with DKEY=$dkey");
245   }
246   if ($file =~ m:../(.+)/(.+)/(.+):) {
247     ($to,$from,$file) = ($1,$2,$3);
248   } else {
249     http_die("Bad DKEY $dkey -> $file");
250   }
251   unless (-f "$to/$from/$file/data") {
252     http_die("File not found");
253   }
254   if (-e "$to/$to/$file/data") {
255     http_die("File $file already exists in your outgoing spool") 
256       if (readlink("$to/$to/$file/id")||$to) ne 
257          (readlink("$to/$from/$file/id")||$from);
258   } else {
259     mkdirp("$to/$to/$file");
260     link "$to/$from/$file/data","$to/$to/$file/data" 
261       or http_die("cannot link to $to/$to/$file/data - $!\n");
262     copy("$to/$from/$file/filename","$to/$to/$file/filename");
263     copy("$to/$from/$file/id","$to/$to/$file/id");
264     open $file,'>',"$to/$to/$file/notify";
265     close $file;
266     open $file,'>',"$to/$to/$file/download";
267     print {$file} "$to\n";
268     close $file;
269     $dkey = randstring(8);
270     unlink "$to/$to/$file/dkey","$to/$to/$file/keep","$dkeydir/$dkey";
271     symlink "../$to/$to/$file","$dkeydir/$dkey";
272     symlink $dkey,"$to/$to/$file/dkey";
273   }
274   nvt_print(
275     "HTTP/1.1 302 Found",
276     "Location: $ENV{PROTO}://$ENV{HTTP_HOST}/rup?akey=$akey&oto=$to&file=$file",
277     'Content-Length: 0',
278     ''
279   );
280   &reexec;
281 }
282
283 # delete file without download
284 if ($akey and $dkey and $command eq 'DELETE') {
285   $del = untaint(readlink "$dkeydir/$dkey"||'');
286   http_die("unknown dkey <code>$dkey</code>") unless $del;
287   $del =~ s:^\.\./::;
288   $filename = filename($del);
289   if (unlink("$del/data") or unlink("$del/upload")) {
290     if (open F,'>',"$del/error") {
291       printf F "%s has been deleted by %s at %s\n",
292                $filename,$ENV{REMOTE_ADDR},isodate(time);
293       close F;
294     }
295     # http_header('200 OK');
296     # print html_header($head);
297     # print "<h3>$filename deleted</h3>\n";
298     nvt_print(
299       "HTTP/1.1 302 Found",
300       "Location: $ENV{PROTO}://$ENV{HTTP_HOST}/fup?akey=$akey&command=LISTRECEIVED",
301       'Content-Length: 0',
302       ""
303     );
304     &reexec;
305   } else { 
306     my $s = $!;
307     http_header('404 Not Found');
308     print html_header($head);
309     print "<h3>$filename not deleted ($s)</h3>\n";
310     print "<a href=\"/fup?akey=$akey&command=LISTRECEIVED\">continue</a>\n" if $akey;
311     print "</body></html>\n";
312   }
313   exit;
314 }
315
316 # special commands
317 if (($from and $id and $rid eq $id or $gkey or $skey) and $command) {
318                                                                      
319   if ($command eq 'CHECKQUOTA') {
320     http_die("illegal command \"$command\"") if $public or $anonymous;
321     nvt_print('HTTP/1.1 204 OK');
322     # nvt_print("X-SID: $ENV{SID}") if $ENV{SID};
323     ($quota,$du) = check_sender_quota($muser||$from);
324     nvt_print("X-Sender-Quota: $quota $du")    if $quota;
325     ($quota,$du) = check_recipient_quota($muser||$from);
326     nvt_print("X-Recipient-Quota: $quota $du") if $quota;
327     nvt_print('');
328     exit;
329   }
330
331   if ($command eq 'LISTSETTINGS') {
332     http_die("illegal command \"$command\"") if $public or $anonymous;
333     nvt_print('HTTP/1.1 204 OK');
334     # nvt_print("X-SID: $ENV{SID}") if $ENV{SID};
335     ($quota,$du) = check_sender_quota($muser||$from);
336     nvt_print("X-Sender-Quota: $quota $du")    if $quota;
337     ($quota,$du) = check_recipient_quota($muser||$from);
338     nvt_print("X-Recipient-Quota: $quota $du") if $quota;
339     $autodelete = lc(readlink "$from/\@AUTODELETE" || $autodelete);
340     nvt_print("X-Autodelete: $autodelete");
341     $keep = readlink "$from/\@KEEP" || $keep;
342     nvt_print("X-Default-Keep: $keep");
343     $locale = readlink "$from/\@LOCALE" || $default_locale || 'english';
344     nvt_print("X-Default-Locale: $locale");
345     $mime = -e "$from/\@MIME" ? 'yes' : 'no';
346     nvt_print("X-MIME: $mime");
347     nvt_print('');
348     exit;
349   }
350
351   if ($command eq 'RENOTIFY') {
352     http_die("illegal command \"$command\"") if $public or $anonymous;
353     my $nfile = '';
354     if ($dkey) {
355       # resend notification e-mail
356       $file = readlink("$dkeydir/$dkey")
357         or html_error($error,"illegal DKEY $dkey");
358       $file =~ s:^../::;
359       $file = untaint($file);
360       unlink "$file/download"; # re-allow download from any ip address
361       notify_locale($dkey,'new');
362       http_header(
363         '200 OK',
364         "X-Notify: $file",
365       );
366       $nfile = $file;
367     } else {
368       http_header('200 OK');
369     }
370     print html_header($head);
371     # list sent files
372     print "<h3>Files from $from, ",
373           "click on the file name to resend a notification e-mail:</h3>\n",
374           "<pre>\n";
375     foreach $file (glob "*/$from/*") {
376       next if $file =~ m:/STDFEX$:;
377       next if $file =~ m:(.+?)/: and -l $1;
378       $size = -s "$file/data";
379       next unless $size;
380       $size = int($size/$MB+0.5);
381       $filename = $comment = '';
382       my $rto = $file;
383       $rto =~ s:/.*::;
384       if ($dkey = readlink "$file/dkey") {
385         if ($rto ne $to) {
386           $to = $rto;
387           print "\nto $to :\n";
388         }
389         if (open $file,'<',"$file/filename") {
390           $filename = <$file>;
391           close $file;
392         }
393         if ($filename and length $filename) { 
394           $filename = html_quote($filename);
395         } else { 
396           $filename = '???';
397         }
398         if (open $file,'<',"$file/comment") {
399           $comment = untaint(html_quote(getline($file)));
400           close $file;
401         }
402         my $rkeep = untaint(readlink "$file/keep"||$keep_default)
403                     - int((time-mtime("$file/filename"))/$DS);
404         if ($comment =~ /NOMAIL/ or 
405            (readlink "$to/\@NOTIFICATION"||'') =~ /^no/i) {
406           printf "%8s MB [%s d] %s/%s/%s\n",
407                  $size,
408                  $rkeep,
409                  $durl,
410                  $dkey,
411                  urlencode(basename($file));
412         } else {
413           printf "%8s MB [%s d] <a href=\"%s\">%s</a>%s %s\n",
414                  $size,
415                  $rkeep,
416                  untaint("/fup?akey=$akey&dkey=$dkey&command=RENOTIFY"),
417                  $filename,
418                  $comment ? qq' "$comment"' : '',
419                  $file eq $nfile ? 
420                    " &rarr; notification e-mail has been resent" :
421                    "";
422         }
423       }
424     }
425     pq(qq(
426       '</pre>'
427       '<p><a href="/foc?akey=$akey">back to F*EX operation control</a>'
428       '</body></html>'
429     ));
430     exit;
431   } 
432
433   if ($command =~ /^LIST(RECEIVED)?$/) {
434     http_die("illegal command \"$command\"") if $public or $anonymous;
435     # list sent files
436     if ($to and $param{'TO'} eq '*') {
437       http_header('200 OK');
438       print html_header($head);
439 #            "(Format: [size] [rest keep time] [filename] [comment])<p>\n",
440       print "<h3>Files from $from:</h3>\n",
441             "<pre>\n";
442       foreach $file (glob "*/$from/*") {
443         next if $file =~ m:/STDFEX$:;
444         next if $file =~ m:(.+?)/: and -l $1;
445         $size = -s "$file/data";
446         next unless $size;
447         $size = int($size/$MB+0.5);
448         $filename = $comment = '';
449         my $rto = $file;
450         $rto =~ s:/.*::;
451         if ($dkey = readlink "$file/dkey") {
452         # die $file if -s "$file/data" and $file =~ /^$from/;
453           if ($rto ne $to) {
454             $to = $rto;
455             print "\nto $to :\n";
456           }
457           if (open $file,'<',"$file/filename") {
458             $filename = <$file>;
459             close $file;
460           }
461           if ($filename and length $filename) { 
462             $filename = html_quote($filename);
463           } else { 
464             $filename = '???';
465           }
466           if (open $file,'<',"$file/comment") {
467             $comment = untaint(html_quote(getline($file)));
468             close $file;
469           }
470           my $rkeep = untaint(readlink "$file/keep"||$keep_default) 
471                       - int((time-mtime("$file/filename"))/$DS);
472           printf "%8s MB [%s d] <a href=\"%s\">%s</a>%s\n",
473                  $size,
474                  $rkeep,
475                  untaint("/fup?akey=$akey&dkey=$dkey&command=FORWARD"),
476                  $filename,
477                  $comment?qq( "$comment"):'';
478         }
479       }
480       pq(qq(
481         '</pre>'
482         '<p><a href="javascript:history.back()">back to F*EX operation control</a>'
483         '</body></html>'
484       ));
485     } 
486     # list received files
487     else {
488       $to = $from;
489       http_header('200 OK');
490       print html_header($head);
491 #            "(Format: [size] [rest keep time] [URL] [comment])<p>\n",
492       print "<h3>Files for $to (*):</h3>\n",
493             "<pre>\n";
494       foreach $from (glob "$to/*") {
495         next if $from =~ /[A-Z]/;
496         $from =~ s:.*/::;
497         $url = '';
498         foreach $file (glob "$to/$from/*") {
499           next if $file =~ /\/STDFEX$/;
500           $filename = $comment = '';
501           $size = -s "$file/data";
502           next unless $size;
503           $size = int($size/$MB+0.5);
504           if ($dkey = readlink "$file/dkey") {
505             print "\nfrom $from :\n" unless $url;
506             $file =~ m:.*/(.+):;
507             $url = "$durl/$dkey/$1";
508             unless (-l "$dkeydir/$dkey") {
509               symlink untaint("../$file"),untaint("$dkeydir/$dkey");
510             }
511             if (open $file,'<',"$file/filename") {
512               $filename = <$file>;
513               close $file;
514             }
515             if ($filename and length $filename) { 
516               $filename = html_quote($filename);
517             } else { 
518               $filename = '???';
519             }
520             if (open $file,'<',"$file/comment") {
521               $comment = untaint(html_quote(getline($file)));
522               $comment = ' "'.$comment.'"';
523               close $file;
524             }
525             my $rkeep = untaint(readlink "$file/keep"||$keep_default) 
526                         - int((time-mtime("$file/filename"))/$DS);
527             printf "[<a href=\"/fup?akey=%s&dkey=%s&command=DELETE\">delete</a>] ",
528                    $akey,$dkey;
529             printf "[<a href=\"/fup?akey=%s&dkey=%s&command=COPY\">forward</a>] ",
530                    $akey,$dkey;
531             printf "%8s MB (%s d) <a href=\"%s\">%s</a>%s\n",
532                    $size,$rkeep,$url,$filename,$comment;
533           }
534         }
535       }
536       pq(qq(
537         '</pre>'
538         '(*) Files for other e-mail addresses you own will not be listed here!<p>'
539         '<a href="/foc?akey=$akey">back to F*EX operation control</a>'
540         '</body></html>'
541       ));
542     }
543     exit;
544   } 
545       
546   if ($command eq 'LISTSENT') {
547     http_die("illegal command \"$command\"") if $public or $anonymous;
548     # show download URLs 
549     http_header('200 OK');
550     print html_header($head);
551     print "<h2>Download URLs of files you have sent\n";
552     foreach $to (glob "*/$from") {
553       if (@files = glob "$to/*/data") {
554         $to =~ s:/.*::;
555         print "<h3>to <code>$to</code> :</h3>\n";
556         print "<pre>\n";
557         foreach $file (@files) {
558           $file =~ s:/data::;
559           next if $file =~ /\/STDFEX$/;
560           $dkey = readlink "$file/dkey" or next;
561           $file =~ s:.*/::;
562           print "$ENV{PROTO}://$ENV{HTTP_HOST}/fop/$dkey/$file\n";
563         }
564         print "</pre>\n";
565       }
566     }
567     pq(qq(
568       '</pre>'
569       '<p><a href="javascript:history.back()">back to F*EX operation control</a>'
570       '</body></html>'
571     ));
572     exit;
573   }
574       
575   if ($command eq 'FOPLOG') {
576     http_die("illegal command \"$command\"") if $public or $anonymous;
577     if (open my $log,"$logdir/fop.log") {
578       http_header('200 OK');
579       while (<$log>) {
580         next if /\/STDFEX\s/;
581         if (s:^([^/]+)/$from/:$1 :) {
582           if (s:(\d+)/(\d+)$:$1: and $1 and $1 == $2) {
583             s/ \[[\d_]+\]//;
584             print;
585           }
586         }
587       }
588     }
589     exit;
590   }
591   
592   if ($command eq 'RECEIVEDLOG') {
593     http_die("illegal command \"$command\"") if $public or $anonymous;
594     if (open my $log,"$logdir/fup.log") {
595       http_header('200 OK');
596       while (<$log>) {
597         next if /\sSTDFEX\s/;
598         if (/\d+$/) { 
599           my @F = split;
600           if ($F[5] eq $to) {
601             s/ \[[\d_]+\]//;
602             print;
603           }
604         }
605       }
606     }
607     exit;
608   }
609
610   if ($command eq 'SENDLOG') {
611     http_die("illegal command \"$command\"") if $public or $anonymous;
612     if (open my $log,"$logdir/fup.log") {
613       http_header('200 OK');
614       while (<$log>) {
615         next if /\sSTDFEX\s/;
616         if (/(\S+\@\S+)/ and $1 eq $from) { 
617           s/ \[[\d_]+\]//;
618           print;
619         }
620       }
621     }
622     exit;
623   }
624
625   if (@to and $command eq 'CHECKRECIPIENT') {
626     http_die("illegal command \"$command\"") if $public or $anonymous;
627     check_rr($from,@to);
628     nvt_print('HTTP/1.1 204 OK');
629     nvt_print("X-SID: $sid") if $sid;
630     foreach my $to (@group?@group:@to) {
631       # my $options = sprintf "(autodelete=%s,keep=%s,locale=%s)",
632       # readlink "$to/\@LOCALE"||$locale||$locale{$to}||$default_locale;
633       my $options = sprintf "(autodelete=%s,keep=%s,locale=%s,notification=%s)",
634         $autodelete{$to}||$autodelete,
635         $keep{$to}||$keep_default,
636         readlink("$to/\@LOCALE")||$default_locale,
637         readlink("$to/\@NOTIFICATION")||'full';
638       nvt_print("X-Recipient: $to $options");
639     }
640     nvt_print('');
641     # control back to fexsrv for further HTTP handling
642     &reexec;
643   }
644
645   if ($file and @to and $command eq 'DELETE') {
646     http_die("illegal command \"$command\"") if $public or $anonymous;
647     foreach (@group?@group:@to) {
648       my $to = $_;
649       $to =~ s/:\w+=.*//; # remove options from address
650       $del = "$to/$from/$fkey";
651       # swap to and from for special senders, see fup storage swap!
652       $del = "$from/$to/$fkey" if $from =~ /^(fexmail|anonymous)/;
653
654       $del =~ s:^/+::;
655       if ($del =~ /\/\./) {
656         http_die("illegal parameter <code>$del</code>");
657       }
658       $del = untaint($del);
659       
660       if (unlink("$del/data") or unlink("$del/upload")) {
661         if (open F,'>',"$del/error") {
662           print F "$file has been deleted by $from\n";
663           close F;
664         }
665         http_header('200 OK',"X-File: $del");
666         print html_header($head);
667         print "<h3>$file deleted</h3>\n";
668       } else { 
669         http_header("404 Not Found");
670         print html_header($head);
671         print "<h3>$file not deleted</h3>\n";
672       }
673       if ($akey) {
674         printf "<a href=\"/fup?akey=%s&to=%s&command=LISTRECEIVED\">continue</a>\n",
675                $akey,$to;
676       }
677       print "</body></html>\n";
678     }
679     exit;
680   }
681
682 }
683
684 # ip restrictions
685 if ($from and $id and $rid eq $id and open my $ipr,"$from/\@UPLOAD_HOSTS") {
686   my @hosts;
687   while (<$ipr>) {
688     chomp;
689     s/#.*//;
690     push @hosts,$_ if /\w/;
691   }
692   close $ipr;
693   unless (@hosts and ipin($ra,@hosts)) {
694     http_die("<code>$from</code> is not allowed to upload from IP $ra");
695   }
696 }
697
698 # quotas 
699 if ($from and $id and $rid eq $id and @to and not $flink and not $seek) {
700   my ($quota,$du);
701   
702   # check sender quota
703   ($quota,$du) = check_sender_quota($muser||$from);
704   if ($quota and $du+$cl/$MB > $quota) {
705     http_die("you are overquota");
706   }
707   
708   # check recipient quota
709   foreach my $to (@to) {
710     ($quota,$du) = check_recipient_quota($to);
711     if ($quota and $du+$cl/$MB > $quota) {
712       http_die("$to cannot receive files: is overquota");
713     }
714   }
715
716 }
717
718 # check recipients restriction
719 if ($id and $id eq $rid and $from and @to and not $public) {
720   check_rr($from,@to);
721 }
722
723 # on secure mode "fop authorization" also check if recipient(s) exists
724 # (= has a F*EX ID)
725 if (not $addto and $fop_auth and $id and $id eq $rid and $from and @to) {
726   my ($to_reg,$idf,$subuser);
727   foreach my $to (my @loop = @to) {
728     $to =~ s/:\w+=.*//; # remove options from address
729     $to_reg = 0;
730     # full user?
731     if (open $idf,'<',"$to/@") {
732       $to_reg = getline($idf);
733       close $idf;
734     } 
735     # sub user?
736     elsif (open $idf,'<',"$from/\@SUBUSER") {
737       while (<$idf>) {
738         s/#.*//;
739         next unless /:/;
740         chomp;
741         ($subuser) = split ':';
742         if ($subuser eq $to or $subuser eq '*@*'
743             or $subuser =~ /^\*\@(.+)/ and $to =~ /\@\Q$1\E$/i
744             or $subuser =~ /(.+)\@\*$/ and $to =~ /^\Q$1\E\@/i) {
745           $to_reg = $_;
746           last;
747         }
748       }
749       close $idf;
750     }
751     unless ($to_reg) {
752       http_die("recipient <code>$to</code> is not a registered F*EX full or sub user");
753     }
754   }
755 }
756
757 $to = join(',',@to);
758   
759 if ($to =~ /^@(.+)/) {
760   if ($nomail) {
761     http_die("server runs in NOMAIL mode - groups ($to) are not allowed");
762   }
763   my $gf = "$from/\@GROUP/$1";
764   if (open $gf,'<',$gf) {
765     while (<$gf>) {
766       s/#.*//;
767       push @group,$1 if /(.+@.+):/;
768     }
769   }
770   close $gf;
771   $group = $to;
772 }
773
774 if ($redirect) {
775   nvt_print(
776     "HTTP/1.1 302 Found",
777     "Location: $ENV{PROTO}://$ENV{HTTP_HOST}/$redirect?akey=$akey",
778     'Content-Length: 0',
779     ""
780   );
781   &reexec;
782 }
783
784 if ($from and $id and $id eq $rid and $faillog) {
785   unlink $faillog;
786 }
787
788 # display HTML form and request user data
789 unless ($file) {
790
791   if ($test) { $cgi = $test } 
792   else       { $cgi = $ENV{SCRIPT_NAME} }
793   $cgi = 'fup';
794   
795   # delete old cookies on logout referer
796   my @cookies;
797   if ($logout and my $cookie = $ENV{HTTP_COOKIE}) {
798     while ($cookie =~ s/(\w+key)=\w+//) {
799       push @cookies,"Set-Cookie: $1=; Max-Age=0; Discard";
800     }
801   }
802   
803   if (($akey or $skey or $gkey) and $from and -d $from) {
804     # save default locale for this user
805     if (not $locale and ($ENV{HTTP_COOKIE}||'') =~ /\blocale=(\w+)/) {
806       $locale = $1;
807     }
808     mksymlink("$from/\@LOCALE",$locale) if $locale;
809   }
810
811   http_header('200 OK',@cookies);
812   # print html_header($head,'<img src="/fex_small.gif">');
813   print html_header($head);
814     
815   if ($http_client =~ /(Konqueror|w3m)/) {
816     pq(qq(
817       '<p><hr><p>'
818       '<center>'
819       '<h3>Your client seems to be "$1" which is incompatible with F*EX and will probably not work!</h3>'
820       'We recommend firefox.'
821       '</center>'
822       '<p><hr><p>'
823     ));
824   }
825
826   # default "fex yourself" setting?
827   if ($from and $id and $id eq $rid and not $addto 
828       and not ($gkey or $skey or $okey or $public or $anonymous)
829       and (not @to or "@to" eq $from)
830       and -f "$from/\@FEXYOURSELF")
831   { 
832     @to = ($from);
833     $nomail = 'fexyourself';
834   }
835
836   # ask for recipient address(es)
837   elsif ($from and $id and $id eq $rid and ($addto or not $submit or not @to)
838          and not ($gkey or $skey or $okey or $public or $anonymous))
839   {
840     present_locales('/fup');
841     
842     @ab = ("<option></option>");
843     
844     # select menu from server address book
845     if (open my $AB,'<',"$from/\@ADDRESS_BOOK") {
846       while (<$AB>) {
847         s/#.*//g;
848         if (/(\S+)[=\s]+(\S+@[\w.-]+\S*)/) {
849           $_ = "$1 &lt;$2>";
850           s/,.*/,.../g;
851           push @ab,"<option>$_</option>";
852         }
853       }
854       close $AB;
855     }
856     
857     unless (@to) {
858       unless ($nomail) {
859         foreach (glob "$from/\@GROUP/*") {
860           if (-f and not -l) {
861             s:.*/::;
862             push @ab,"<option>\@$_</option>" unless /~$/;
863           }
864         }
865       }
866     }
867       
868     my $ab64 = b64("from=$from&id=$id");
869 #     '<form class="uploadform" name="upload"'
870     pq(qq(
871       '<form name="upload"'
872       '      action="/fup"'
873       '      method="post"'
874       '      accept-charset="UTF-8"'
875       '      enctype="multipart/form-data">'
876       '  <input type="hidden" name="from" value="$from">'
877       '  <input type="hidden" name="id"   value="$id">'
878       '  <table border="1">'
879       '    <tr><td>sender:   <td><a href="/fup/$ab64">$from</a></tr>'
880       '    <tr title="e-mail address or alias"><td>recipient(s):'
881       '        <td><input type="text" name="to" size="96" value="$to"><br>'
882     ));
883     if (grep /@/,@ab) {
884       pq(qq(
885         '        or select from your address book:'
886         '        <select name="addto" size="1">@ab</select>'
887         '        and'
888         '        <input type="submit" name="addsubmit" value="add to recipients list">'
889       ));
890     }
891     pq(qq(
892       '    </tr>'
893       '  </table>'
894       '  <p>'
895     ));
896     my $rr = "$from/\@ALLOWED_RECIPIENTS";
897     if (-s $rr and open $rr,'<',$rr) {
898       pq(qq(
899         'You are a restricted user and may only fex to these recipients:<p>'
900         '<pre>'
901       ));
902       while (<$rr>) {
903         chomp;
904         s/#.*//;
905         s/\s//g;
906         next unless $_;
907         if (/^\@LOCAL_RDOMAINS/) {
908           foreach my $rd (@local_rdomains) {
909             print "*\@$rd\n";
910           }
911         } else {
912           print "$_\n";
913         }
914       }
915       print "</pre><p>\n";
916       close $rr;
917     }
918     print qq'  <input type="submit" name="submit" value="check recipient(s) and continue">';
919     if ($fex_yourself =~ /^yes|1/i) {
920       print qq' or <input type="submit" name="fexyourself" value="fex yourself">'
921     }
922     print "\n</form>\n<p>\n";
923     if ($akey and -f "$from/\@" and not $captive ) {
924       pq(qq(
925         '<a href="/foc?akey=$akey">user config & operation control</a>'
926       ));
927     }
928     
929     if ($from eq $admin ) {
930       pq(qq(
931         '<p>'
932         '<a href="/fac">server config & admin control</a>'
933       ));
934     }
935     
936     if (0 and -f "$docdir/FIX.jar") {
937       print "<p>\n";
938       if    ($public) { print "<a href=\"/fix?from=$from&id=$public&to=$to\">" }
939       elsif ($skey)   { print "<a href=\"/fix?skey=$skey&to=$to\">" }
940       elsif ($gkey)   { print "<a href=\"/fix?gkey=$gkey&to=$to\">" }
941       else            { print "<a href=\"/fix?akey=$akey\">" }
942       print "Alternate Java client</a> (for files > 2 GB or sending of more than one file)\n";
943     }
944     print &logout;
945     if (-x "$FEXHOME/cgi-bin/login") {
946       print $info_login||$info_1;
947     }
948     pq(qq(
949       '<p><hr><p>'
950       '<b>'
951       'Warning: the recipient must not be a mailing list, because after'
952       'download the file will be no more available!'
953       '</b><br>'
954       'Contact <a href="mailto:$ENV{SERVER_ADMIN}">fexmaster</a>'
955       'if you want to fex to a mailing list,'
956       'he can allow multiple downloads for specific addresses.'
957       '</body></html>'
958     ));
959     exit;
960   } 
961   
962   # ask for filename
963   if ($from and ($id or $okey)) {
964     $to = $group if $group;
965     present_locales($ENV{REQUEST_URI}) if $skey or $gkey or $okey;
966 #      "        '$ENV{PROTO}://$ENV{HTTP_HOST}/$cgi?showstatus=$uid',"
967     pq(qq(
968       '<script type="text/javascript">'
969       '  function showstatus() {'
970       '    var file  = document.forms["upload"].elements["file"].value;'
971       '    if (file != "") {'
972       '      window.open('
973       "        '/$cgi?showstatus=$uid',"
974       "        'fup_status',"
975       "        'width=700,height=500'"
976       '      );'
977       '      return true;'
978       '    }'
979       '    return false;'
980       '  }'
981       ''
982       '  function checkupload() {'
983       '    var file  = document.forms["upload"].elements["file"].value;'
984       '    if (file == "") { alert("No file selected"); }'
985       '  }'
986       ''
987       '  function reportsize() {'
988       '    var form = document.forms["upload"];'
989       '    var filesize = form.file.files[0].size;'
990       '    // alert(filesize + " bytes");'
991       '    form.elements["filesize"].value = filesize;'
992       '    filesize = filesize.toString();'
993       '    filesize = filesize.replace(/(\\d)(?=(\\d\\d\\d)+(?!\\d))/g,"\$1,");'
994       '    document.getElementById("filesize").innerHTML = filesize + " bytes";'
995       '  }'
996       '</script>'
997     ));
998     pq(qq(
999       '<form name="upload"'
1000       '      action="/fup"'
1001       '      method="post"'
1002       '      accept-charset="UTF-8"'
1003       '      enctype="multipart/form-data"'
1004       '      onsubmit="return showstatus();">'
1005       '  <input type="hidden" name="uid"      value="$uid">'
1006       '  <input type="hidden" name="from"     value="$from">'
1007       '  <input type="hidden" name="filesize" value="">'
1008     ));
1009     
1010     if ($public) {
1011       my $toh = join('<br>',@to);
1012       pq(qq(
1013         '  <input type="hidden" name="id" value="$public">'
1014         '  <input type="hidden" name="to" value="$to">'
1015         '  <table border="1">'
1016         '    <tr><td>sender:   <td><code>$from</code></tr>'
1017         '    <tr><td>recipient:<td><code>$toh</code></tr>'
1018       ));
1019     } elsif ($okey) {
1020       pq(qq(
1021         '  <input type="hidden" name="okey" value="$okey">'
1022         '  <input type="hidden" name="to" value="$to">'
1023         '  <table border="1">'
1024         '    <tr><td>sender:   <td>$from</tr>'
1025         '    <tr><td>recipient:<td>$to</tr>'
1026       ));
1027     } elsif ($skey) {
1028       pq(qq(
1029         '  <input type="hidden" name="skey" value="$skey">'
1030         '  <table border="1">'
1031         '    <tr><td>sender:   <td>$from</tr>'
1032         '    <tr><td>recipient:<td>$to</tr>'
1033       ));
1034     } elsif (@group) {
1035       if ($gkey) {
1036         pq(qq(
1037           '  <input type="hidden" name="gkey" value="$gkey">'
1038         ));
1039       }
1040       my $toh = "group $group:<ul>";
1041       my $toc = join(',',@group);
1042       foreach my $gm (@group) { $toh .= "<li>$gm" }
1043       $toh .= "</ul>";
1044       pq(qq(
1045         '  <input type="hidden" name="id" value="$id">'
1046         '  <table border="1">'
1047         '    <tr><td>sender:<td>$from</tr>'
1048         '    <tr><td>recipient(s):'
1049         '        <td><input type="hidden" name="to" value="$toc">$toh</tr>'
1050       ));
1051     } else {
1052       my $toc = join(',',@to);
1053       my $toh = join('<br>',@to);
1054       pq(qq(
1055         '  <input type="hidden" name="akey" value="$akey">'
1056         '  <table border="1">'
1057         '    <tr><td>sender:<td>$from</tr>'
1058       ));
1059       if ($anonymous) {
1060         pq(qq(
1061           '    <tr><td>recipient:'
1062           '        <td><input type="hidden" name="to" value="$toc">$toh</tr>'
1063         ));
1064       } else {
1065         pq(qq(
1066           '    <tr><td><a href="/fup?akey=$akey&to=$toc">recipient(s)</a>:'
1067           '        <td><input type="hidden" name="to" value="$toc">$toh</tr>'
1068         ));
1069       }
1070     }
1071     
1072     $autodelete = lc $autodelete;
1073     $keep = $keep_default unless $keep;
1074     my ($quota,$du) = check_sender_quota($muser||$from);
1075     $quota = $quota 
1076            ? "<tr><td>sender quota (used):<td>$quota ($du) MB</tr>" 
1077            : '';
1078     
1079     $bwl = qq'<input type="text" name="bwlimit" size="8" value="$bwlimit"> kB/s';
1080     if (@throttle) {
1081       foreach (@throttle) {
1082         if (/\[?(.+?)\]?:(\d+)$/) {
1083           my $throttle = $1;
1084           my $limit = $2;
1085           # throttle ip address?
1086           if ($throttle =~ /^[\w:.-]+$/) {
1087             if (ipin($ra,$throttle)) {
1088               $bwl = qq'<input type="hidden" name="bwlimit" value="$limit"> $limit kB/s';
1089               last;
1090             }
1091           } 
1092           # throttle e-mail address?
1093           else {
1094             # allow wildcard *, but not regexps
1095             $throttle =~ quotemeta $throttle;
1096             $throttle =~ s/\*/.*/g;
1097             if ($from =~ /^$throttle$/i) {
1098               $bwl = qq'<input type="hidden" name="bwlimit" value="$limit"> $limit kB/s';
1099               last;
1100             }
1101           }
1102         }
1103       }
1104     }
1105     
1106     $autodelete = $autodelete{$to} if $autodelete{$to};
1107     
1108     my $adt = '';
1109     for ($autodelete) {
1110          if (/yes/i)   { $adt = 'delete file after download' } 
1111       elsif (/no/i)    { $adt = 'do not delete file after download' }
1112       elsif (/delay/i) { $adt = 'delete file after download with delay' } 
1113       elsif (/^\d+$/)  { $adt = "delete file $autodelete days after download" }
1114     }
1115     $adt .= qq'<input type="hidden" name="autodelete" value="$autodelete">';
1116
1117     my $ctr = my $ktr = '';
1118     if ($nomail) {
1119       $ctr = qq'<em>no notification e-mail will be send</em>';
1120     } else {
1121       $ctr = qq'<input type="text" name="comment" size="80" value="$comment">';
1122     }
1123     if ($captive) {
1124       $ktr = qq'$keep days<input type="hidden" name="keep" value="$keep">';
1125     } else {
1126       $ktr = qq'$keep days<input type="hidden" name="keep" value="$keep">';
1127     }
1128     pq(qq(
1129       '    <tr><td>autodelete:'
1130       '      <td>$adt'
1131       '    </tr>'
1132       '    <tr title="keep file max $keep days, then delete it"><td>keep:'
1133       '      <td>$ktr'
1134       '    </tr>'
1135       '    $quota'
1136       '    <tr title="optional, full speed if empty"><td>bandwith limit:'
1137       '      <td>$bwl'
1138       '    </tr>'
1139       '    <tr title="optional, will be included in notification e-mail"><td>comment:'
1140       '      <td>$ctr'
1141       '    </tr>'
1142       '    <tr title="If you want to send more than one file, then put them in a zip or tar archive"><td>file:'
1143       '      <td><input type="file" name="file" size="80" value="$file" onchange="reportsize();">'
1144       '    </tr>'
1145       '    <tr><td>file size:<td id="filesize"></td></tr>'
1146       '  </table>'
1147       '  <p>
1148       '  <input type="submit" value="upload" onclick="checkupload()">'
1149       '<p>'
1150       '</form>'
1151     ));
1152     if ($akey and -f "$from/\@" and not $captive) {
1153       print "<p>\n",
1154             "<a href=\"/foc?akey=$akey\">user config & operation control</a>\n";
1155     }
1156     if ($from eq $admin ) {
1157       pq(qq(
1158         '<p>'
1159         '<a href="/fac">server config & admin control</a>'
1160       ));
1161     }
1162     if (0 and -f "$docdir/FIX.jar" and not $okey) {
1163       print "<p>\n";
1164       if    ($public) { print "<a href=\"/fix?from=$from&id=$public&to=$to\">" }
1165       elsif ($skey)   { print "<a href=\"/fix?skey=$skey&to=$to\">" }
1166       elsif ($gkey)   { print "<a href=\"/fix?gkey=$gkey&to=$to\">" }
1167       else            { print "<a href=\"/fix?akey=$akey&to=$to\">" }
1168       print "Alternate Java client</a> (for files > 2 GB or sending of more than one file)\n";
1169     }
1170     print &logout;
1171     print $info_2;
1172     # printf "<hr><pre>%s</pre>\n",$ENV{HTTP_HEADER};
1173     print "</body></html>\n";
1174     exit;
1175   }
1176
1177   present_locales('/fup');
1178
1179   if ($ENV{REQUEST_METHOD} eq 'POST') {
1180     pq(qq(
1181       '<font color="red"><h3>'
1182       '  You have to fill out this form completely to continue.'
1183       '</h3></font>'
1184     ));
1185   }
1186
1187   pq(qq(
1188     '<form action="/fup"'
1189     '      method="post"'
1190     '      accept-charset="ISO-8859-1"'
1191     '      enctype="multipart/form-data">'
1192     '  <table>'
1193     '    <tr><td>sender:'
1194     '        <td><input type="text"     name="from" size="40" value="$from"></tr>'
1195     '    <tr><td>auth-ID:'
1196     '        <td><input type="password" name="id"   size="16" value="$id" autocomplete="off"></tr>'
1197     '  </table>'
1198   ));
1199   if ($mail_authid and not ($fop_auth or $nomail)) {
1200 #    pq(qq(
1201 #      'If you enter "?" as your auth-ID then it will be sent by e-mail to you.'
1202 #      '<p>'
1203 #    ));
1204     pq(qq(
1205       '  <input type="checkbox" name="ID_forgotten" value="ID_forgotten">'
1206       '  I have lost my auth-ID! Send it to me by e-mail! '
1207       '  (you must fill out sender field above)'
1208     ));
1209   }
1210   pq(qq(
1211     '  <p><input type="submit" value="check ID and continue"><p>'
1212   ));
1213   if (not $nomail and (
1214     @local_domains and @local_hosts or 
1215     @local_rdomains and @local_rhosts or 
1216     @demo
1217   )) {
1218     pq(qq(
1219       'You can <a href="/fur">register yourself</a> '
1220       'if you do not have a F*EX account yet.<p>'
1221     ));
1222   }
1223   if (@anonymous_upload and ipin($ra,@anonymous_upload)) {
1224     my $a = 'anonymous_'.int(rand(999999));
1225     pq(qq(
1226       'You may also use <a href="/fup?from=anonymous&to=$a">anonymous upload</a>'
1227     ));
1228   }
1229   # if (-f "$docdir/sup.html") {
1230   #  pq(qq(
1231   #    '<br>'
1232   #    'You may also use <a href="/sup.html">simple upload</a>'
1233   #  ));
1234   # }
1235   print "</form>\n";
1236     
1237   print $info_1;
1238
1239   if ($debug and $debug>1) {
1240     print "<hr>\n<pre>\n";
1241     foreach $v (sort keys %ENV) {
1242       print "$v = $ENV{$v}\n";
1243     }
1244     print "</pre>\n";
1245   }
1246   
1247   print "</body></html>\n";
1248   exit;
1249 }
1250
1251 # from sup.html
1252 if ($from and $file and not @to) {
1253   check_rr($from,$from);
1254   @to = ($from);
1255   $sup = 'fexyourself';
1256 }
1257
1258 # all these variables should be defined here, but just to be sure...
1259 http_die("no file specified")       unless $file;
1260 http_die("no sender specified")     unless $from;
1261 http_die("no recipient specified")  unless @to;
1262 unless ($okey and -l "$to/\@OKEY/$okey") {
1263   http_die("no auth-ID specified") unless $id;
1264   unless ($rid eq $id or $gkey or $skey) {
1265     faillog("user $from, id $id");
1266     http_die("wrong auth-ID specified");
1267   }
1268 }
1269
1270 &check_status($from);
1271
1272 if (@throttle) {
1273   foreach (@throttle) {
1274     if (/(.+):(\d+)$/) {
1275       my $throttle = $1;
1276       my $limit = $2;
1277       if (not $bwlimit or $limit < $bwlimit) {
1278         # throttle ip address?
1279         if ($throttle =~ /^[\d.-]+$/) {
1280           if (ipin($ra,$throttle)) {
1281             $bwlimit = $limit;
1282             last;
1283           }
1284         }
1285         # throttle e-mail address?
1286         else {
1287           # allow wildcard *, but not regexps
1288           $throttle =~ quotemeta $throttle;
1289           $throttle =~ s/\*/.*/g;
1290           if ($from =~ /^$throttle$/i) {
1291             $bwlimit = $limit;
1292             last;
1293           }
1294         }
1295       }
1296     }
1297   }
1298 }
1299
1300 # address rewriting for storage (swap sender and recipient), see also fop!
1301 if (not ($skey or $gkey) and $from =~ /^(anonymous|fexmail)/) {
1302   ($from,@to) = ("@to",$from);
1303 }
1304
1305 if (not $anonymous and $overwrite =~ /^n/i) {
1306   foreach $to (@to) {
1307     if (-f "$to/$from/$fkey/data") {
1308       http_die("<code>$file</code> already exists for <code>$to</code>");
1309     }
1310   }
1311 }
1312
1313 # additional last check
1314 unless (@group or $gkey or $skey or $public or $okey) {
1315   foreach $to (@to) {
1316     checkaddress($to) or 
1317       http_die("<code>$to</code> is not a valid e-mail address");
1318   }
1319 }
1320
1321
1322 $to = join(',',@to);
1323
1324 # file overwriting for anonymous is only possible if his client has the 
1325 # download cookie - else request purging
1326 if ($anonymous and not $seek and my $dkey = readlink "$to/$from/$fkey/dkey") {
1327   if ($overwrite =~ /^n/i) {
1328     http_die("<code>$file</code> already exists for <code>$to</code>");
1329   }
1330   if ($ENV{HTTP_COOKIE} !~ /$dkey/) {
1331     my $purge = "/fop/$dkey/$dkey?purge";
1332     # http_die("$file already exists $dkey:$ENV{HTTP_COOKIE}:");
1333     http_die("<code>$file</code> already exists - <a href=\"$purge\">purge it?!</a>");
1334   }
1335 }
1336
1337 if (@group) {
1338   @to = @group;
1339   $comment = "[$group] $comment";
1340 } elsif ($public) {
1341   $comment .= ' (public upload)';
1342 }
1343
1344 # file data still waits on STDIN ... get it now!
1345 &get_file;
1346
1347 if ($to eq $from and $file eq 'ADDRESS_BOOK') {
1348   unlink "$from/\@ADDRESS_BOOK";
1349   rename "$from/$from/ADDRESS_BOOK/upload","$from/\@ADDRESS_BOOK"
1350     or http_die("cannot save $from/\@ADDRESS_BOOK - $!\n");
1351   http_header('200 OK');
1352   print html_header($head);
1353   print "address book updated",
1354         "</body></html>\n";
1355   exit;
1356 }
1357
1358 # finalize upload
1359 unless ($nostore) {
1360   foreach (@group?@group:@to) {
1361     my $to = $_;
1362     $to =~ s/:\w+=.*//; # remove options from address
1363     $filed     = "$to/$from/$fkey";
1364     $save      = "$filed/data";
1365     $upload    = "$filed/upload";
1366     $download  = "$filed/download";
1367     $dkey{$to} = readlink "$filed/dkey";
1368     $overwrite{$to}++ if -f $save and not -f $download;
1369     unlink $save,$download;
1370     rename $upload,$save or http_die("cannot rename $upload to $save - $!\n");
1371     
1372     # log dkey
1373     my $msg = sprintf "%s %s %s %s %s\n",
1374                       isodate(time),$dkey{$to},$from,$to,$fkey;
1375     writelog('dkey.log',$msg);
1376     
1377     # send notification e-mails if necessary
1378     if (not $nomail and (readlink "$to/\@NOTIFICATION"||'') !~ /^no/i
1379         and ($comment or not $overwrite{$to})) {
1380       notify_locale($dkey{$to},'new');
1381       debuglog("notify $filed [$filename] '$comment'");
1382     }
1383   }
1384 }
1385
1386 # send HTTP status
1387 $HTTP_HEADER = 'HTTP/1.1 200 OK';
1388 if ($nostore) {
1389   nvt_print($HTTP_HEADER,'Content-Type: text/html','');
1390   exit if $http_client =~ /^fexsend/;
1391 } elsif ($file eq 'STDFEX') {
1392   nvt_print($HTTP_HEADER,'');
1393   exit;
1394 } else {
1395   nvt_print($HTTP_HEADER);
1396   if ($xkey and not $restricted) {
1397     my $x = "$durl//$xkey";
1398     $x =~ s:/fop::;
1399     nvt_print("X-Location: $x");
1400   }
1401   if ($anonymous) {
1402     my $dkey = $dkey{$to};
1403     my $cookie = $dkey;
1404     $cookie = $1 if $ENV{HTTP_COOKIE} =~ /anonymous=([\w:]+)/;
1405     $cookie .= ':'.$dkey if $cookie !~ /$dkey/;
1406     nvt_print("Set-Cookie: anonymous=$cookie");
1407     $keep{$to} = readlink("$to/\@KEEP")||$keep_default;
1408   }
1409   foreach (@group?@group:@to) {
1410     my $to = $_;
1411     $to =~ s/:\w+=.*//; # remove options from address
1412     my $file = "$to/$from/$fkey";
1413     my $options = sprintf "(autodelete=%s,keep=%s,locale=%s,notification=%s)",
1414       readlink("$file/autodelete")||$autodelete,
1415       readlink("$file/keep")||readlink("$to/\@KEEP")||$keep_default,
1416       readlink("$to/\@LOCALE")||readlink("$file/locale")||$default_locale,
1417       readlink("$to/\@NOTIFICATION")||'full';
1418     nvt_print("X-Recipient: $to $options");
1419     nvt_print("X-Location: $durl/$dkey{$to}/$fkey") unless $restricted;
1420   }
1421   if ($http_client =~ /^(fexsend|schwuppdiwupp)/) {
1422     nvt_print('');
1423     exit;
1424   } else {
1425     nvt_print('Content-Type: text/html','');
1426   }
1427 }
1428
1429 # send HTML report
1430 print html_header($head);
1431
1432 if ($nostore) {
1433   printf "%s (%s MB) received\n",$file,int($ndata/$MB);
1434 } elsif (not $restricted and ($anonymous or $from eq $to)) {
1435   my $size = $ndata<2*1024 ? sprintf "%s B",$ndata:
1436              $ndata<2*$MB   ? sprintf "%s kB",int($ndata/1024):
1437                              sprintf "%s MB",int($ndata/$MB);
1438   pq(qq(
1439     '<code>$file</code> ($size) received and saved<p>'
1440     'Download URL for copy & paste:'
1441     '<h2>$durl/$dkey{$to}/$fkey</h2>'
1442     'Link is valid for $keep{$to} days!<p>'
1443   ));
1444 } else {
1445   if ($ndata<2*1024) {
1446     print "<code>$file</code> ($ndata B) received and saved<p>\n";
1447     if (not $boring and not $seek) {
1448       print "Ehh... $ndata <b>BYTES</b>?! You are kidding?<p>\n";
1449     }
1450   } elsif ($ndata<2*$MB) {
1451     $ndata = int($ndata/1024);
1452     print "<code>$file</code> ($ndata kB) received and saved<p>\n";
1453     if ($ndata<1024 and not ($boring or $seek)) {
1454       print "Using F*EX for less than 1 MB: ",
1455         "ever heard of MIME e-mail? &#9786;<p>\n";
1456     }
1457   } else {
1458     $ndata = int($ndata/$MB);
1459     print "<code>$file</code> ($ndata MB) received and saved<p>\n";
1460   }
1461   print "<ul>\n";
1462   foreach $to (@to) {
1463     print "<li>";
1464     if ($nomail or $nomail{$to}) {
1465       if ($restricted) {
1466         rmrf("$to/$from/$fkey");
1467         print "<code>$file</code> removed because you are a restricted user ".
1468               "and recipient $to cannot receive e-mail<p>\n";
1469       } else {
1470         pq(qq(
1471           '$to cannot receive e-mail &rarr;'
1472           '<h3><font color="red">'
1473           '  No notification e-mail has been sent to $to!'
1474           '</font></h3>'
1475           'Download URL for copy & paste:'
1476         ));
1477         if ($xkey) {
1478           my $x = "$durl{$to}//$xkey";
1479           $x =~ s:/fop::;
1480           print "<h2><code>$x</code></h2>\n";
1481         } else {
1482           print "<h2>$durl/$dkey{$to}/$fkey</h2>\n";
1483           print "Link is valid for $keep{$to} days!<p>\n";
1484         }
1485       }
1486     } elsif ($overwrite{$to} and not $comment) { 
1487       print "(old <code>$file</code> for $to overwritten)<p>\n" 
1488     } else { 
1489       print "$to notified<p>\n"
1490     }
1491   }
1492   print "</ul>\n";
1493 }
1494
1495 if ($okey) {
1496   unlink "$to/\@OKEY/$okey";
1497 } elsif (not $anonymous and not $sup) {
1498   print "<a href=\"/fup?submit=again";
1499   if    ($public) { print "&from=$from&to=$to&id=$id" }
1500   elsif ($skey)   { print "&skey=$skey" }
1501   elsif ($gkey)   { print "&gkey=$gkey" }
1502   elsif ($akey)   { print "&akey=$akey&to=$to" }
1503   print "&bwlimit=$bwlimit&autodelete=$autodelete&keep=$keep\">";
1504   print "send another file</a>\n";
1505   if ($http_client !~ /fexsend/ and $http_client =~ /Linux/i) {
1506     print qq'<p>Hi Linux-user, try <a href="/FAQ/user.html#Why_should_I_use_a_special_F_EX_client">fexsend</a>! &#9786;<p>\n';
1507   }
1508   print &logout;
1509 }
1510
1511 print "</body></html>\n";
1512 exit;
1513
1514
1515 # parse GET and POST requests
1516 sub parse_request {
1517   my %to;
1518   my ($to,$dkey);
1519   my ($x,$k,$v);
1520   my $qs = $ENV{QUERY_STRING};
1521   local $_;
1522
1523   # get JUP parameters from environment (HTTP headers)
1524   while (($k,$v) = each %ENV) {
1525     if ($k =~ s/^FEX_//) {
1526       setparam($k,$v);
1527     }
1528   }
1529   
1530   # decode base64 PATH_INFO to QUERY_STRING
1531   if ($ENV{PATH_INFO} =~ m:^/(\w+=*)$:) {
1532     if ($qs) {
1533       $qs = sprintf("%s&%s",decode_b64($1),$qs);
1534     } else {
1535       $qs = decode_b64($1);
1536     }
1537   }
1538
1539   # parse HTTP QUERY_STRING (parameter=value pairs)
1540   if ($qs) {
1541     foreach (split '&',$qs) {
1542       if (s/^(\w+)=//) {
1543         my $x = $1;
1544         # decode URL-encoding
1545         s/%([a-f0-9]{2})/chr(hex($1))/gie;
1546         setparam($x,$_); 
1547       }
1548     }
1549   }
1550
1551   # HTTP redirect does not work correctly with opera!
1552   # ==> locale handling is now done by fexsrv
1553   if (0 and $locale) {
1554     nvt_print(
1555       "HTTP/1.1 302 Found",
1556       "Location: $ENV{PROTO}://$ENV{HTTP_HOST}/fup",
1557       "Set-Cookie: locale=$locale",
1558       'Expires: 0',
1559       'Content-Length: 0',
1560       ''
1561     );
1562     &reexec;
1563   }
1564   
1565   if ($showstatus) {
1566     &showstatus;
1567     exit;
1568   }
1569   
1570   # check for akey, gkey and skey (from HTTP GET)
1571   &check_keys;
1572   
1573   if ($ENV{REQUEST_METHOD} eq 'POST' and $cl) {
1574     foreach $sig (keys %SIG) {
1575       if ($sig !~ /^(CHLD|CLD)$/) {
1576         $SIG{$sig} = \&sigexit;
1577       }
1578     }
1579     $SIG{PIPE} = 'IGNORE' if $ENV{PROTO} eq 'https'; # stunnel workaround
1580     $SIG{__DIE__} = \&sigdie;
1581     http_die("invalid Content-Length header \"$cl\"") if $cl !~ /^-?\d+$/;
1582     debuglog($0);
1583     debuglog(sprintf("awaiting %d bytes from %s %s",
1584                      $cl,$ENV{REMOTE_ADDR}||'',$ENV{REMOTE_HOST}||''),"\n");
1585
1586     &check_space($cl) if $cl > 0;
1587     
1588     $SIG{ALRM} = sub { die "TIMEOUT\n" };
1589     alarm($timeout);
1590     binmode(STDIN,':raw');
1591     
1592     if (defined($ENV{FEX_FILENAME})) {
1593       # JUP via HTTP header
1594       $file = $param{'FILE'} = $ENV{FEX_FILENAME};
1595       $fileid = $ENV{FEX_FILEID} || 0;
1596       $fpsize = $ENV{X_CONTENT_LENGTH} || 0;
1597       $boundary = '';
1598     } elsif ($contentlength) {
1599       # JUP via URL parameter
1600       $fpsize = $contentlength;
1601       $boundary = '';
1602     } else {
1603       # FUP
1604       if ($ENV{CONTENT_TYPE} =~ /boundary=\"?([\w\-\+\/_]+)/) {
1605         $boundary = $1;
1606       } else {
1607         http_die("malformed HTTP POST (no boundary found)");
1608       }
1609     
1610       READPOST: while (&nvt_read) {
1611         # the file itself - *must* be last part of POST!
1612         if (/^Content-Disposition:\s*form-data;\s*name="file";\s*filename="(.+)"/i) {
1613           push @header,$_;
1614           $file = $param{'FILE'} = $1;
1615           while (&nvt_read) {
1616             last if /^\s*$/;
1617             $fileid = $1 if /^X-File-ID:\s*(.+)/;
1618             $fpsize = $1 if /^Content-Length:\s*(\d+)/;
1619             $flink  = $1 if /^Content-Location:\s*(\/.+)/;
1620             push @header,$_;
1621           }
1622           # STDIN is now at begin of file, will be read later with get_file()
1623           last; 
1624         }
1625         # all other parameters
1626         if (/^Content-Disposition:\s*form-data;\s*name="([a-z]\w*)"/i) {
1627           my $x = $1;
1628           nvt_skip_to('^\s*$');
1629           &nvt_read;
1630           setparam($x,$_);
1631           NEXTPART: while (&nvt_read) {
1632             last READPOST if /^--\Q$boundary--/;
1633             last NEXTPART if /^--\Q$boundary/;
1634           }
1635         }
1636       }
1637     }
1638     
1639     if (length($file)) {
1640       $file =~ s/%(\d+)/chr($1)/ge;
1641       $file = untaint(strip_path(normalize($file)));
1642       $file =~ s/[\\\/<>]/_/g; # filter out dangerous chars
1643       $file =~ s/^\|//;        # filter out dangerous chars
1644       $file =~ s/\|$//;        # filter out dangerous chars
1645       $filename = $file;
1646       $fkey = urlencode($file);
1647     }
1648
1649     # check for akey, gkey and skey (from HTTP POST)
1650     &check_keys;
1651
1652   }
1653
1654   if ($from) {
1655     unless ($skey or $gkey or $okey) {
1656       $from .= '@'.$mdomain if $mdomain and $from !~ /@/;
1657       if ($from ne 'anonymous' and not checkaddress($from)) {
1658         http_die("<code>$from</code> is not a valid e-mail address");
1659       }
1660     }
1661     $from = untaint($from);
1662   }
1663
1664   # collect multiple addresses and check for aliases (not group)
1665   if (@to and "@to" !~ /^@[\w-]+$/ 
1666       and not ($gkey or $addto or $command =~ /^LIST(RECEIVED)?$/)) 
1667   {
1668         
1669     # read address book
1670     if ($from and open my $AB,'<',"$from/\@ADDRESS_BOOK") {
1671       my ($alias,$address,$autodelete,$locale,$keep);
1672       while (<$AB>) {
1673         s/#.*//;
1674         $_ = lc $_;
1675         if (s/^\s*(\S+)[=\s]+(\S+)//) {
1676           ($alias,$address) = ($1,$2);
1677           $autodelete = $locale = $keep = '';
1678           $autodelete = $1 if /autodelete=(\w+)/;
1679           $locale     = $1 if /locale=(\w+)/;
1680           $keep       = $1 if /keep=(\d+)/;
1681           foreach my $address (split(",",$address)) {
1682             $address .= '@'.$mdomain if $mdomain and $address !~ /@/;
1683             push @{$ab{$alias}},$address;
1684             $autodelete{$alias} = $autodelete;
1685             $keep{$alias}       = $keep;
1686             $locale{$alias}     = $locale;
1687           }
1688         }
1689       }
1690       close $AB;
1691     }
1692
1693     # look for recipient's options and eliminate dupes
1694     %to = ();
1695     foreach my $to (my @loop = @to) {
1696      # address book alias?
1697       if ($to !~ /@/ and $ab{$to}) {
1698         foreach my $address (my @loop = @{$ab{$to}}) {
1699           $address .= '@'.$mdomain if $mdomain and $address !~ /@/;
1700           $to{$address} = $address; # ignore dupes
1701           if ($specific{'autodelete'}) {
1702             $autodelete{$address} = $specific{'autodelete'};
1703           } elsif ($autodelete{$to}) {
1704             $autodelete{$address} = $autodelete{$to};
1705           } else {
1706             $autodelete{$address} = readlink "$address/\@AUTODELETE" 
1707                                     || $autodelete;
1708           }
1709           if (my $locale = readlink "$address/\@LOCALE") {
1710             $locale{$address} = $locale;
1711           } elsif ($locale{$to}) {
1712             $locale{$address} = $locale{$to};
1713           } else {
1714             $locale{$address} = $locale ;
1715           }
1716           unless ($locale{$address}) {
1717             $locale{$address} = $default_locale || 'english';
1718           }
1719           if ($specific{'keep'}) { $keep{$address} = $specific{'keep'} }
1720           elsif ($keep{$to})     { $keep{$address} = $keep{$to} }
1721         }
1722       } else {
1723         $to = expand($to);
1724         $to{$to} = $to; # ignore dupes
1725         unless ($autodelete{$to}) {
1726           $autodelete{$to} = readlink "$to/\@AUTODELETE" || $autodelete;
1727         }
1728         $autodelete{$to} = $specific{'autodelete'}  if $specific{'autodelete'};
1729         $keep{$to} = $keep_default;
1730         $keep{$to} = $keep                          if $keep;
1731         $keep{$to} = untaint(readlink "$to/\@KEEP") if -l "$to/\@KEEP";
1732         $keep{$to} = $specific{'keep'}              if $specific{'keep'};
1733         # recipient specific parameters
1734         $keep{$to}       = $1 if $to =~ /:keep=(\d+)/i;
1735         $autodelete{$to} = $1 if $to =~ /:autodelete=(\w+)/i;
1736       }
1737       $autodelete{$to} = 'NO' if $to =~ /$amdl/; # mailing lists, etc
1738       if (-e "$to/\@CAPTIVE") {
1739         my $v;
1740         $v = readlink "$to/\@AUTODELETE" and $autodelete{$to} = $v;
1741         $v = readlink "$to/\@KEEP"       and $keep{$to}       = $v;
1742       }
1743     }
1744     @to = keys %to;
1745     
1746     if (scalar(@to) == 1) {
1747       $to = "@to";        
1748       $keep       = $keep{$to}       if $keep{$to};
1749       $autodelete = $autodelete{$to} if $autodelete{$to};
1750     }
1751         
1752     # check recipients and eliminate dupes
1753     %to = ();
1754     foreach $to (@to) {
1755       if ($to eq 'anonymous') {
1756         $to{$to} = $to;
1757       } else {
1758         if ($to =~ /^@(.+)/) {
1759           http_die("You cannot send to more than one group") if @to > 1;
1760           http_die("Group <code>$to</code> does not exist") unless -f "$from/\@GROUP/$1";
1761         } else {
1762           if ($skey or $gkey or $okey or checkaddress($to)) {
1763             $to .= '@'.$mdomain if $mdomain and $to !~ /@/;
1764             $to{$to} = untaint($to);
1765           } else {
1766             http_die("<code>$to</code> is not a valid e-mail address");
1767           }
1768         }
1769       }
1770     }
1771     @to = values %to;
1772   }
1773
1774   foreach $to (@to) {
1775     unless (checkforbidden($to)) {
1776       http_die("<code>$to</code> is not allowed");
1777     }
1778   }
1779 }
1780
1781
1782 # show the status progress bar
1783 sub showstatus {
1784   my $wclose;
1785   my ($upload,$data,$sfile,$ukey,$file);
1786   my ($nsize,$tsize);
1787   my ($t0,$t1,$t2,$tt,$ts,$tm);
1788   my ($osize,$percent,$npercent);
1789   local $_;
1790   
1791   $wclose = '<p><a href="#" onclick="window.close()">close</a>'."\n".
1792             '</body></html>'."\n";
1793   $ukey   = "$ukeydir/$uid";
1794   $upload = "$ukey/upload";
1795   $data   = "$ukey/data";
1796   $sfile  = "$ukey/size";
1797   for (1..$timeout) {
1798     sleep 1;
1799     $tsize = readlink $sfile and last;
1800     # upload error?
1801     # remark: stupid Internet Explorer *needs* the error represented in this 
1802     # asynchronous popup window, because it cannot display the error in the
1803     # main window on HTTP POST!
1804     if (-f $ukey and open $ukey,'<',$ukey or 
1805         -f "$ukey/error" and open $ukey,'<',"$ukey/error") {
1806       undef $/;
1807       unlink $ukey;
1808       html_error($error,<$ukey> || 'unknown');
1809     }
1810   }
1811   # unlink $sfile;
1812   
1813   if (defined $tsize and $tsize == 0) {
1814     print "<script type='text/javascript'>window.close()</script>\n";
1815     exit;
1816   }
1817   unless ($tsize) {
1818     html_error($error,
1819                "no file data received - does your file exist or is it >2GB?")
1820   }
1821   html_error($error,"file size unknown") unless $tsize =~ /^\d+$/;
1822   
1823   http_header('200 OK');
1824   if (open $ukey,'<',"$ukey/filename") {
1825     local $/;
1826     $file = <$ukey>;
1827     close $ukey;
1828   }
1829   http_die("no filename?!") unless $file;
1830   
1831   my $ssize = $tsize;
1832   if ($ssize<2097152) {
1833     $ssize = sprintf "%d kB",int($ssize/1024);
1834   } else {
1835     $ssize = sprintf "%d MB",int($ssize/1048576);
1836   }
1837   
1838   pq(qq(
1839     "<html><body>"
1840     "<center>"
1841     "<h1>Upload Status for<br><code>$file ($ssize)</code></h1>"
1842     '<img src="/action-fex-camel.gif" id="afc">'
1843     "</center>"
1844     "<input type='text' id='percent' style='margin-left:1ex;color:black;background:transparent;border:none;width:32ex;' disabled='true' value='0%'>"
1845     "<div style='border:1px solid black;width:100%;height:20px;'>"
1846     "<div style='float:left;width:0%;background:black;height:20px;' id='bar'>"
1847     "</div></div>"
1848   ));
1849     
1850   # wait for upload file
1851   for (1..9) {
1852     last if -f $upload or -f $data;
1853     sleep 1;
1854   }
1855   unless (-f $upload or -f $data) {
1856     print "<p><H3>ERROR: no upload received</H3>\n";
1857     print $wclose;
1858     exit;
1859   }
1860   
1861   $SIG{ALRM} = sub { die "TIMEOUT in showstatus: no (more) data received\n" };
1862   alarm($timeout*2);
1863   
1864   $t0 = $t1 = time;
1865   $osize = $percent = $npercent = 0;
1866   
1867   for ($percent = 0; $percent<100; sleep(1)) {
1868     $t2 = time;
1869     $nsize = -s $upload;
1870     if (defined $nsize) {
1871       if ($nsize<$osize) {
1872         print "<p><h3>ABORTED</h3>\n";
1873         print $wclose;
1874         exit;
1875       }
1876       if ($nsize>$osize) {
1877         alarm($timeout*2);
1878         $osize = $nsize;
1879       }
1880       $npercent = int($nsize*100/$tsize);
1881       $showsize = calcsize($tsize,$nsize);
1882     } else {
1883       $npercent = 100;
1884       $showsize = calcsize($tsize,$tsize);
1885     }
1886     # hint: for ISDN (or even slower) links, 5 s tcp delay is minimum
1887     # so, updating more often is contra-productive
1888     if ($t2>$t1+5 or $npercent>$percent) {
1889       $percent = $npercent;
1890       $t1 = $t2; 
1891       $tm = int(($t2-$t0)/60);
1892       $ts = $t2-$t0-$tm*60;
1893       $tt = sprintf("%d:%02d",$tm,$ts);
1894       pq(qq(
1895         "<script type='text/javascript'>"
1896         "  document.getElementById('bar').style.width = '$percent%';"
1897         "  document.getElementById('percent').value = '$showsize, $tt, $percent %';"
1898         "</script>"
1899       )) or last;
1900     }
1901   }
1902   
1903   alarm(0);
1904   if ($npercent == 100) {
1905     print "<h3>file successfully transferred</h3>\n";
1906   } else {
1907     print "<h3>file transfer aborted</h3>\n";
1908   }
1909   pq(qq(
1910     "<script type='text/javascript'>"
1911     "  document.getElementById('afc').src='/logo.jpg'"
1912     "</script>"
1913   ));
1914   print $wclose;
1915   unlink $ukey;
1916   exit;
1917 }
1918
1919
1920 # get file from post request
1921 sub get_file {
1922   my ($to,$filed,$upload,$nupload,$speed,$download);
1923   my ($b,$n,$uss);
1924   my $dkey;
1925   my ($fh,$filesize);
1926   my ($t0,$tt);
1927   my $fb = 0;           # file bytes
1928   my $ebl = 0;          # end boundary length
1929
1930   # FUP, not JUP
1931   if ($boundary) {
1932     $ebl = length($boundary)+8; # 8: 2 * CRLF + 2 * "--"
1933   }
1934
1935   unless ($nostore) {
1936
1937     # download already in progress?
1938     foreach $to (@to) {
1939       $to =~ s/:\w+=.*//; # remove options from address
1940       $filed = "$to/$from/$fkey";
1941       $download = "$filed/download";
1942       if (-f $download and open $download,'>>',$download) {
1943         flock($download,LOCK_EX|LOCK_NB) or
1944           http_die("<code>$filed</code> locked: a download is currently in progress");
1945       }
1946     }
1947     
1948     # prepare upload
1949     foreach $to (@to) {
1950       $to =~ s/:\w+=.*//; # remove options from address
1951       $filed = "$to/$from/$fkey";
1952       $nupload = "$filed/upload"; # upload for next recipient
1953       mkdirp($filed);
1954       
1955       # upload already prepared (for first recipient)?
1956       if ($upload) {
1957         # link upload for next recipient
1958         unless ($upload eq $nupload or
1959                 -r $upload and -r $nupload and
1960                 (stat $upload)[1] == (stat $nupload)[1]) 
1961         {
1962           unlink $nupload;
1963           link $upload,$nupload;
1964         }
1965       } 
1966       
1967       # first recipient => create upload
1968       else {
1969         $upload = $nupload;
1970         unlink "$ukeydir/$uid";
1971         if ($flink) {
1972           if ($seek) {
1973             http_die("cannot resume on link upload");
1974           }
1975           &nvt_read and $flink = $_;
1976           if ($flink !~ /^\//) {
1977             http_die("no file link name ($flink)");
1978           }
1979           $flink = abs_path($flink);
1980           my $fok;
1981           foreach (@file_link_dirs) {
1982             my $dir = abs_path($_);
1983             $fok = $flink if $flink =~ /^\Q$dir\//;
1984           }
1985           unless ($fok) {
1986             http_die("<code>$flink</code> not allowed for linking");
1987           }
1988           my @s = stat($flink);
1989           unless (@s and ($s[2] & S_IROTH) and -r $flink) {
1990             http_die("cannot read <code>$flink</code>");
1991           }
1992           unless (-f $flink and not -l $flink) {
1993             http_die("<code>$flink</code> is not a regular file");
1994           }
1995           # http_die("DEBUG: flink = $flink");
1996           &nvt_read;
1997           &nvt_read if /^$/;
1998           unless (/^--\Q$boundary--/) {
1999             http_die("found no MIME end boundary in upload ($_)");
2000           }
2001           unlink $upload;
2002           symlink untaint($flink),$upload;
2003         } else {
2004           unlink $upload if -l $upload;
2005           open $upload,'>>',$upload or http_die("cannot write $upload - $!");
2006           flock($upload,LOCK_EX|LOCK_NB) or
2007             http_die("<code>$file</code> locked: a transfer is already in progress");
2008           unless ($seek) {
2009             seek $upload,0,0;
2010             truncate $upload,0;
2011           }
2012           # already uploaded file data size
2013           $uss = -s $upload;
2014           # provide upload ID symlink for showstatus
2015           symlink "../$filed","$ukeydir/$uid";
2016         }
2017       }
2018       
2019       unlink "$filed/autodelete",
2020              "$filed/error",
2021              "$filed/restrictions",
2022              "$filed/locale",
2023              "$filed/keep",
2024              "$filed/header",
2025              "$filed/id",
2026              "$filed/ip",
2027              "$filed/speed",
2028              "$filed/replyto",
2029              "$filed/useragent",
2030              "$filed/uurl",
2031              "$filed/comment",
2032              "$filed/notify";
2033       unlink "$filed/size" unless $seek;
2034     
2035       # showstatus needs file name and size
2036       # fexsend needs full file size (+$seek)
2037       $fh = "$filed/filename";
2038       open $fh,'>',$fh or die "cannot write $fh - $!\n";
2039       print {$fh} $filename;
2040       close $fh;
2041       if ($::filesize > 0 or $cl > 0) {
2042         if ($::filesize > 0) { $filesize = $fpsize || $::filesize }
2043         else                 { $filesize = $cl-$RB-$ebl+$seek }
2044         # new file
2045         unless ($seek) {
2046           if ($::filesize > 0) {
2047             # total file size as reported by POST
2048             mksymlink("$filed/size",$::filesize) 
2049               or die "cannot write $filed/size - $!\n";
2050           } else {
2051             # file size as counted
2052             mksymlink("$filed/size",$filesize) 
2053               or die "cannot write $filed/size - $!\n";
2054           }
2055         }
2056       }
2057     
2058       if ($from eq "@to") {
2059         # special "fex yourself"
2060         mksymlink("$filed/autodelete",'NO');
2061       } else {
2062         $autodelete{$to} = $autodelete unless $autodelete{$to};
2063         if ($autodelete{$to} =~ /^(DELAY|NO|\d+)$/i) {
2064           mksymlink("$filed/autodelete",$autodelete{$to});
2065         }
2066       }
2067
2068       if (my $keep = $keep{$to} || $::keep) {
2069         mksymlink("$filed/keep",$keep);
2070       }
2071       mksymlink("$filed/id",$fileid) if $fileid;
2072       mksymlink("$filed/ip",$ra)     if $ra;
2073       if (my $uurl = $ENV{REQUEST_URL}) {
2074         mksymlink("$filed/uurl",$uurl);
2075       }
2076       if ($http_client and open $http_client,'>',"$filed/useragent") {
2077         print {$http_client} $http_client,"\n";
2078         close $http_client;
2079       }
2080       if ($_ = readlink "$to/\@LOCALE") {
2081         # mksymlink("$filed/locale",$_);
2082       } elsif ($locale{$to}) {
2083         mksymlink("$filed/locale",$locale{$to});
2084       } elsif ($locale and $locale ne $default_locale) {
2085         mksymlink("$filed/locale",$locale);
2086       }
2087       if ($replyto and $replyto =~ /.@./) {
2088         mksymlink("$filed/replyto",$replyto);
2089       }
2090     
2091       my $arh = "$from/\@ALLOWED_RHOSTS";
2092       if (-s $arh) {
2093         copy($arh,"$filed/restrictions");
2094       }
2095       
2096       if (@header and open $fh,'>',"$filed/header") {
2097         print {$fh} join("\n",@header),"\n";
2098         close $fh;
2099       }
2100     
2101       if ((readlink "$to/\@NOTIFICATION"||'') =~ /^no/i) {
2102         $nomail{$to} = 'NOTIFICATION';
2103       }
2104
2105       if ($nomail) {
2106         open $fh,'>',"$filed/notify" and close $fh;
2107       } 
2108       if ($comment) {
2109         if (open $fh,'>',"$filed/comment") {
2110           print {$fh} encode_utf8($comment);
2111           close $fh;
2112         }
2113       }
2114
2115       # provide download ID key
2116       unless ($dkey = readlink("$filed/dkey") and -l "$dkeydir/$dkey") {
2117         $dkey = randstring(8);
2118         unlink "$dkeydir/$dkey";
2119         symlink "../$filed","$dkeydir/$dkey" 
2120           or http_die("cannot symlink $dkeydir/$dkey ($!)");
2121         unlink "$filed/dkey";
2122         symlink $dkey,"$filed/dkey";
2123       }
2124     
2125     }
2126
2127     # extra download (XKEY)?
2128     if ($anonymous and $fkey =~ /^afex_\d/ or
2129         $from eq "@to" and $comment =~ s:^//(.*)$:NOMAIL:) 
2130     {
2131       $xkey = $1||$fkey;
2132       $nomail = $comment;
2133       my $x = "$xkeydir/$xkey";
2134       unless (-l $x and readlink($x) eq "../$from/$from/$fkey") {
2135         if (-e $x) {
2136           http_die("extra download key $xkey already exists");
2137         }
2138         symlink "../$from/$from/$fkey",$x 
2139           or http_die("cannot symlink $x - $!\n");
2140         unlink "$x/xkey";
2141         symlink $xkey,"$x/xkey";
2142       }
2143     }
2144     
2145   }
2146   
2147   # file link?
2148   if ($flink) {
2149     # upload link has been already created, no data to read any more
2150     $to = join(',',@to);
2151     fuplog($to,$fkey,0);
2152     debuglog("upload link successfull, dkey=$dkey");
2153   }
2154
2155   # regular file
2156   else {
2157
2158     # at last, read (real) file data
2159     $t0 = time();
2160   
2161     # streaming data?
2162     if ($cl == -1) {
2163       alarm($timeout*2);
2164       # read until EOF, including MIME end boundary
2165       # note: cannot use sysread because of previous buffered read!
2166       while ($n = read(STDIN,$_,$bs)) {
2167         $RB += $n;
2168         $fb += $n;
2169         syswrite $upload,$_ unless $nostore;
2170         alarm($timeout*2);
2171       }
2172       # size of transferred file, without end boundary
2173       $ndata = untaint($fb-$ebl);
2174     } 
2175     
2176     # normal file with known file size
2177     else {
2178       
2179       if ($fpsize) {
2180         debuglog(sprintf("still awaiting %d+%d = %d bytes",
2181                  $fpsize,$ebl,$fpsize+$ebl));
2182         $cl = $RB+$fpsize+$ebl; # recalculate CONTENT_LENGTH
2183       } else {
2184         if ($::filesize) {
2185           $cl = $RB+$::filesize+$ebl; # recalculate CONTENT_LENGTH
2186         }
2187         debuglog(sprintf("still awaiting %d-%d = %d bytes",
2188                          $cl,$RB,$cl-$RB));
2189       }
2190       # read until end boundary, not EOF
2191       while ($RB < $cl-$ebl) {
2192         $b = $cl-$ebl-$RB; 
2193         $b = $bs if $b > $bs;
2194         # max wait for 1 kB/s, but at least 10 s
2195         # $timeout = $b/1024;
2196         # $timeout = 10 if $timeout < 10;
2197         alarm($timeout);
2198         if ($n = read(STDIN,$_,$b)) {
2199           $RB += $n;
2200           $fb += $n;
2201           # syswrite is much faster than print
2202           syswrite $upload,$_ unless $nostore;
2203           if ($bwlimit) {
2204             alarm(0);
2205             $tt = (time-$t0) || 1;
2206             while ($RB/$tt/1024 > $bwlimit) {
2207               sleep 1;
2208               $tt = time-$t0;
2209             }
2210           }
2211           # debuglog($_);
2212         } else {
2213           last;
2214         }
2215       }
2216       # read end boundary - F*IX is broken!
2217       if ($ebl and $http_client !~ /F\*IX/) {
2218         $_ = <STDIN>;
2219         $_ = <STDIN>||'';
2220         unless (/^--\Q$boundary--/) {
2221           http_die("found no MIME end boundary in upload ($_)");
2222         }
2223       }
2224       $RB += $ebl;
2225       $ndata = untaint($fb);
2226     } 
2227
2228     alarm(0);
2229   
2230     unless ($nostore) {
2231       close $upload; # or die "cannot close $upload - $!\n";;
2232   
2233       # throuput in kB/s
2234       $tt = (time-$t0) || 1;
2235       mksymlink("$filed/speed",int($fb/1024/$tt));
2236       
2237       unless ($ndata) {
2238         http_die(
2239           "No file data received!".
2240           " File name correct?".
2241           " File too big (browser-limit: 2 GB!)?"
2242         );
2243       }
2244       
2245       $to = join(',',@to);
2246     
2247       # streaming upload?
2248       if ($cl == -1) {
2249       
2250         open $upload,'<',$upload or http_die("internal error - cannot read upload");
2251         seek $upload,$ndata+2,0;
2252         $_ = <$upload>||'';
2253         unless (/^--\Q$boundary--/) {
2254           http_die("found no MIME end boundary in upload ($_)");
2255         }
2256         close $upload;
2257         truncate $upload,$ndata;
2258         
2259       } else {
2260       
2261         # truncate boundary string
2262         # truncate $upload,$ndata+$uss if -s $upload > $ndata+$uss;
2263       
2264         # incomplete?
2265         if ($cl != $RB) {
2266           fuplog($to,$fkey,$ndata,'(aborted)');
2267           if ($fpsize) {
2268             http_die("read $RB bytes, but Content-Length announces $fpsize bytes");
2269           } else {
2270             http_die("read $RB bytes, but CONTENT_LENGTH announces $cl bytes");
2271           }
2272         }
2273       
2274         # multipost, not complete
2275         if ($::filesize > -s $upload) {
2276           http_header('206 Partial OK');
2277           exit;
2278         }
2279       
2280         # save error?
2281         if (-s $upload > ($::filesize||$filesize)) {
2282           fuplog($to,$fkey,$ndata,'(write error: upload > filesize)');
2283           http_die("internal server error while writing file data");
2284         }
2285       
2286       }
2287       fuplog($to,$fkey,$ndata);
2288       debuglog("upload successfull, dkey=$dkey");
2289     }
2290   }
2291 }
2292
2293
2294 # check recipients restriction
2295 sub check_rr {
2296   my $from = shift;
2297   my @to = @_;
2298   my $rr = "$from/\@ALLOWED_RECIPIENTS";
2299   my ($allowed,$to,$ar,$rd);
2300   
2301   if (-s $rr and open $rr,'<',$rr) {
2302
2303     $restricted = $rr;
2304
2305     foreach (@to) {
2306       my $to = $_;
2307       $allowed = 0;
2308       seek $rr,0,0;
2309       while (<$rr>) {
2310         chomp;
2311         s/#.*//;
2312         s/\s//g;
2313         
2314         if (/^\@LOCAL_RDOMAINS/) {
2315           $ar = '(@';
2316           foreach (@local_rdomains) {
2317             my $rd = $_;
2318             # allow wildcard *, but not regexps
2319             $rd =~ s/\./\\./g;
2320             $rd =~ s/\*/[\\w.-]+/g;
2321             $ar .= '|[^\@]+\@' . $rd;
2322           }
2323           $ar .= ')';
2324         } else {
2325           # allow wildcard *, but not regexps
2326           $ar = quotemeta $_;
2327           $ar =~ s/\\\*/[^@]*/g;
2328         }
2329         
2330         if ($to =~ /^$ar$/i) {
2331           $allowed = 1;
2332           last;
2333         }
2334         
2335       }
2336       
2337       unless ($allowed) {
2338         fuplog("ERROR: $from not allowed to fex to $to");
2339         debuglog("$to not in $spooldir/$from/\@ALLOWED_RECIPIENTS");
2340         http_die("You ($from) are not allowed to fex to $to");
2341       }
2342     }
2343     
2344     close $rr;
2345   }
2346 }
2347
2348
2349 # add domain to user if necessary
2350 sub expand {
2351   my @users = @_;
2352   my @ua;
2353   
2354   foreach my $u (my @loop = @users) {
2355     if ($u =~ /^anonymous(_\d+)?$/) { 
2356       $u = "$u\@$hostname";
2357     }
2358     if ($u eq 'nettest') { 
2359       if ($mdomain and -d "$u\@$mdomain") {
2360         $u .= "\@$mdomain"
2361       } elsif (-d "$u\@$hostname") {
2362         $u .= "\@$hostname"    
2363       }
2364     }
2365     if    ($u =~ /@/)          { push @ua,$u } 
2366     elsif ($mdomain)           { push @ua,"$u\@$mdomain" } 
2367     elsif (-d "$u\@$hostname") { push @ua,"$u\@$hostname" } 
2368     else                       { push @ua,$u }
2369   }
2370   
2371   return wantarray ? @ua : join(',',@ua);
2372 }
2373
2374
2375 # forward-copy (bounce) an already uploaded file
2376 sub forward {
2377   my $file = shift;
2378   my ($nfile,$to,$AB);
2379   my ($filename);
2380   my (%to);
2381
2382   http_die("no file data for <code>$file</code>") unless -f "$file/data";
2383
2384   if (@to) {
2385
2386     # check recipients restriction
2387     check_rr($from,@to);
2388
2389     # read aliases from address book
2390     if (open $AB,'<',"$from/\@ADDRESS_BOOK") {
2391       while (<$AB>) {
2392         s/#.*//;
2393         $_ = lc $_;
2394         if (s/^\s*(\S+)[=\s]+(\S+)//) {
2395           my ($alias,$address) = ($1,$2);
2396           foreach my $address (split(",",$address)) {
2397             $address .= '@'.$mdomain if $mdomain and $address !~ /@/;
2398             push @{$ab{$alias}},$address;
2399           }
2400         }
2401       }
2402       close $AB;
2403     }
2404
2405     # collect addresses
2406     foreach my $to (my @loop = @to) {
2407       if ($ab{$to}) {
2408         foreach my $address (@{$ab{$to}}) {
2409           $to{$address} = $address;
2410         }
2411       } else {
2412         $to .= '@'.$mdomain if $mdomain and $to !~ /@/;
2413         $to{$to} = $to;
2414       }
2415     }
2416
2417     http_header('200 OK');
2418     print html_header($head);
2419
2420     @to = keys %to;
2421     
2422     foreach my $to (my @loop = @to) {
2423       $to =~ s/:\w+=.*//; # remove options from address
2424       $nfile = $file;
2425       $nfile =~ s:.*?/:$to/:;
2426       next if $nfile eq $file;
2427       mkdirp($nfile);
2428       http_die("cannot create directory $nfile") unless -d $nfile;
2429       unlink "$nfile/data",
2430              "$nfile/upload",
2431              "$nfile/download",
2432              "$nfile/autodelete",
2433              "$nfile/error",
2434              "$nfile/restrictions",
2435              "$nfile/keep",
2436              "$nfile/header",
2437              "$nfile/id",
2438              "$nfile/speed",
2439              "$nfile/comment",
2440              "$nfile/replyto",
2441              "$nfile/notify";
2442       if ($comment) {
2443         open $comment,'>',"$nfile/comment";
2444         print {$comment} $comment;
2445         close $comment;
2446       }
2447       if ($autodelete =~ /^(DELAY|NO|\d+)$/i) {
2448         symlink($autodelete,"$nfile/autodelete");
2449       }
2450       symlink($keep||$keep_default,         "$nfile/keep");
2451                     copy("$file/id",        "$nfile/id");
2452                     copy("$file/ip",        "$nfile/ip");
2453                     copy("$file/speed",     "$nfile/speed");
2454                     copy("$file/replyto",   "$nfile/replyto");
2455       $filename   = copy("$file/filename",  "$nfile/filename");
2456       link               "$file/data",      "$nfile/data"
2457         or die http_die("cannot create $nfile/data - $!");
2458       unless ($dkey = readlink("$nfile/dkey") and -l "$dkeydir/$dkey") {
2459         $dkey = randstring(8);
2460         unlink "$dkeydir/$dkey";
2461         symlink "../$nfile","$dkeydir/$dkey" 
2462           or http_die("cannot symlink $dkeydir/$dkey");
2463         unlink "$nfile/dkey";
2464         symlink $dkey,"$nfile/dkey" 
2465           or http_die("cannot create $nfile/dkey - $!");
2466       }
2467       
2468       if ($nomail or $nomail{$to}) {
2469         if ($filename) {
2470           my $url = "$durl/$dkey/".normalize_filename($filename);
2471           pq(qq(
2472             'Download-URL for $to:<br>'
2473             '<code>$url</code>'
2474             '<p>'
2475           ));
2476         }
2477       } else {
2478         notify_locale($dkey,'new');
2479         fuplog($to,urlencode($filename),"(forwarded)");
2480         if ($filename) {
2481           pq(qq(
2482             'File "$filename" copy-forwarded to $to and notified.'
2483             '<p>'
2484           ));
2485         }
2486       }
2487     }
2488     pq(qq(
2489       '<a href="/foc?akey=$akey">back to F*EX operation control</a>'
2490       '</body></html>'
2491     ));
2492   } else {
2493     $filename = filename($file);
2494     http_header('200 OK');
2495     print html_header($head);
2496     pq(qq(
2497       '<form name="upload"'
2498       '      action="/fup"'
2499       '      method="post"'
2500       '      accept-charset="UTF-8"'
2501       '      enctype="multipart/form-data">'
2502       '  <input type="hidden" name="akey"    value="$akey">'
2503       '  <input type="hidden" name="dkey"    value="$dkey">'
2504       '  <input type="hidden" name="command" value="FORWARD">'
2505       '  forward a copy of "<code>$filename</code>" to:<br>'
2506       '  <input type="text" name="to" size="80">'
2507       '</form>'
2508       '</body></html>'
2509     ));
2510   }
2511 }
2512
2513
2514 # modify file parameter
2515 sub modify {
2516   my $file = shift;
2517   my $filename = filename($file);
2518   my $dkey = readlink "$file/$dkey";
2519   my $to;
2520   my @parameter;
2521
2522   http_die("no file data for <code>$file</code>") unless -f "$file/data";
2523
2524   $to = $file;
2525   $to =~ s:/.*::;
2526   if ($specific{'keep'}) {
2527     mksymlink("$file/keep",$keep);
2528     utime time,time,"$file/filename";
2529     push @parameter,'KEEP';
2530   }
2531   if ($specific{'autodelete'}) {
2532     mksymlink("$file/autodelete",$autodelete);
2533     push @parameter,'AUTODELETE';
2534   }
2535   if ($comment) {
2536     if (open $comment,'>',"$file/comment") {
2537       print {$comment} $comment;
2538       close $comment;
2539     }
2540     notify_locale($dkey,'new');
2541     push @parameter,'COMMENT';
2542   }
2543   http_header('200 OK');
2544   print "Parameter ".join(',',@parameter)." modified for $filename for $to\n";
2545 }
2546
2547
2548 sub calcsize {
2549   my ($tsize,$nsize) = @_;
2550   if ($tsize<2097152) {
2551     return sprintf "%d kB",int($nsize/1024);
2552   } else {
2553     return sprintf "%d MB",int($nsize/1048576);
2554   }
2555 }
2556
2557
2558 # set parameter variables
2559 sub setparam {
2560   my ($v,$vv) = @_;
2561   my ($idf,$to);
2562   
2563   $v = uc(despace($v));
2564
2565 #  if ($vv =~ /([<>])/) {
2566 #    http_die(sprintf("\"&#%s;\" is not allowed in parameter $v",ord($1)));
2567 #  }
2568
2569   $param{$v} = $vv;
2570   if ($v eq 'LOGOUT') {
2571     $logout = $v;
2572     # skey and gkey are persistant!
2573     $akey = $1 if $ENV{QUERY_STRING} =~ /AKEY:(\w+)/i;
2574     unlink "$akeydir/$akey";
2575     $login = $FEXHOME.'/cgi-bin/login';
2576     if (-x $login) {
2577       $login = readlink $login || 'login';
2578       nvt_print(
2579         "HTTP/1.1 302 Found",
2580         "Location: $ENV{PROTO}://$ENV{HTTP_HOST}/$login",
2581         'Content-Length: 0',
2582         ""
2583       );
2584     } else {
2585       nvt_print(
2586         "HTTP/1.1 302 Found",
2587         "Location: $ENV{PROTO}://$ENV{HTTP_HOST}/fup",
2588         'Content-Length: 0',
2589         ""
2590       );
2591     }
2592     &reexec;
2593   } elsif ($v eq 'LOCALE' and $vv =~ /^(\w+)$/) {
2594     $locale = $1;
2595   } elsif ($v eq 'REDIRECT' and $vv =~ /^([\w?=]+)$/) {
2596     $redirect = $1;
2597   } elsif (($v eq 'KEY' or $v eq 'SKEY') and $vv =~ /^([\w:]+)$/) { 
2598     $skey = $1;
2599     $restricted = $v;
2600   } elsif ($v eq 'GKEY' and $vv =~ /^([\w:]+)$/) { 
2601     $gkey = $1 unless $nomail;
2602     $restricted = $v;
2603   } elsif ($v eq 'DKEY' and $vv =~ /^(\w+)$/) { 
2604     $dkey = $1;
2605   } elsif ($v eq 'AKEY' and $vv =~ /^(\w+)$/) { 
2606     $akey = $1;
2607   } elsif ($v eq 'FROM' or $v eq 'USER') { 
2608     $from = normalize_email($vv);
2609     $from = untaint(expand($from));
2610     checkchars('from address',$from);
2611     # maybe FROM=SUBUSER !
2612     # checkaddress($from) or http_die("FROM $from is no legal e-mail address");
2613   } elsif ($v eq 'REPLYTO') { 
2614     $replyto = normalize_email($vv);
2615     checkchars('replyto address',$replyto);
2616     checkaddress($replyto) or 
2617       http_die("REPLYTO $replyto is no legal e-mail address");
2618   } elsif ($v eq 'ADDTO') {
2619     $vv =~ s/\s.*//;
2620     $addto = normalize_email($vv);
2621   } elsif ($v eq 'SUBMIT') {
2622     $submit = decode_utf8(normalize($vv));
2623   } elsif ($v eq 'FEXYOURSELF') {
2624     $submit = $vv;
2625     @to = ($from);
2626   } elsif ($v eq 'TO') {
2627     # extract AUTODELETE and KEEP options
2628     if ($vv =~ s/[\s,]+AUTODELETE=(\w+)//i) {
2629       $specific{'autodelete'} = $autodelete = uc($1);
2630     }
2631     if ($vv =~ s/[\s,]+KEEP=(\d+)//i) {
2632       $keep = $1;
2633       $keep = $keep_max if $keep_max and $keep > $keep_max;
2634       $specific{'keep'} = $keep;
2635     }
2636     $to = normalize(lc($vv));
2637     $to =~ s/[\n\s;,]+/,/g;
2638     if ($from) {
2639       if ($to eq '.') {
2640         $to = $from;
2641       }
2642       if ($to eq '//') {
2643         $to = $from;
2644         $comment = '//';
2645       }
2646     }
2647     checkchars('to address',$to);
2648     push @to,split(',',$to);
2649   } elsif ($v eq 'ID') {
2650     $id = despace($vv);
2651     checkchars('auth-ID',$id);
2652   } elsif ($v eq 'TCE') {
2653     $test = despace($vv);
2654   } elsif ($v eq 'OKEY' and $vv =~ /^(\w+)$/) {
2655     $okey = $1;
2656     $restricted = $v;
2657   } elsif ($v eq 'FILEID' and $vv =~ /^(\w+)$/) {
2658     $fileid = $1;
2659   } elsif ($v eq 'CONTENTLENGTH' and $vv =~ /^(\d+)$/) {
2660     $contentlength = $1;
2661   } elsif ($v eq 'FILE' or $v eq 'FILENAME') {
2662     $file = strip_path(normalize($vv));
2663   } elsif ($v eq 'UID' and $vv =~ /^(\w+)$/) {
2664     $uid = $1;
2665   } elsif ($v eq 'ID_FORGOTTEN') {
2666     $id_forgotten = $vv;
2667   } elsif ($v eq 'SHOWSTATUS' and $vv =~ /^(\w+)$/) {
2668     $showstatus = $uid = $1;
2669   } elsif ($v eq 'COMMENT') {
2670     $comment = decode_utf8(normalize($vv));
2671     $comment =~ s/^\s*!\.!/!SHORTMAIL!/;
2672     $comment =~ s/^!#!/!NOMAIL!/;
2673     $comment =~ s/^!-!/!NOSTORE!/;
2674     $nomail = $comment if $comment =~ /NOMAIL/;
2675     $nostore = $nomail = $comment if $comment =~ /NOSTORE/;
2676     $bcc .= " $from"   if $comment =~ s/\s*!bcc!?\s*//i;
2677     # backward compatibility
2678     foreach my $cmd (qw(
2679       DELETE LIST CHECKQUOTA CHECKRECIPIENT RECEIVEDLOG SENDLOG FOPLOG FORWARD
2680     )) { $command = $comment if $comment eq $cmd }
2681   } elsif ($v eq 'COMMAND') {
2682     $command = normalize($vv);
2683   } elsif ($v eq 'BWLIMIT' and $vv =~ /^(\d+)$/) {
2684     $bwlimit = $1;
2685   } elsif ($v eq 'SEEK' and $vv =~ /^(\d+)$/) {
2686     $seek = $1;
2687   } elsif ($v eq 'FILESIZE' and $vv =~ /^(\d+)$/) {
2688     $filesize = $1; # complete filesize! 
2689     &check_space($filesize-$seek);
2690   } elsif ($v eq 'AUTODELETE' and $vv =~ /^(\w+)$/) {
2691     $specific{'autodelete'} = $autodelete = uc($1);
2692   } elsif ($v eq 'KEEP' and $vv =~ /^(\d+)$/) {
2693     $keep = $1;
2694     $keep = $keep_max if $keep_max and $keep > $keep_max;
2695     $specific{'keep'} = $keep;
2696   } elsif ($v eq 'TIMEOUT' and $vv =~ /^(\d+)$/) {
2697     $specific{'timeout'} = $timeout = $1;     
2698   }
2699 }
2700
2701
2702 sub id_forgotten {
2703   my ($id,$to,$subuser,$gm,$skey,$gkey,$url,$fup);
2704   
2705   return if $nomail;
2706   
2707   $fup = $durl;
2708   $fup =~ s:/fop:/fup:;
2709   
2710   # full user
2711   if (open $from,'<',"$from/\@") {
2712     $id = getline($from);
2713     close $from;
2714   }
2715   if ($id) {
2716     $url = "$fup/".b64("from=$from&id=$id");
2717     mail_forgotten($from,qqq(qq(
2718       'Your reqested F*EX auth-ID for $fup?from=$from is:'
2719       '$id'
2720       ''
2721       'Or use:'
2722       '$url'
2723     )));
2724     exit;
2725   }
2726   
2727   # sub user
2728   foreach my $skey (glob("$skeydir/*")) {
2729     if (-f $skey and open $skey,'<',$skey) {
2730       while (<$skey>) {
2731         $_ = lc;
2732         if (/^(\w+)=(.+)/) {
2733           $subuser = $2 if $1 eq 'from';
2734           $to      = $2 if $1 eq 'to';
2735         }
2736       }
2737       close $skey;
2738     }
2739     if ($from and $to and $from eq $subuser) {
2740       $skey =~ s:.*/::;
2741       mail_forgotten($subuser,qqq(qq(
2742         'Your reqested F*EX login is:'
2743         ''
2744         '$fup?skey=$skey'
2745       )));
2746       exit;
2747     }
2748   }
2749   
2750   # group user
2751   foreach my $gkey (glob("$gkeydir/*")) {
2752     if (-f $gkey and open $gkey,'<',$gkey) {
2753       while (<$gkey>) {
2754         $_ = lc;
2755         if (/^(\w+)=(.+)/) {
2756           $gm = $2 if $1 eq 'from';
2757           $to = $2 if $1 eq 'to';
2758         }
2759       }
2760       close $gkey;
2761     }
2762     if ($gm and $to and $from eq $gm) {
2763       $gkey =~ s:.*/::;
2764       mail_forgotten($gm,qqq(qq(
2765         'Your reqested F*EX login is:'
2766         ''
2767         '$fup?gkey=$gkey'
2768       )));
2769       exit;
2770     }
2771   }
2772   http_die("<code>$from</code> is not a F*EX user on this server");
2773 }
2774
2775
2776 sub mail_forgotten {
2777   my $user = shift;
2778   my @msg = @_;
2779   local *P;
2780
2781   return if $nomail;
2782
2783   open P,'|-',$sendmail,$user,$bcc or http_die("cannot start sendmail - $!\n");
2784   pq(P,qq(
2785     'From: $admin'
2786     'To: $user'
2787     'Subject: F*EX service $hostname'
2788     'X-Mailer: F*EX'
2789     ''
2790   ));
2791   print P @msg;
2792   close P or http_die("cannot send mail - $!\n");
2793   http_header('200 OK');
2794   print html_header($head);
2795   print "<h3>Mail has been sent to you ($from)</h3>\n";
2796   print "</body></html>\n";
2797 }
2798
2799
2800 # lookup akey, skey and gkey (full and sub user and group)
2801 sub check_keys {
2802
2803   # only one key can be valid
2804   $akey = $gkey = '' if $skey;
2805   $akey = $skey = '' if $gkey;
2806
2807   if ($skey) {
2808     # encrypted SKEY?
2809     if ($skey =~ s/^MD5H:(.+)/$1/) {
2810       # search real SKEY
2811       foreach my $s (glob "$skeydir/*") {
2812         $s =~ s:.*/::;
2813         if ($skey eq md5_hex($s.$sid)) {
2814           $skey = $s;
2815           last;
2816         }
2817       }
2818     }
2819     if (open $skey,'<',"$skeydir/$skey") {
2820       $akey = $gkey = '';
2821       while (<$skey>) {
2822         if (/^(\w+)=(.+)/) {
2823           $from = $2          if lc($1) eq 'from';
2824           @to = ($muser = $2) if lc($1) eq 'to';
2825           $rid = $id = $2     if lc($1) eq 'id';
2826         }
2827       }
2828       close $skey;
2829     } else {
2830       # $skey = '';
2831       http_die("invalid SKEY <code>$skey</code>");
2832     }
2833   }
2834
2835   if ($gkey) {
2836     # encrypted GKEY?
2837     if ($gkey =~ s/^MD5H:(.+)/$1/) {
2838       # search real GKEY
2839       foreach my $g (glob "$gkeydir/*") {
2840         $g =~ s:.*/::;
2841         if ($gkey eq md5_hex($g.$sid)) {
2842           $gkey = $g;
2843           last;
2844         }
2845       }
2846     }
2847     if (open $gkey,'<',"$gkeydir/$gkey") {
2848       $akey = $skey = '';
2849       while (<$gkey>) {
2850         if (/^(\w+)=(.+)/) {
2851           $from        = $2 if lc($1) eq 'from';
2852           $to = $muser = $2 if lc($1) eq 'to';
2853           $rid = $id   = $2 if lc($1) eq 'id';
2854           # $user      = $2 if lc($1) eq 'user';
2855         }
2856       }
2857       close $gkey;
2858       @to = ($to);
2859     } else {
2860       # $gkey = '';
2861       http_die("invalid GKEY <code>$gkey</code>");
2862     }
2863   }
2864
2865   if ($akey and not $id) {
2866     my $idf;
2867
2868     # sid is not set with web browser
2869     # akey with sid is set with schwuppdiwupp & co
2870     $idf = "$akeydir/$akey/@";
2871     
2872     if (open $idf,'<',$idf and $id = getline($idf)) {
2873       close $idf;
2874       $from = readlink "$akeydir/$akey"
2875         or http_die("internal server error: no $akey symlink");
2876       $from =~ s:.*/::;
2877       $from = untaint($from);
2878       if ($akey ne md5_hex("$from:$id")) {
2879         $from = $id = '';
2880       }
2881     } else {
2882       $akey = '';
2883     }
2884   }
2885
2886 }
2887
2888
2889 # check if there is enough space on spool
2890 sub check_space {
2891   my $req = shift;
2892   my ($df,$free,$uprq);
2893   local *P;
2894   
2895   if (open $df,"df -k $spooldir|") {
2896     while (<$df>) {
2897       if (/^.+?\s+\d+\s+\d+\s+(\d+)/ and $req/1024 > $1) {
2898         $free = int($1/1024);
2899         $uprq = int($req/$MB);
2900         if (not $nomail and open P,"|$sendmail -t") {
2901           pq(P,qq(
2902             'From: $admin'
2903             'To: $admin'
2904             'Subject: F*EX spool out of space'
2905             ''
2906             'F*EX spool $spooldir on $ENV{SERVER_NAME} is out of space.'
2907             ''
2908             'Current free space: $free MB'
2909             'Upload request: $uprq MB'
2910           ));
2911           close P;
2912         }
2913         debuglog("aborting because not enough free space in spool ($free MB)");
2914         http_die("not enough free space for this upload");
2915       }
2916     }
2917     close $df;
2918   }
2919 }
2920
2921
2922 # global substitution as a function like in gawk
2923 sub gsub { 
2924   local $_ = shift;
2925   my ($p,$r) = @_; 
2926   s/$p/$r/g; 
2927   return $_;
2928 }
2929
2930
2931 # standard log
2932 sub fuplog {
2933   my $msg = "@_";
2934   
2935   $msg =~ s/\n/ /g;
2936   $msg =~ s/\s+$//;
2937   $msg = sprintf "%s [%s_%s] %s (%s) %s\n",
2938                  isodate(time),$$,$ENV{REQUESTCOUNT},$from,$fra,$msg;
2939   writelog($log,$msg);
2940 }
2941
2942
2943 sub sigdie {
2944   local $_ = shift;
2945   chomp;
2946   sigexit('DIE',$_);
2947 }
2948
2949
2950 sub sigexit {
2951   my ($sig) = @_;
2952   my $msg;
2953   my $to = join(',',@to);
2954
2955   $SIG{__DIE__} = 'DEFAULT';
2956   foreach (keys %SIG) { $SIG{$_} = 'DEFAULT' }
2957
2958   $msg = @_ ? "@_" : '???';
2959   $msg =~ s/\n/ /g;
2960   $msg =~ s/\s+$//;
2961   $msg = sprintf "%s %s (%s) %s %s caught SIGNAL %s %s\n",
2962                  isodate(time),
2963                  $from||'-',
2964                  $fra||'-',
2965                  $to||'-',
2966                  encode_Q($file||'-'),
2967                  $msg,
2968                  $RB?"(after $RB bytes)":"";
2969   
2970   writelog($log,$msg);
2971   
2972   if ($sig eq 'DIE') {
2973     shift;
2974     die "$msg\n";
2975   } else {
2976     die "SIGNAL $msg\n";
2977   }
2978 }
2979
2980
2981 sub present_locales {
2982   my $url = shift;
2983   my @locales = @::locales; # from fex.ph
2984   my ($locale,$lang);
2985   
2986   if ($url =~ /\?/) { 
2987     $url .= "&";
2988     $url =~ s/locale=\w+&//g;
2989   } else { 
2990     $url .= "?";
2991   }
2992   
2993   if (@locales) {
2994     map { $_ = "$FEXHOME/locale/$_" } @locales;
2995   } else {
2996     @locales = glob "$FEXHOME/locale/*";
2997   }
2998
2999   if (@locales > 1) {
3000     print "<h3>";
3001     foreach my $locale (my @loop = @locales) {
3002       if (-x "$locale/cgi-bin/fup") {
3003         $lang = "$locale/lang.html";
3004         $locale =~ s:.*/::;
3005         if (open $lang,'<',$lang and $lang = getline($lang)) {
3006           close $lang;
3007         } else {
3008           $lang = $locale;
3009         }
3010         print "<a href=\"${url}locale=$locale\">$lang</a> ";
3011       }
3012     }
3013     print "</h3>\n";
3014   }
3015 }
3016
3017
3018 sub check_camel {
3019   my ($logo,$camel);
3020   local $/;
3021   
3022   if (open $logo,"$docdir/logo.jpg") {
3023     $camel = md5_hex(<$logo>) eq 'ad8a95bba8dd1a61d70bd38611bc2059';
3024   }
3025   if ($camel and open $logo,"$docdir/action-fex-camel.gif") {
3026     $camel = md5_hex(<$logo>) eq '1f3d7acc70377496f95c5adddaf4ca7b';
3027   }
3028   http_die("Missing camel") unless $camel;
3029 }