]> git.treefish.org Git - fex.git/blobdiff - cgi-bin/fup
Original release 20160919
[fex.git] / cgi-bin / fup
index d43cda0edcd0402d228427ff2ce36959591a031a..9ba0ef27ecc8118654520e4072b6ff655778f9bd 100755 (executable)
@@ -8,20 +8,17 @@
 #      Sebastian Zaiser <szcode@arcor.de> (upload status)
 #
 
+BEGIN { ($ENV{PERLINIT}||'') =~ /(.+)/s and eval $1 }
+
+use utf8;
 use Encode;
 use Fcntl              qw':flock :seek :mode';
 use IO::Handle;
 use Digest::MD5                qw'md5_hex';
-use CGI::Carp          qw'fatalsToBrowser';
 use Cwd                        qw'abs_path';
 
-use constant DS => 60*60*24;
-use constant M  => 1024*1024;
-
 # add fex lib
-die "$0: no \$FEXLIB\n" unless $ENV{FEXLIB};
 (our $FEXLIB) = $ENV{FEXLIB} =~ /(.+)/;
-die "$0: no $FEXLIB\n" unless -d $FEXLIB;
 
 $| = 1;
 
@@ -39,10 +36,12 @@ our (@registration_hosts,@demo,@file_link_dirs);
 
 # import from fex.pp
 our ($FEXHOME);
-our ($spooldir,$durl,$tmpdir,$logdir,$docdir,$hostname,$admin,$fra);
-our ($keep_default,$recipient_quota,$sender_quota);
-our ($sendmail,$mdomain,$fop_auth,$mail_auth,$faillog);
+our ($spooldir,$durl,$tmpdir,@logdir,$logdir,$docdir,$hostname,$admin,$fra);
+our ($keep_default,$recipient_quota,$sender_quota,$fex_yourself);
+our ($sendmail,$mdomain,$fop_auth,$mail_auth,$faillog,$amdl);
 our ($dkeydir,$ukeydir,$akeydir,$skeydir,$gkeydir,$xkeydir);
+our ($MB,$DS);
+our $RB;               # read POST bytes (total)
 our $akey = '';
 our $dkey = '';
 our $skey = '';
@@ -54,26 +53,26 @@ our $fpsize = 0;    # file part size (MIME-part)
 
 my $data;
 my $boundary;
-my $rb = 0;            # read bytes, totally
 my $rid = '';          # real ID
 my @header;            # HTTP entity header
 my $fileid;            # file ID
 my $captive;
 my $muser;             # main user fur sub or group user
-  
+my %specific;          # upload specific KEEP and AUTODELETE parameters
+
 # load common code, local config: $FEXLIB/fex.ph
-require "$FEXLIB/fex.pp" or die "$0: cannot load $FEXLIB/fex.pp - $!\n";
+require "$FEXLIB/fex.pp";
 
 # load fup local config
 our ($info_1,$info_2,$info_login);
 
 $locale = $ENV{LOCALE} || 'english';
-foreach my $pl (
-  "/var/lib/fex/locale/$locale/lib/fup.pl", 
+foreach (
+  "/var/lib/fex/locale/$locale/lib/fup.pl",
   "$FEXLIB/fup.pl",
 ) {
-  if (-f $pl) {
-    require $pl or die "$0: cannot load $FEXLIB/fup.pl - $!\n";
+  if (-f) {
+    require;
     last;
   }
 }
@@ -82,7 +81,7 @@ foreach my $pl (
 
 chdir $spooldir or http_die("$spooldir - $!\n");
 
-my $log = "$logdir/fup.log";
+my $log = 'fup.log';
 
 my $http_client = $ENV{HTTP_USER_AGENT} || '';
 my $cl = $ENV{X_CONTENT_LENGTH} || $ENV{CONTENT_LENGTH} || 0;
@@ -111,11 +110,16 @@ if ($addto) {
   my %to;
   foreach $to (@to) { $to{$to} = 1 }
   push @to,$addto unless $to{$addto};
-  if ($submit and @to == 1) { $addto = '' }
+  # user has submitted with [select from your address book] ?
+  # if ($submit and @to == 1) { $addto = '' }
 }
 
 $to = join(',',@to);
 
+if ($from eq $to and $fex_yourself =~ /^no|0$/i) {
+  http_die("fexing to yourself is not allowed");
+}
+
 $uid = randstring(8) unless $uid; # upload ID
 
 # user requests for forgotten ID
@@ -128,7 +132,7 @@ if ($from and $id_forgotten and $mail_authid and not ($fop_auth or $nomail)) {
 
 # public recipients? (needs no auth-ID for sender)
 if ($to and $id and $id eq 'PUBLIC' and @public_recipients) {
-  
+
   unless ($from) {
     http_die("missing sender e-mail address");
   }
@@ -145,12 +149,12 @@ if ($to and $id and $id eq 'PUBLIC' and @public_recipients) {
 }
 
 # anonymous upload from enabled IP?
-if ($from =~ /^anonymous@/ and 
+if ($from =~ /^anonymous@/ and
     @anonymous_upload and ipin($ra,@anonymous_upload)) {
   $id = $rid = $anonymous = 'anonymous';
   if ($to =~ /^anonymous/) {
     @to = ($to);
-    $autodelete{$to} = $autodelete = 'NO'; 
+    $autodelete{$to} = $autodelete = $specific{'autodelete'}||'NO';
   }
   $nomail = $anonymous;
 }
@@ -160,7 +164,7 @@ $comment = 'NOMAIL' if $nomail and not $comment;
 # one time token
 if ($okey) {
   $to = "@to" or http_die("no recipient specified");
-  $from = readlink "$to/\@OKEY/$okey" 
+  $from = readlink "$to/\@OKEY/$okey"
     or http_die("no upload key \"<code>$okey</code>\" - ".
                 "request another one from <code>$to</code>");
   $from = untaint($from);
@@ -198,10 +202,9 @@ if ($from and $id and not ($gkey or $skey or $public or $okey)) {
     # set akey link for HTTP sessions
     # (need original id for consistant non-moving akey)
     if (-d $akeydir and open $idf,'<',"$from/@" and my $id = getline($idf)) {
-      $akey = untaint(md5_hex("$from:$id"));
-      mksymlink("$akeydir/$akey","../$from");
-      # show URL from fexsend
-      if ($from eq $to and $comment eq '*') {
+      # akey for webbrowser or fexsend special
+      if (not $sid or ($from eq $to and ($comment eq '*')) or $command) {
+        $akey = untaint(md5_hex("$from:$id"));
         mksymlink("$akeydir/$akey","../$from");
       }
     }
@@ -214,6 +217,11 @@ if ($from and $id and not ($gkey or $skey or $public or $okey)) {
   }
 }
 
+# optional $auth_hook() in fup.pl
+if ($auth_hook and ($akey or $skey or $gkey) and $from and -d $from) {
+  &$auth_hook;
+}
+
 # forward a copy of a file to another recipient
 if ($akey and $dkey and $command eq 'FORWARD') {
   my $file = untaint(readlink "$dkeydir/$dkey"||'');
@@ -246,12 +254,12 @@ if ($akey and $dkey and $command eq 'COPY') {
     http_die("File not found");
   }
   if (-e "$to/$to/$file/data") {
-    http_die("File $file already exists in your outgoing spool") 
-      if (readlink("$to/$to/$file/id")||$to) ne 
+    http_die("File $file already exists in your outgoing spool")
+      if (readlink("$to/$to/$file/id")||$to) ne
          (readlink("$to/$from/$file/id")||$from);
   } else {
     mkdirp("$to/$to/$file");
-    link "$to/$from/$file/data","$to/$to/$file/data" 
+    link "$to/$from/$file/data","$to/$to/$file/data"
       or http_die("cannot link to $to/$to/$file/data - $!\n");
     copy("$to/$from/$file/filename","$to/$to/$file/filename");
     copy("$to/$from/$file/id","$to/$to/$file/id");
@@ -296,7 +304,7 @@ if ($akey and $dkey and $command eq 'DELETE') {
       ""
     );
     &reexec;
-  } else { 
+  } else {
     my $s = $!;
     http_header('404 Not Found');
     print html_header($head);
@@ -309,7 +317,7 @@ if ($akey and $dkey and $command eq 'DELETE') {
 
 # special commands
 if (($from and $id and $rid eq $id or $gkey or $skey) and $command) {
-                                                                     
+
   if ($command eq 'CHECKQUOTA') {
     http_die("illegal command \"$command\"") if $public or $anonymous;
     nvt_print('HTTP/1.1 204 OK');
@@ -371,7 +379,7 @@ if (($from and $id and $rid eq $id or $gkey or $skey) and $command) {
       next if $file =~ m:(.+?)/: and -l $1;
       $size = -s "$file/data";
       next unless $size;
-      $size = int($size/M+0.5);
+      $size = int($size/$MB+0.5);
       $filename = $comment = '';
       my $rto = $file;
       $rto =~ s:/.*::;
@@ -384,9 +392,9 @@ if (($from and $id and $rid eq $id or $gkey or $skey) and $command) {
           $filename = <$file>;
           close $file;
         }
-        if ($filename and length $filename) { 
+        if ($filename and length $filename) {
           $filename = html_quote($filename);
-        } else { 
+        } else {
           $filename = '???';
         }
         if (open $file,'<',"$file/comment") {
@@ -394,23 +402,23 @@ if (($from and $id and $rid eq $id or $gkey or $skey) and $command) {
           close $file;
         }
         my $rkeep = untaint(readlink "$file/keep"||$keep_default)
-                    - int((time-mtime("$file/filename"))/DS);
-        if ($comment =~ /NOMAIL/ or 
+                    - int((time-mtime("$file/filename"))/$DS);
+        if ($comment =~ /NOMAIL/ or
            (readlink "$to/\@NOTIFICATION"||'') =~ /^no/i) {
-          printf "%8s MB [%s d] %s/%s/%s\n",
+          printf "%8s MB (%2s d) %s/%s/%s\n",
                  $size,
                  $rkeep,
                  $durl,
                  $dkey,
                  urlencode(basename($file));
         } else {
-          printf "%8s MB [%s d] <a href=\"%s\">%s</a>%s %s\n",
+          printf "%8s MB (%2s d) <a href=\"%s\">%s</a>%s %s\n",
                  $size,
                  $rkeep,
                  untaint("/fup?akey=$akey&dkey=$dkey&command=RENOTIFY"),
                  $filename,
                  $comment ? qq' "$comment"' : '',
-                 $file eq $nfile ? 
+                 $file eq $nfile ?
                    " &rarr; notification e-mail has been resent" :
                    "";
         }
@@ -422,7 +430,7 @@ if (($from and $id and $rid eq $id or $gkey or $skey) and $command) {
       '</body></html>'
     ));
     exit;
-  } 
+  }
 
   if ($command =~ /^LIST(RECEIVED)?$/) {
     http_die("illegal command \"$command\"") if $public or $anonymous;
@@ -438,11 +446,12 @@ if (($from and $id and $rid eq $id or $gkey or $skey) and $command) {
         next if $file =~ m:(.+?)/: and -l $1;
         $size = -s "$file/data";
         next unless $size;
-        $size = int($size/M+0.5);
+        $size = int($size/$MB+0.5);
         $filename = $comment = '';
         my $rto = $file;
         $rto =~ s:/.*::;
         if ($dkey = readlink "$file/dkey") {
+        # die $file if -s "$file/data" and $file =~ /^$from/;
           if ($rto ne $to) {
             $to = $rto;
             print "\nto $to :\n";
@@ -451,20 +460,21 @@ if (($from and $id and $rid eq $id or $gkey or $skey) and $command) {
             $filename = <$file>;
             close $file;
           }
-          if ($filename and length $filename) { 
+          if ($filename and length $filename) {
             $filename = html_quote($filename);
-          } else { 
+          } else {
             $filename = '???';
           }
           if (open $file,'<',"$file/comment") {
             $comment = untaint(html_quote(getline($file)));
             close $file;
           }
-          my $rkeep = untaint(readlink "$file/keep"||$keep_default) 
-                      - int((time-mtime("$file/filename"))/DS);
-          printf "%8s MB [%s d] <a href=\"%s\">%s</a>%s\n",
+          my $rkeep = untaint(readlink "$file/keep"||$keep_default)
+                      - int((time-mtime("$file/filename"))/$DS);
+          printf "%8s MB (%2s d) %s <a href=\"%s\">%s</a>%s\n",
                  $size,
                  $rkeep,
+                 stat("$file/download")?'+':'-',
                  untaint("/fup?akey=$akey&dkey=$dkey&command=FORWARD"),
                  $filename,
                  $comment?qq( "$comment"):'';
@@ -475,7 +485,7 @@ if (($from and $id and $rid eq $id or $gkey or $skey) and $command) {
         '<p><a href="javascript:history.back()">back to F*EX operation control</a>'
         '</body></html>'
       ));
-    } 
+    }
     # list received files
     else {
       $to = $from;
@@ -493,7 +503,7 @@ if (($from and $id and $rid eq $id or $gkey or $skey) and $command) {
           $filename = $comment = '';
           $size = -s "$file/data";
           next unless $size;
-          $size = int($size/M+0.5);
+          $size = int($size/$MB+0.5);
           if ($dkey = readlink "$file/dkey") {
             print "\nfrom $from :\n" unless $url;
             $file =~ m:.*/(.+):;
@@ -505,9 +515,9 @@ if (($from and $id and $rid eq $id or $gkey or $skey) and $command) {
               $filename = <$file>;
               close $file;
             }
-            if ($filename and length $filename) { 
+            if ($filename and length $filename) {
               $filename = html_quote($filename);
-            } else { 
+            } else {
               $filename = '???';
             }
             if (open $file,'<',"$file/comment") {
@@ -515,13 +525,13 @@ if (($from and $id and $rid eq $id or $gkey or $skey) and $command) {
               $comment = ' "'.$comment.'"';
               close $file;
             }
-            my $rkeep = untaint(readlink "$file/keep"||$keep_default) 
-                        - int((time-mtime("$file/filename"))/DS);
+            my $rkeep = untaint(readlink "$file/keep"||$keep_default)
+                        - int((time-mtime("$file/filename"))/$DS);
             printf "[<a href=\"/fup?akey=%s&dkey=%s&command=DELETE\">delete</a>] ",
                    $akey,$dkey;
             printf "[<a href=\"/fup?akey=%s&dkey=%s&command=COPY\">forward</a>] ",
                    $akey,$dkey;
-            printf "%8s MB (%s d) <a href=\"%s\">%s</a>%s\n",
+            printf "%8s MB (%2s d) <a href=\"%s\">%s</a>%s\n",
                    $size,$rkeep,$url,$filename,$comment;
           }
         }
@@ -534,11 +544,11 @@ if (($from and $id and $rid eq $id or $gkey or $skey) and $command) {
       ));
     }
     exit;
-  } 
-      
+  }
+
   if ($command eq 'LISTSENT') {
     http_die("illegal command \"$command\"") if $public or $anonymous;
-    # show download URLs 
+    # show download URLs
     http_header('200 OK');
     print html_header($head);
     print "<h2>Download URLs of files you have sent\n";
@@ -564,7 +574,7 @@ if (($from and $id and $rid eq $id or $gkey or $skey) and $command) {
     ));
     exit;
   }
-      
+
   if ($command eq 'FOPLOG') {
     http_die("illegal command \"$command\"") if $public or $anonymous;
     if (open my $log,"$logdir/fop.log") {
@@ -581,14 +591,14 @@ if (($from and $id and $rid eq $id or $gkey or $skey) and $command) {
     }
     exit;
   }
-  
+
   if ($command eq 'RECEIVEDLOG') {
     http_die("illegal command \"$command\"") if $public or $anonymous;
-    if (open my $fuplog,"$logdir/fup.log") {
+    if (open my $log,"$logdir/fup.log") {
       http_header('200 OK');
-      while (<$fuplog>) {
+      while (<$log>) {
         next if /\sSTDFEX\s/;
-        if (/\d+$/) { 
+        if (/\d+$/) {
           my @F = split;
           if ($F[5] eq $to) {
             s/ \[[\d_]+\]//;
@@ -602,11 +612,11 @@ if (($from and $id and $rid eq $id or $gkey or $skey) and $command) {
 
   if ($command eq 'SENDLOG') {
     http_die("illegal command \"$command\"") if $public or $anonymous;
-    if (open my $fuplog,"$logdir/fup.log") {
+    if (open my $log,"$logdir/fup.log") {
       http_header('200 OK');
-      while (<$fuplog>) {
+      while (<$log>) {
         next if /\sSTDFEX\s/;
-        if (/(\S+\@\S+)/ and $1 eq $from) { 
+        if (/(\S+\@\S+)/ and $1 eq $from) {
           s/ \[[\d_]+\]//;
           print;
         }
@@ -623,11 +633,12 @@ if (($from and $id and $rid eq $id or $gkey or $skey) and $command) {
     foreach my $to (@group?@group:@to) {
       # my $options = sprintf "(autodelete=%s,keep=%s,locale=%s)",
       # readlink "$to/\@LOCALE"||$locale||$locale{$to}||$default_locale;
-      my $options = sprintf "(autodelete=%s,keep=%s,locale=%s,notification=%s)",
+      # my $options = sprintf "(autodelete=%s,keep=%s,locale=%s,notification=%s)",
+      my $options = sprintf "(autodelete=%s,keep=%s,locale=%s)",
         $autodelete{$to}||$autodelete,
         $keep{$to}||$keep_default,
-        readlink("$to/\@LOCALE")||$default_locale,
-        readlink("$to/\@NOTIFICATION")||'full';
+        readlink("$to/\@LOCALE")||$locale{$to}||$default_locale;
+        readlink("$to/\@NOTIFICATION")||'full';
       nvt_print("X-Recipient: $to $options");
     }
     nvt_print('');
@@ -649,7 +660,7 @@ if (($from and $id and $rid eq $id or $gkey or $skey) and $command) {
         http_die("illegal parameter <code>$del</code>");
       }
       $del = untaint($del);
-      
+
       if (unlink("$del/data") or unlink("$del/upload")) {
         if (open F,'>',"$del/error") {
           print F "$file has been deleted by $from\n";
@@ -658,7 +669,7 @@ if (($from and $id and $rid eq $id or $gkey or $skey) and $command) {
         http_header('200 OK',"X-File: $del");
         print html_header($head);
         print "<h3>$file deleted</h3>\n";
-      } else { 
+      } else {
         http_header("404 Not Found");
         print html_header($head);
         print "<h3>$file not deleted</h3>\n";
@@ -688,20 +699,20 @@ if ($from and $id and $rid eq $id and open my $ipr,"$from/\@UPLOAD_HOSTS") {
   }
 }
 
-# quotas 
+# quotas
 if ($from and $id and $rid eq $id and @to and not $flink and not $seek) {
   my ($quota,$du);
-  
+
   # check sender quota
   ($quota,$du) = check_sender_quota($muser||$from);
-  if ($quota and $du+$cl/M > $quota) {
+  if ($quota and $du+$cl/$MB > $quota) {
     http_die("you are overquota");
   }
-  
+
   # check recipient quota
   foreach my $to (@to) {
     ($quota,$du) = check_recipient_quota($to);
-    if ($quota and $du+$cl/M > $quota) {
+    if ($quota and $du+$cl/$MB > $quota) {
       http_die("$to cannot receive files: is overquota");
     }
   }
@@ -717,15 +728,14 @@ if ($id and $id eq $rid and $from and @to and not $public) {
 # (= has a F*EX ID)
 if (not $addto and $fop_auth and $id and $id eq $rid and $from and @to) {
   my ($to_reg,$idf,$subuser);
-  foreach (@to) {
-    my $to = $_;
+  foreach my $to (my @loop = @to) {
     $to =~ s/:\w+=.*//; # remove options from address
     $to_reg = 0;
     # full user?
     if (open $idf,'<',"$to/@") {
       $to_reg = getline($idf);
       close $idf;
-    } 
+    }
     # sub user?
     elsif (open $idf,'<',"$from/\@SUBUSER") {
       while (<$idf>) {
@@ -749,7 +759,7 @@ if (not $addto and $fop_auth and $id and $id eq $rid and $from and @to) {
 }
 
 $to = join(',',@to);
-  
+
 if ($to =~ /^@(.+)/) {
   if ($nomail) {
     http_die("server runs in NOMAIL mode - groups ($to) are not allowed");
@@ -782,20 +792,20 @@ if ($from and $id and $id eq $rid and $faillog) {
 # display HTML form and request user data
 unless ($file) {
 
-  if ($test) { $cgi = $test } 
+  if ($test) { $cgi = $test }
   else       { $cgi = $ENV{SCRIPT_NAME} }
   $cgi = 'fup';
-  
+
   # delete old cookies on logout referer
   my @cookies;
   if ($logout and my $cookie = $ENV{HTTP_COOKIE}) {
     while ($cookie =~ s/(\w+key)=\w+//) {
-      push @cookies,"Set-Cookie: $1=; Max-Age=0; Discard";
+      push @cookies,"Set-Cookie: $1=x; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT";
     }
   }
-  
-  # save default locale for this user
+
   if (($akey or $skey or $gkey) and $from and -d $from) {
+    # save default locale for this user
     if (not $locale and ($ENV{HTTP_COOKIE}||'') =~ /\blocale=(\w+)/) {
       $locale = $1;
     }
@@ -805,7 +815,7 @@ unless ($file) {
   http_header('200 OK',@cookies);
   # print html_header($head,'<img src="/fex_small.gif">');
   print html_header($head);
-    
+
   if ($http_client =~ /(Konqueror|w3m)/) {
     pq(qq(
       '<p><hr><p>'
@@ -818,11 +828,11 @@ unless ($file) {
   }
 
   # default "fex yourself" setting?
-  if ($from and $id and $id eq $rid and not $addto 
+  if ($from and $id and $id eq $rid and not $addto
       and not ($gkey or $skey or $okey or $public or $anonymous)
       and (not @to or "@to" eq $from)
       and -f "$from/\@FEXYOURSELF")
-  { 
+  {
     @to = ($from);
     $nomail = 'fexyourself';
   }
@@ -832,22 +842,25 @@ unless ($file) {
          and not ($gkey or $skey or $okey or $public or $anonymous))
   {
     present_locales('/fup');
-    
+
+    # print "[$addto] [$submit] [@to]<p>\n";
+
     @ab = ("<option></option>");
-    
+
     # select menu from server address book
-    if (open my $ab,'<',"$from/\@ADDRESS_BOOK") {
-      while (<$ab>) {
+    if (open my $AB,'<',"$from/\@ADDRESS_BOOK") {
+      while (<$AB>) {
         s/#.*//g;
         if (/(\S+)[=\s]+(\S+@[\w.-]+\S*)/) {
           $_ = "$1 &lt;$2>";
           s/,.*/,.../g;
+          s/:.*/>/;
           push @ab,"<option>$_</option>";
         }
       }
-      close $ab;
+      close $AB;
     }
-    
+
     unless (@to) {
       unless ($nomail) {
         foreach (glob "$from/\@GROUP/*") {
@@ -858,7 +871,7 @@ unless ($file) {
         }
       }
     }
-      
+
     my $ab64 = b64("from=$from&id=$id");
 #     '<form class="uploadform" name="upload"'
     pq(qq(
@@ -870,7 +883,7 @@ unless ($file) {
       '  <input type="hidden" name="from" value="$from">'
       '  <input type="hidden" name="id"   value="$id">'
       '  <table border="1">'
-      '    <tr><td>sender:   <td><a href="/fup/$ab64">$from</a></tr>'
+      '    <tr><td>sender:   <td><a href="/foc">$from</a></tr>'
       '    <tr title="e-mail address or alias"><td>recipient(s):'
       '        <td><input type="text" name="to" size="96" value="$to"><br>'
     ));
@@ -902,6 +915,11 @@ unless ($file) {
           foreach my $rd (@local_rdomains) {
             print "*\@$rd\n";
           }
+        } elsif (/^\@LOCAL_USERS/) {
+          foreach (glob "*/@") {
+            s:/.::;
+            print "$_\n";
+          }
         } else {
           print "$_\n";
         }
@@ -909,25 +927,24 @@ unless ($file) {
       print "</pre><p>\n";
       close $rr;
     }
-    pq(qq(
-      '  <input type="submit" name="submit" value="check recipient(s) and continue">'
-      '  or <input type="submit" name="fexyourself" value="fex yourself">'
-      '</form>'
-      '<p>'
-    ));
+    print qq'  <input type="submit" name="submit" value="check recipient(s) and continue">';
+    if ($fex_yourself =~ /^yes|1/i) {
+      print qq' or <input type="submit" name="fexyourself" value="fex yourself">'
+    }
+    print "\n</form>\n<p>\n";
     if ($akey and -f "$from/\@" and not $captive ) {
       pq(qq(
         '<a href="/foc?akey=$akey">user config & operation control</a>'
       ));
     }
-    
+
     if ($from eq $admin ) {
       pq(qq(
         '<p>'
         '<a href="/fac">server config & admin control</a>'
       ));
     }
-    
+
     if (0 and -f "$docdir/FIX.jar") {
       print "<p>\n";
       if    ($public) { print "<a href=\"/fix?from=$from&id=$public&to=$to\">" }
@@ -937,30 +954,39 @@ unless ($file) {
       print "Alternate Java client</a> (for files > 2 GB or sending of more than one file)\n";
     }
     print &logout;
-    if (-x "$FEXHOME/cgi-bin/login") {
-      print $info_login||$info_1;
-    }
-    print "</body></html>\n";
+    pq(qq(
+      '<p><hr><p>'
+      '<b>'
+      'Warning: the recipient must not be a mailing list,'
+      'because after download the file will be no more available!'
+      '</b><br>'
+      'Contact <a href="mailto:$ENV{SERVER_ADMIN}">fexmaster</a> if you want to fex to a mailing list,'
+      'he can allow multiple downloads for specific addresses.'
+      '<p>'
+      'Use a <a href="/tools.html">F*EX client</a> if you want to send more than one file or resume an interrupted upload.'
+      '</body></html>'
+      '<p>'
+   ));
+    print $info_1;
     exit;
-  } 
-  
+  }
+
   # ask for filename
   if ($from and ($id or $okey)) {
     $to = $group if $group;
     present_locales($ENV{REQUEST_URI}) if $skey or $gkey or $okey;
+#      "        '$ENV{PROTO}://$ENV{HTTP_HOST}/$cgi?showstatus=$uid',"
     pq(qq(
       '<script type="text/javascript">'
       '  function showstatus() {'
-      '    var file  = document.forms["upload"].elements["file"].value;'
-      '    if (file != "") {'
-      '      window.open('
-      "        '$ENV{PROTO}://$ENV{HTTP_HOST}/$cgi?showstatus=$uid',"
-      "        'fup_status',"
-      "        'width=700,height=500'"
-      '      );'
-      '      return true;'
-      '    }'
-      '    return false;'
+      '    var file = document.forms["upload"].elements["file"].value;'
+      '    if (file == "") return false;'
+      '    window.open('
+      "      '/$cgi?showstatus=$uid',"
+      "      'fup_status',"
+      "      'width=700,height=500'"
+      '    );'
+      '    return true;'
       '  }'
       ''
       '  function checkupload() {'
@@ -990,7 +1016,7 @@ unless ($file) {
       '  <input type="hidden" name="from"     value="$from">'
       '  <input type="hidden" name="filesize" value="">'
     ));
-    
+
     if ($public) {
       my $toh = join('<br>',@to);
       pq(qq(
@@ -1038,7 +1064,7 @@ unless ($file) {
       pq(qq(
         '  <input type="hidden" name="akey" value="$akey">'
         '  <table border="1">'
-        '    <tr><td>sender:<td>$from</tr>'
+        '    <tr><td>sender:<td><a href="/foc">$from</a></tr>'
       ));
       if ($anonymous) {
         pq(qq(
@@ -1052,15 +1078,15 @@ unless ($file) {
         ));
       }
     }
-    
+
     $autodelete = lc $autodelete;
     $keep = $keep_default unless $keep;
     my ($quota,$du) = check_sender_quota($muser||$from);
-    $quota = $quota 
-           ? "<tr><td>sender quota (used):<td>$quota ($du) MB</tr>" 
+    $quota = $quota
+           ? "<tr><td>sender quota (used):<td>$quota ($du) MB</tr>"
            : '';
-    
-    $bwl = qq'<td><input type="text" name="bwlimit" size="8" value="$bwlimit"> kB/s';
+
+    $bwl = qq'<input type="text" name="bwlimit" size="8" value="$bwlimit"> kB/s';
     if (@throttle) {
       foreach (@throttle) {
         if (/\[?(.+?)\]?:(\d+)$/) {
@@ -1069,63 +1095,62 @@ unless ($file) {
           # throttle ip address?
           if ($throttle =~ /^[\w:.-]+$/) {
             if (ipin($ra,$throttle)) {
-              $bwl = qq'<td><input type="hidden" name="bwlimit" value="$limit"> $limit kB/s';
+              $bwl = qq'<input type="hidden" name="bwlimit" value="$limit"> $limit kB/s';
               last;
             }
-          } 
+          }
           # throttle e-mail address?
           else {
             # allow wildcard *, but not regexps
             $throttle =~ quotemeta $throttle;
             $throttle =~ s/\*/.*/g;
             if ($from =~ /^$throttle$/i) {
-              $bwl = qq'<td><input type="hidden" name="bwlimit" value="$limit"> $limit kB/s';
+              $bwl = qq'<input type="hidden" name="bwlimit" value="$limit"> $limit kB/s';
               last;
             }
           }
         }
       }
     }
-    
+
     $autodelete = $autodelete{$to} if $autodelete{$to};
-    
+
     my $adt = '';
     for ($autodelete) {
-         if (/yes/i)   { $adt = 'delete file after download' } 
+         if (/yes/i)   { $adt = 'delete file after download' }
       elsif (/no/i)    { $adt = 'do not delete file after download' }
-      elsif (/delay/i) { $adt = 'delete file after download with delay' } 
+      elsif (/delay/i) { $adt = 'delete file after download with delay' }
       elsif (/^\d+$/)  { $adt = "delete file $autodelete days after download" }
     }
+    $adt .= qq'<input type="hidden" name="autodelete" value="$autodelete">';
 
     my $ctr = my $ktr = '';
     if ($nomail) {
-      $ctr = qq'<td><input type="hidden" name="comment" value="$comment">'
-            .qq'<em>no notification e-mail will be send</em>';
-      $ktr = qq'<input type="text" name="keep" size="2" value="$keep"> days</tr>';
-      $ktr = qq'$keep days<input type="hidden" name="keep" value="$keep"></tr>';
+      $ctr = qq'<em>no notification e-mail will be send</em>';
     } else {
-      $ctr = qq'<td><input type="text" name="comment" size="80" value="$comment">';
-      $ktr = qq'$keep days<input type="hidden" name="keep" value="$keep"></tr>';
+      $ctr = qq'<input type="text" name="comment" size="80" value="$comment">';
     }
     if ($captive) {
-      $ktr = qq'$keep days<input type="hidden" name="keep" value="$keep"></tr>';
+      $ktr = qq'$keep days<input type="hidden" name="keep" value="$keep">';
+    } else {
+      $ktr = qq'$keep days<input type="hidden" name="keep" value="$keep">';
     }
-    
     pq(qq(
-      '    <tr title="$adt"><td>autodelete:<td>$adt</tr>'
-      '    <input type="hidden" name="autodelete" value="$autodelete">'
-      '    <tr title="keep file max $keep days, then delete it"><td>keep:<td>'
-      '    $ktr'
+      '    <tr><td>autodelete:'
+      '      <td>$adt'
+      '    </tr>'
+      '    <tr title="keep file max $keep days, then delete it"><td>keep:'
+      '      <td>$ktr'
+      '    </tr>'
       '    $quota'
       '    <tr title="optional, full speed if empty"><td>bandwith limit:'
-      '      $bwl'
+      '      <td>$bwl'
       '    </tr>'
       '    <tr title="optional, will be included in notification e-mail"><td>comment:'
-      '      $ctr'
+      '      <td>$ctr'
       '    </tr>'
-      '    <tr title="If you want to send more than one file, then put them in a zip or tar archive">'
-      '        <td>file:'
-      '        <td><input type="file" name="file" size="80" value="$file" onchange="reportsize();">'
+      '    <tr title="If you want to send more than one file, then put them in a zip or tar archive"><td>file:'
+      '      <td><input type="file" name="file" size="80" value="$file" onchange="reportsize();">'
       '    </tr>'
       '    <tr><td>file size:<td id="filesize"></td></tr>'
       '  </table>'
@@ -1172,7 +1197,7 @@ unless ($file) {
   pq(qq(
     '<form action="/fup"'
     '      method="post"'
-    '      accept-charset="ISO-8859-1"'
+    '      accept-charset="UTF-8"'
     '      enctype="multipart/form-data">'
     '  <table>'
     '    <tr><td>sender:'
@@ -1196,10 +1221,9 @@ unless ($file) {
     '  <p><input type="submit" value="check ID and continue"><p>'
   ));
   if (not $nomail and (
-    @local_domains and @local_hosts and ipin($ra,@local_hosts)
-    or @local_rdomains and @local_rhosts and
-       (not @registration_hosts or ipin($ra,@registration_hosts)) 
-    or @demo
+    @local_domains and @local_hosts or
+    @local_rdomains and @local_rhosts or
+    @demo
   )) {
     pq(qq(
       'You can <a href="/fur">register yourself</a> '
@@ -1219,8 +1243,8 @@ unless ($file) {
   #  ));
   # }
   print "</form>\n";
-    
-  print $info_1;
+
+  print $info_login||$info_1;
 
   if ($debug and $debug>1) {
     print "<hr>\n<pre>\n";
@@ -1229,7 +1253,7 @@ unless ($file) {
     }
     print "</pre>\n";
   }
-  
+
   print "</body></html>\n";
   exit;
 }
@@ -1239,6 +1263,7 @@ if ($from and $file and not @to) {
   check_rr($from,$from);
   @to = ($from);
   $sup = 'fexyourself';
+  $keep{$from} = readlink("$from/\@KEEP")||$keep_default;
 }
 
 # all these variables should be defined here, but just to be sure...
@@ -1297,14 +1322,17 @@ if (not $anonymous and $overwrite =~ /^n/i) {
 }
 
 # additional last check
-foreach $to (@to) {
-  checkaddress($to) or 
-    http_die("<code>$to</code> is not a valid e-mail address");
+unless (@group or $gkey or $skey or $public or $okey) {
+  foreach $to (@to) {
+    checkaddress($to) or
+      http_die("<code>$to</code> is not a valid e-mail address");
+  }
 }
 
+
 $to = join(',',@to);
 
-# file overwriting for anonymous is only possible if his client has the 
+# file overwriting for anonymous is only possible if his client has the
 # download cookie - else request purging
 if ($anonymous and not $seek and my $dkey = readlink "$to/$from/$fkey/dkey") {
   if ($overwrite =~ /^n/i) {
@@ -1351,17 +1379,12 @@ unless ($nostore) {
     $overwrite{$to}++ if -f $save and not -f $download;
     unlink $save,$download;
     rename $upload,$save or http_die("cannot rename $upload to $save - $!\n");
-    
+
     # log dkey
-    my $dlog = "$logdir/dkey.log";
-    if (open $dlog,'>>',$dlog) {
-      flock $dlog,LOCK_EX;
-      seek $dlog,0,SEEK_END;
-      printf {$dlog} "%s %s %s %s %s\n",
-                     isodate(time),$dkey{$to},$from,$to,$fkey;
-      close $dlog;
-    }
-    
+    my $msg = sprintf "%s %s %s %s %s\n",
+                      isodate(time),$dkey{$to},$from,$to,$fkey;
+    writelog('dkey.log',$msg);
+
     # send notification e-mails if necessary
     if (not $nomail and (readlink "$to/\@NOTIFICATION"||'') !~ /^no/i
         and ($comment or not $overwrite{$to})) {
@@ -1398,11 +1421,12 @@ if ($nostore) {
     my $to = $_;
     $to =~ s/:\w+=.*//; # remove options from address
     my $file = "$to/$from/$fkey";
-    my $options = sprintf "(autodelete=%s,keep=%s,locale=%s,notification=%s)",
+    # my $options = sprintf "(autodelete=%s,keep=%s,locale=%s,notification=%s)",
+    my $options = sprintf "(autodelete=%s,keep=%s,locale=%s)",
       readlink("$file/autodelete")||$autodelete,
       readlink("$file/keep")||readlink("$to/\@KEEP")||$keep_default,
-      readlink("$to/\@LOCALE")||readlink("$file/locale")||$default_locale,
-      readlink("$to/\@NOTIFICATION")||'full';
+      readlink("$to/\@LOCALE")||readlink("$file/locale")||$default_locale;
+      readlink("$to/\@NOTIFICATION")||'full';
     nvt_print("X-Recipient: $to $options");
     nvt_print("X-Location: $durl/$dkey{$to}/$fkey") unless $restricted;
   }
@@ -1418,11 +1442,11 @@ if ($nostore) {
 print html_header($head);
 
 if ($nostore) {
-  printf "%s (%s MB) received\n",$file,$ndata/M;
+  printf "%s (%s MB) received\n",$file,int($ndata/$MB);
 } elsif (not $restricted and ($anonymous or $from eq $to)) {
   my $size = $ndata<2*1024 ? sprintf "%s B",$ndata:
-             $ndata<2*M           ? sprintf "%s kB",int($ndata/1024):
-                             sprintf "%s MB",int($ndata/M);
+             $ndata<2*$MB  ? sprintf "%s kB",int($ndata/1024):
+                             sprintf "%s MB",int($ndata/$MB);
   pq(qq(
     '<code>$file</code> ($size) received and saved<p>'
     'Download URL for copy & paste:'
@@ -1435,7 +1459,7 @@ if ($nostore) {
     if (not $boring and not $seek) {
       print "Ehh... $ndata <b>BYTES</b>?! You are kidding?<p>\n";
     }
-  } elsif ($ndata<2*M) {
+  } elsif ($ndata<2*$MB) {
     $ndata = int($ndata/1024);
     print "<code>$file</code> ($ndata kB) received and saved<p>\n";
     if ($ndata<1024 and not ($boring or $seek)) {
@@ -1443,7 +1467,7 @@ if ($nostore) {
         "ever heard of MIME e-mail? &#9786;<p>\n";
     }
   } else {
-    $ndata = int($ndata/M);
+    $ndata = int($ndata/$MB);
     print "<code>$file</code> ($ndata MB) received and saved<p>\n";
   }
   print "<ul>\n";
@@ -1471,9 +1495,9 @@ if ($nostore) {
           print "Link is valid for $keep{$to} days!<p>\n";
         }
       }
-    } elsif ($overwrite{$to} and not $comment) { 
-      print "(old <code>$file</code> for $to overwritten)<p>\n" 
-    } else { 
+    } elsif ($overwrite{$to} and not $comment) {
+      print "(old <code>$file</code> for $to overwritten)<p>\n"
+    } else {
       print "$to notified<p>\n"
     }
   }
@@ -1491,7 +1515,13 @@ if ($okey) {
   print "&bwlimit=$bwlimit&autodelete=$autodelete&keep=$keep\">";
   print "send another file</a>\n";
   if ($http_client !~ /fexsend/ and $http_client =~ /Linux/i) {
-    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';
+    print '<p>Hi Linux-user, try ',
+          '<a href="/FAQ/user.html#Why_should_I_use_a_special_F_EX_client">',
+          "fexsend</a>! &#9786;<p>\n";
+  }
+  if ($http_client !~ /fexit/ and $http_client =~ /Windows/i) {
+    print '<p>Hi Windows-user, try <a href="/fexit.html">fexit</a>! ',
+          "&#9786;<p>\n";
   }
   print &logout;
 }
@@ -1514,7 +1544,7 @@ sub parse_request {
       setparam($k,$v);
     }
   }
-  
+
   # decode base64 PATH_INFO to QUERY_STRING
   if ($ENV{PATH_INFO} =~ m:^/(\w+=*)$:) {
     if ($qs) {
@@ -1527,11 +1557,21 @@ sub parse_request {
   # parse HTTP QUERY_STRING (parameter=value pairs)
   if ($qs) {
     foreach (split '&',$qs) {
-      if (s/^(\w+)=//) {
-        my $x = $1;
+      if (s/^(\w+)=(.*)//) {
+        my $p = uc($1);
+        my $v = $2;
         # decode URL-encoding
-        s/%([a-f0-9]{2})/chr(hex($1))/gie;
-        setparam($x,$_); 
+        $v =~ s/%([a-f0-9]{2})/chr(hex($1))/gie;
+        setparam($p,$v);
+        if ($p eq 'AUTODELETE') {
+          $specific{'autodelete'} = $autodelete = $v;
+        }
+        if ($p eq 'KEEP' and /^\d+$/) {
+          $specific{'keep'} = $keep = $v;
+        }
+        # if ($p eq 'LOCALE') {
+        #   $specific{'locale'} = $locale = $v;
+        # }
       }
     }
   }
@@ -1549,15 +1589,15 @@ sub parse_request {
     );
     &reexec;
   }
-  
+
   if ($showstatus) {
     &showstatus;
     exit;
   }
-  
+
   # check for akey, gkey and skey (from HTTP GET)
   &check_keys;
-  
+
   if ($ENV{REQUEST_METHOD} eq 'POST' and $cl) {
     foreach $sig (keys %SIG) {
       if ($sig !~ /^(CHLD|CLD)$/) {
@@ -1572,11 +1612,14 @@ sub parse_request {
                      $cl,$ENV{REMOTE_ADDR}||'',$ENV{REMOTE_HOST}||''),"\n");
 
     &check_space($cl) if $cl > 0;
-    
-    $SIG{ALRM} = sub { die "TIMEOUT\n" };
+
+    $SIG{ALRM} = sub {
+      $SIG{__DIE__} = 'DEFAULT';
+      die "TIMEOUT\n";
+    };
     alarm($timeout);
     binmode(STDIN,':raw');
-    
+
     if (defined($ENV{FEX_FILENAME})) {
       # JUP via HTTP header
       $file = $param{'FILE'} = $ENV{FEX_FILENAME};
@@ -1594,7 +1637,7 @@ sub parse_request {
       } else {
         http_die("malformed HTTP POST (no boundary found)");
       }
-    
+
       READPOST: while (&nvt_read) {
         # the file itself - *must* be last part of POST!
         if (/^Content-Disposition:\s*form-data;\s*name="file";\s*filename="(.+)"/i) {
@@ -1608,7 +1651,7 @@ sub parse_request {
             push @header,$_;
           }
           # STDIN is now at begin of file, will be read later with get_file()
-          last; 
+          last;
         }
         # all other parameters
         if (/^Content-Disposition:\s*form-data;\s*name="([a-z]\w*)"/i) {
@@ -1623,7 +1666,7 @@ sub parse_request {
         }
       }
     }
-    
+
     if (length($file)) {
       $file =~ s/%(\d+)/chr($1)/ge;
       $file = untaint(strip_path(normalize($file)));
@@ -1640,36 +1683,54 @@ sub parse_request {
   }
 
   if ($from) {
-    $from .= '@'.$mdomain if $mdomain and $from !~ /@/;
-    if ($from ne 'anonymous' and not checkaddress($from)) {
-      http_die("<code>$from</code> is not a valid e-mail address");
+    unless ($skey or $gkey or $okey) {
+      $from .= '@'.$mdomain if $mdomain and $from !~ /@/;
+      if ($from ne 'anonymous' and not checkaddress($from)) {
+        http_die("<code>$from</code> is not a valid e-mail address");
+      }
     }
     $from = untaint($from);
   }
 
   # collect multiple addresses and check for aliases (not group)
-  if (@to and "@to" !~ /^@[\w-]+$/ 
-      and not ($gkey or $addto or $command =~ /^LIST(RECEIVED)?$/)) 
-  {
-        
+  if (@to and "@to" !~ /^@[\w-]+$/
+      and not ($gkey or $addto or $command =~ /^LIST(RECEIVED)?$/)) {
     # read address book
     if ($from and open my $AB,'<',"$from/\@ADDRESS_BOOK") {
-      my ($alias,$address,$autodelete,$locale,$keep);
+      my ($alias,$addresses,$autodelete,$locale,$keep);
       while (<$AB>) {
         s/#.*//;
         $_ = lc $_;
         if (s/^\s*(\S+)[=\s]+(\S+)//) {
-          ($alias,$address) = ($1,$2);
+          ($alias,$addresses) = ($1,$2);
+          # alias specific options?
           $autodelete = $locale = $keep = '';
           $autodelete = $1 if /autodelete=(\w+)/;
           $locale     = $1 if /locale=(\w+)/;
           $keep       = $1 if /keep=(\d+)/;
-          foreach my $address (split(",",$address)) {
-            $address .= '@'.$mdomain if $mdomain and $address !~ /@/;
+          foreach my $address (split(",",$addresses)) {
+            # alias address specific :options?
+            if ($address =~ s/(.+?):(.+)/$1/) {
+              my @options = split(':',$2);
+              $address = expand($address);
+              foreach (@options) {
+                if (/^keep=(\d+)$/i) {
+                  $alias_keep{$alias}{$address} = $1
+                }
+                if (/^autodelete=(yes|no|delay)$/i) {
+                  $alias_autodelete{$alias}{$address} = $1
+                }
+                if (/^locale=(\w+)$/i) {
+                  $alias_locale{$alias}{$address} = $1
+                }
+              }
+            } else {
+              $address = expand($address);
+            }
             push @{$ab{$alias}},$address;
-            $autodelete{$alias} = $autodelete;
-            $keep{$alias}       = $keep;
-            $locale{$alias}     = $locale;
+            $autodelete{$alias} = $autodelete if $autodelete;
+            $keep{$alias}       = $keep       if $keep;
+            $locale{$alias}     = $locale     if $locale;
           }
         }
       }
@@ -1678,50 +1739,89 @@ sub parse_request {
 
     # look for recipient's options and eliminate dupes
     %to = ();
-    foreach (@to) {
-     my $to = $_;
-     # address book alias?
-      if ($ab{$to}) {
-        foreach (@{$ab{$to}}) {
-          my $address = $_;
-          $address .= '@'.$mdomain if $mdomain and $address !~ /@/;
+    foreach my $to (my @loop = @to) {
+      # address book alias?
+      if ($to !~ /@/ and ($ab{$to} or $to =~ /(.+?):(.+)/ and $ab{$1})) {
+        my $alias = $to;
+        my @options = ();
+        $alias =~ s/:(.*)// and @options = split(':',$1);
+        if (@options) {
+          # alias with :options
+          $alias =~ s/:.*//;
+          foreach my $address (my @loop = @{$ab{$alias}}) {
+            $to{$address} = $address; # ignore dupes
+            foreach (@options) {
+              $keep{$address} = $1       if /^keep=(\d+)$/i;
+              $autodelete{$address} = $1 if /^autodelete=(yes|no|delay)$/i;
+              $locale{$address} = $1     if /^locale=(\w+)$/i;
+            }
+          }
+        }
+        foreach my $address (my @loop = @{$ab{$alias}}) {
           $to{$address} = $address; # ignore dupes
-          if ($specific{'autodelete'}) {
-            $autodelete{$address} = $specific{'autodelete'};
-          } elsif ($autodelete{$to}) {
-            $autodelete{$address} = $autodelete{$to};
-          } else {
-            $autodelete{$address} = readlink "$address/\@AUTODELETE" 
-                                    || $autodelete;
+          unless ($keep{$address}) {
+            $keep{$address} = $keep{$alias} if $keep{$alias};
+            if ($specific{'keep'}) {
+              $keep{$address} = $specific{'keep'}
+            } elsif (my $keep = $alias_keep{$alias}{$address}) {
+              $keep{$address} = $keep;
+            } elsif ($keep{$alias}) {
+              $keep{$address} = $keep{$alias}
+            }
           }
-          if ($_ = readlink "$address/\@LOCALE") {
-            $locale{$address} = $_;
-          } elsif ($locale{$to}) {
-            $locale{$address} = $locale{$to};
-          } else {
-            $locale{$address} = $locale ;
+          unless ($autodelete{$address}) {
+            if ($specific{'autodelete'}) {
+              $autodelete{$address} = $specific{'autodelete'};
+            } elsif (my $autodelete = $alias_autodelete{$alias}{$address}) {
+              $autodelete{$address} = $keep;
+            } elsif ($autodelete{$alias}) {
+              $autodelete{$address} = $autodelete{$alias};
+            } else {
+              $autodelete{$address} = readlink "$address/\@AUTODELETE"
+                                      || $autodelete;
+            }
           }
           unless ($locale{$address}) {
-            $locale{$address} = $default_locale || 'english';
+            if (my $locale = readlink "$address/\@LOCALE") {
+              $locale{$address} = $locale;
+            } elsif ($locale{$alias}) {
+              $locale{$address} = $locale{$alias};
+            } elsif ($locale = $alias_locale{$alias}{$address}) {
+              $locale{$address} = $locale;
+            } else {
+              $locale{$address} = $::locale ;
+            }
+            $locale{$address} ||= $default_locale || 'english';
           }
-          if ($specific{'keep'}) { $keep{$address} = $specific{'keep'} }
-          elsif ($keep{$to})     { $keep{$address} = $keep{$to} }
         }
       } else {
+        # regular address, not an alias
+        if ($to =~ s/(.+?):(.+)/$1/) {
+          my @options = split(':',$2);
+          $to = expand($to);
+          foreach (@options) {
+            $keep{$to} = $1       if /^keep=(\d+)$/i;
+            $autodelete{$to} = $1 if /^autodelete=(yes|no|delay)$/i;
+            $locale{$to} = $1     if /^locale=(\w+)$/i;
+          }
+        }
         $to = expand($to);
         $to{$to} = $to; # ignore dupes
         unless ($autodelete{$to}) {
-          $autodelete{$to} = readlink "$to/\@AUTODELETE" || $autodelete;
+          $autodelete{$to} = untaint(readlink("$to/\@AUTODELETE")
+                                     ||$autodelete);
+          if ($specific{'autodelete'}) {
+            $autodelete{$to} = $specific{'autodelete'};
+          }
+        }
+        unless ($keep{$to}) {
+          $keep{$to} = $keep_default;
+          $keep{$to} = $keep                          if $keep;
+          $keep{$to} = untaint(readlink "$to/\@KEEP") if -l "$to/\@KEEP";
+          $keep{$to} = $specific{'keep'}              if $specific{'keep'};
         }
-        $autodelete{$to} = $specific{'autodelete'}  if $specific{'autodelete'};
-        $keep{$to} = $keep_default;
-        $keep{$to} = $keep                          if $keep;
-        $keep{$to} = untaint(readlink "$to/\@KEEP") if -l "$to/\@KEEP";
-        $keep{$to} = $specific{'keep'}              if $specific{'keep'};
-        # recipient specific parameters
-        $keep{$to}       = $1 if $to =~ /:keep=(\d+)/i;
-        $autodelete{$to} = $1 if $to =~ /:autodelete=(\w+)/i;
       }
+      $autodelete{$to} = 'NO' if $to =~ /$amdl/; # mailing lists, etc
       if (-e "$to/\@CAPTIVE") {
         my $v;
         $v = readlink "$to/\@AUTODELETE" and $autodelete{$to} = $v;
@@ -1729,13 +1829,13 @@ sub parse_request {
       }
     }
     @to = keys %to;
-    
+
     if (scalar(@to) == 1) {
-      $to = "@to";        
+      $to = "@to";
       $keep       = $keep{$to}       if $keep{$to};
       $autodelete = $autodelete{$to} if $autodelete{$to};
     }
-        
+
     # check recipients and eliminate dupes
     %to = ();
     foreach $to (@to) {
@@ -1746,8 +1846,8 @@ sub parse_request {
           http_die("You cannot send to more than one group") if @to > 1;
           http_die("Group <code>$to</code> does not exist") unless -f "$from/\@GROUP/$1";
         } else {
-          $to .= '@'.$mdomain if $mdomain and $to !~ /@/;
-          if (checkaddress($to)) {
+          if ($skey or $gkey or $okey or checkaddress($to)) {
+            $to .= '@'.$mdomain if $mdomain and $to !~ /@/;
             $to{$to} = untaint($to);
           } else {
             http_die("<code>$to</code> is not a valid e-mail address");
@@ -1774,7 +1874,7 @@ sub showstatus {
   my ($t0,$t1,$t2,$tt,$ts,$tm);
   my ($osize,$percent,$npercent);
   local $_;
-  
+
   $wclose = '<p><a href="#" onclick="window.close()">close</a>'."\n".
             '</body></html>'."\n";
   $ukey   = "$ukeydir/$uid";
@@ -1785,10 +1885,10 @@ sub showstatus {
     sleep 1;
     $tsize = readlink $sfile and last;
     # upload error?
-    # remark: stupid Internet Explorer *needs* the error represented in this 
+    # remark: stupid Internet Explorer *needs* the error represented in this
     # asynchronous popup window, because it cannot display the error in the
     # main window on HTTP POST!
-    if (-f $ukey and open $ukey,'<',$ukey or 
+    if (-f $ukey and open $ukey,'<',$ukey or
         -f "$ukey/error" and open $ukey,'<',"$ukey/error") {
       undef $/;
       unlink $ukey;
@@ -1796,7 +1896,7 @@ sub showstatus {
     }
   }
   # unlink $sfile;
-  
+
   if (defined $tsize and $tsize == 0) {
     print "<script type='text/javascript'>window.close()</script>\n";
     exit;
@@ -1806,7 +1906,7 @@ sub showstatus {
                "no file data received - does your file exist or is it >2GB?")
   }
   html_error($error,"file size unknown") unless $tsize =~ /^\d+$/;
-  
+
   http_header('200 OK');
   if (open $ukey,'<',"$ukey/filename") {
     local $/;
@@ -1814,14 +1914,14 @@ sub showstatus {
     close $ukey;
   }
   http_die("no filename?!") unless $file;
-  
+
   my $ssize = $tsize;
   if ($ssize<2097152) {
     $ssize = sprintf "%d kB",int($ssize/1024);
   } else {
     $ssize = sprintf "%d MB",int($ssize/1048576);
   }
-  
+
   pq(qq(
     "<html><body>"
     "<center>"
@@ -1833,7 +1933,7 @@ sub showstatus {
     "<div style='float:left;width:0%;background:black;height:20px;' id='bar'>"
     "</div></div>"
   ));
-    
+
   # wait for upload file
   for (1..9) {
     last if -f $upload or -f $data;
@@ -1844,13 +1944,16 @@ sub showstatus {
     print $wclose;
     exit;
   }
-  
-  $SIG{ALRM} = sub { die "TIMEOUT in showstatus: no (more) data received\n" };
+
+  $SIG{ALRM} = sub {
+    $SIG{__DIE__} = 'DEFAULT';
+    die "TIMEOUT in showstatus: no (more) data received\n";
+  };
   alarm($timeout*2);
-  
+
   $t0 = $t1 = time;
   $osize = $percent = $npercent = 0;
-  
+
   for ($percent = 0; $percent<100; sleep(1)) {
     $t2 = time;
     $nsize = -s $upload;
@@ -1874,7 +1977,7 @@ sub showstatus {
     # so, updating more often is contra-productive
     if ($t2>$t1+5 or $npercent>$percent) {
       $percent = $npercent;
-      $t1 = $t2; 
+      $t1 = $t2;
       $tm = int(($t2-$t0)/60);
       $ts = $t2-$t0-$tm*60;
       $tt = sprintf("%d:%02d",$tm,$ts);
@@ -1886,7 +1989,7 @@ sub showstatus {
       )) or last;
     }
   }
-  
+
   alarm(0);
   if ($npercent == 100) {
     print "<h3>file successfully transferred</h3>\n";
@@ -1931,26 +2034,26 @@ sub get_file {
           http_die("<code>$filed</code> locked: a download is currently in progress");
       }
     }
-    
+
     # prepare upload
     foreach $to (@to) {
       $to =~ s/:\w+=.*//; # remove options from address
       $filed = "$to/$from/$fkey";
       $nupload = "$filed/upload"; # upload for next recipient
       mkdirp($filed);
-      
+
       # upload already prepared (for first recipient)?
       if ($upload) {
         # link upload for next recipient
         unless ($upload eq $nupload or
                 -r $upload and -r $nupload and
-                (stat $upload)[1] == (stat $nupload)[1]) 
+                (stat $upload)[1] == (stat $nupload)[1])
         {
           unlink $nupload;
           link $upload,$nupload;
         }
-      } 
-      
+      }
+
       # first recipient => create upload
       else {
         $upload = $nupload;
@@ -2002,7 +2105,7 @@ sub get_file {
           symlink "../$filed","$ukeydir/$uid";
         }
       }
-      
+
       unlink "$filed/autodelete",
              "$filed/error",
              "$filed/restrictions",
@@ -2014,10 +2117,11 @@ sub get_file {
              "$filed/speed",
              "$filed/replyto",
              "$filed/useragent",
+             "$filed/uurl",
              "$filed/comment",
              "$filed/notify";
       unlink "$filed/size" unless $seek;
-    
+
       # showstatus needs file name and size
       # fexsend needs full file size (+$seek)
       $fh = "$filed/filename";
@@ -2026,24 +2130,29 @@ sub get_file {
       close $fh;
       if ($::filesize > 0 or $cl > 0) {
         if ($::filesize > 0) { $filesize = $fpsize || $::filesize }
-        else                 { $filesize = $cl-$rb-$ebl+$seek }
+        else                 { $filesize = $cl-$RB-$ebl+$seek }
         # new file
         unless ($seek) {
           if ($::filesize > 0) {
             # total file size as reported by POST
-            mksymlink("$filed/size",$::filesize) 
+            mksymlink("$filed/size",$::filesize)
               or die "cannot write $filed/size - $!\n";
           } else {
             # file size as counted
-            mksymlink("$filed/size",$filesize) 
+            mksymlink("$filed/size",$filesize)
               or die "cannot write $filed/size - $!\n";
           }
         }
       }
-    
-      $autodelete{$to} = $autodelete unless $autodelete{$to};
-      if ($autodelete{$to} =~ /^(DELAY|NO|\d+)$/i) {
-        mksymlink("$filed/autodelete",$autodelete{$to});
+
+      if ($from eq "@to") {
+        # special "fex yourself"
+        mksymlink("$filed/autodelete",$specific{'autodelete'}||'NO');
+      } else {
+        $autodelete{$to} = $autodelete unless $autodelete{$to};
+        if ($autodelete{$to} =~ /^(DELAY|NO|\d+)$/i) {
+          mksymlink("$filed/autodelete",$autodelete{$to});
+        }
       }
 
       if (my $keep = $keep{$to} || $::keep) {
@@ -2051,6 +2160,9 @@ sub get_file {
       }
       mksymlink("$filed/id",$fileid) if $fileid;
       mksymlink("$filed/ip",$ra)     if $ra;
+      if (my $uurl = $ENV{REQUEST_URL}) {
+        mksymlink("$filed/uurl",$uurl);
+      }
       if ($http_client and open $http_client,'>',"$filed/useragent") {
         print {$http_client} $http_client,"\n";
         close $http_client;
@@ -2065,24 +2177,24 @@ sub get_file {
       if ($replyto and $replyto =~ /.@./) {
         mksymlink("$filed/replyto",$replyto);
       }
-    
+
       my $arh = "$from/\@ALLOWED_RHOSTS";
       if (-s $arh) {
         copy($arh,"$filed/restrictions");
       }
-      
+
       if (@header and open $fh,'>',"$filed/header") {
         print {$fh} join("\n",@header),"\n";
         close $fh;
       }
-    
+
       if ((readlink "$to/\@NOTIFICATION"||'') =~ /^no/i) {
         $nomail{$to} = 'NOTIFICATION';
       }
 
       if ($nomail) {
         open $fh,'>',"$filed/notify" and close $fh;
-      } 
+      }
       if ($comment) {
         if (open $fh,'>',"$filed/comment") {
           print {$fh} encode_utf8($comment);
@@ -2094,17 +2206,17 @@ sub get_file {
       unless ($dkey = readlink("$filed/dkey") and -l "$dkeydir/$dkey") {
         $dkey = randstring(8);
         unlink "$dkeydir/$dkey";
-        symlink "../$filed","$dkeydir/$dkey" 
+        symlink "../$filed","$dkeydir/$dkey"
           or http_die("cannot symlink $dkeydir/$dkey ($!)");
         unlink "$filed/dkey";
         symlink $dkey,"$filed/dkey";
       }
-    
+
     }
 
     # extra download (XKEY)?
     if ($anonymous and $fkey =~ /^afex_\d/ or
-        $from eq "@to" and $comment =~ s:^//(.*)$:NOMAIL:) 
+        $from eq "@to" and $comment =~ s:^//(.*)$:NOMAIL:)
     {
       $xkey = $1||$fkey;
       $nomail = $comment;
@@ -2113,15 +2225,15 @@ sub get_file {
         if (-e $x) {
           http_die("extra download key $xkey already exists");
         }
-        symlink "../$from/$from/$fkey",$x 
+        symlink "../$from/$from/$fkey",$x
           or http_die("cannot symlink $x - $!\n");
         unlink "$x/xkey";
         symlink $xkey,"$x/xkey";
       }
     }
-    
+
   }
-  
+
   # file link?
   if ($flink) {
     # upload link has been already created, no data to read any more
@@ -2135,52 +2247,53 @@ sub get_file {
 
     # at last, read (real) file data
     $t0 = time();
-  
+
     # streaming data?
     if ($cl == -1) {
       alarm($timeout*2);
       # read until EOF, including MIME end boundary
+      # note: cannot use sysread because of previous buffered read!
       while ($n = read(STDIN,$_,$bs)) {
-        $rb += $n;
+        $RB += $n;
         $fb += $n;
         syswrite $upload,$_ unless $nostore;
         alarm($timeout*2);
       }
       # size of transferred file, without end boundary
       $ndata = untaint($fb-$ebl);
-    } 
-    
+    }
+
     # normal file with known file size
     else {
-      
+
       if ($fpsize) {
         debuglog(sprintf("still awaiting %d+%d = %d bytes",
                  $fpsize,$ebl,$fpsize+$ebl));
-        $cl = $rb+$fpsize+$ebl; # recalculate CONTENT_LENGTH
+        $cl = $RB+$fpsize+$ebl; # recalculate CONTENT_LENGTH
       } else {
         if ($::filesize) {
-          $cl = $rb+$::filesize+$ebl; # recalculate CONTENT_LENGTH
+          $cl = $RB+$::filesize+$ebl; # recalculate CONTENT_LENGTH
         }
         debuglog(sprintf("still awaiting %d-%d = %d bytes",
-                         $cl,$rb,$cl-$rb));
+                         $cl,$RB,$cl-$RB));
       }
       # read until end boundary, not EOF
-      while ($rb < $cl-$ebl) {
-        $b = $cl-$ebl-$rb; 
+      while ($RB < $cl-$ebl) {
+        $b = $cl-$ebl-$RB;
         $b = $bs if $b > $bs;
         # max wait for 1 kB/s, but at least 10 s
         # $timeout = $b/1024;
         # $timeout = 10 if $timeout < 10;
         alarm($timeout);
         if ($n = read(STDIN,$_,$b)) {
-          $rb += $n;
+          $RB += $n;
           $fb += $n;
           # syswrite is much faster than print
           syswrite $upload,$_ unless $nostore;
           if ($bwlimit) {
             alarm(0);
             $tt = (time-$t0) || 1;
-            while ($rb/$tt/1024 > $bwlimit) {
+            while ($RB/$tt/1024 > $bwlimit) {
               sleep 1;
               $tt = time-$t0;
             }
@@ -2198,19 +2311,19 @@ sub get_file {
           http_die("found no MIME end boundary in upload ($_)");
         }
       }
-      $rb += $ebl;
+      $RB += $ebl;
       $ndata = untaint($fb);
-    } 
+    }
 
     alarm(0);
-  
+
     unless ($nostore) {
       close $upload; # or die "cannot close $upload - $!\n";;
-  
+
       # throuput in kB/s
       $tt = (time-$t0) || 1;
       mksymlink("$filed/speed",int($fb/1024/$tt));
-      
+
       unless ($ndata) {
         http_die(
           "No file data received!".
@@ -2218,12 +2331,12 @@ sub get_file {
           " File too big (browser-limit: 2 GB!)?"
         );
       }
-      
+
       $to = join(',',@to);
-    
+
       # streaming upload?
       if ($cl == -1) {
-      
+
         open $upload,'<',$upload or http_die("internal error - cannot read upload");
         seek $upload,$ndata+2,0;
         $_ = <$upload>||'';
@@ -2232,34 +2345,34 @@ sub get_file {
         }
         close $upload;
         truncate $upload,$ndata;
-        
+
       } else {
-      
+
         # truncate boundary string
         # truncate $upload,$ndata+$uss if -s $upload > $ndata+$uss;
-      
+
         # incomplete?
-        if ($cl != $rb) {
+        if ($cl != $RB) {
           fuplog($to,$fkey,$ndata,'(aborted)');
           if ($fpsize) {
-            http_die("read $rb bytes, but Content-Length announces $fpsize bytes");
+            http_die("read $RB bytes, but Content-Length announces $fpsize bytes");
           } else {
-            http_die("read $rb bytes, but CONTENT_LENGTH announces $cl bytes");
+            http_die("read $RB bytes, but CONTENT_LENGTH announces $cl bytes");
           }
         }
-      
+
         # multipost, not complete
         if ($::filesize > -s $upload) {
           http_header('206 Partial OK');
           exit;
         }
-      
+
         # save error?
         if (-s $upload > ($::filesize||$filesize)) {
           fuplog($to,$fkey,$ndata,'(write error: upload > filesize)');
           http_die("internal server error while writing file data");
         }
-      
+
       }
       fuplog($to,$fkey,$ndata);
       debuglog("upload successfull, dkey=$dkey");
@@ -2274,7 +2387,7 @@ sub check_rr {
   my @to = @_;
   my $rr = "$from/\@ALLOWED_RECIPIENTS";
   my ($allowed,$to,$ar,$rd);
-  
+
   if (-s $rr and open $rr,'<',$rr) {
 
     $restricted = $rr;
@@ -2287,7 +2400,7 @@ sub check_rr {
         chomp;
         s/#.*//;
         s/\s//g;
-        
+
         if (/^\@LOCAL_RDOMAINS/) {
           $ar = '(@';
           foreach (@local_rdomains) {
@@ -2298,26 +2411,29 @@ sub check_rr {
             $ar .= '|[^\@]+\@' . $rd;
           }
           $ar .= ')';
+        } elsif (/^\@LOCAL_USERS/ and -s "$to/@") {
+          $allowed = 1;
+          last;
         } else {
           # allow wildcard *, but not regexps
           $ar = quotemeta $_;
           $ar =~ s/\\\*/[^@]*/g;
         }
-        
+
         if ($to =~ /^$ar$/i) {
           $allowed = 1;
           last;
         }
-        
+
       }
-      
+
       unless ($allowed) {
         fuplog("ERROR: $from not allowed to fex to $to");
         debuglog("$to not in $spooldir/$from/\@ALLOWED_RECIPIENTS");
         http_die("You ($from) are not allowed to fex to $to");
       }
     }
-    
+
     close $rr;
   }
 }
@@ -2327,25 +2443,24 @@ sub check_rr {
 sub expand {
   my @users = @_;
   my @ua;
-  
-  foreach (@users) {
-    my $u = $_;
-    if ($u =~ /^anonymous(_\d+)?$/) { 
+
+  foreach my $u (my @loop = @users) {
+    if ($u =~ /^anonymous(_\d+)?$/) {
       $u = "$u\@$hostname";
     }
-    if ($u eq 'nettest') { 
+    if ($u eq 'nettest') {
       if ($mdomain and -d "$u\@$mdomain") {
         $u .= "\@$mdomain"
       } elsif (-d "$u\@$hostname") {
-        $u .= "\@$hostname"    
+        $u .= "\@$hostname"
       }
     }
-    if    ($u =~ /@/)          { push @ua,$u } 
-    elsif ($mdomain)           { push @ua,"$u\@$mdomain" } 
-    elsif (-d "$u\@$hostname") { push @ua,"$u\@$hostname" } 
+    if    ($u =~ /@/)          { push @ua,$u }
+    elsif ($mdomain)           { push @ua,"$u\@$mdomain" }
+    elsif (-d "$u\@$hostname") { push @ua,"$u\@$hostname" }
     else                       { push @ua,$u }
   }
-  
+
   return wantarray ? @ua : join(',',@ua);
 }
 
@@ -2354,11 +2469,14 @@ sub expand {
 sub forward {
   my $file = shift;
   my ($nfile,$to,$AB);
-  my ($filename);
+  my ($filename,$keep);
   my (%to);
 
   http_die("no file data for <code>$file</code>") unless -f "$file/data";
 
+  $keep = $::keep||$keep_default;
+  if (my $mt = mtime("$file/data")) { $keep += int((time-$mt)/$DS) }
+
   if (@to) {
 
     # check recipients restriction
@@ -2381,8 +2499,7 @@ sub forward {
     }
 
     # collect addresses
-    foreach (@to) {
-      my $to = $_;
+    foreach my $to (my @loop = @to) {
       if ($ab{$to}) {
         foreach my $address (@{$ab{$to}}) {
           $to{$address} = $address;
@@ -2393,13 +2510,12 @@ sub forward {
       }
     }
 
+    @to = keys %to;
+
     http_header('200 OK');
     print html_header($head);
 
-    @to = keys %to;
-    
-    foreach (@to) {
-      my $to = $_;
+    foreach my $to (my @loop = @to) {
       $to =~ s/:\w+=.*//; # remove options from address
       $nfile = $file;
       $nfile =~ s:.*?/:$to/:;
@@ -2425,26 +2541,26 @@ sub forward {
         close $comment;
       }
       if ($autodelete =~ /^(DELAY|NO|\d+)$/i) {
-        symlink($autodelete,"$nfile/autodelete");
-      }
-      symlink($keep||$keep_default,         "$nfile/keep");
-                    copy("$file/id",        "$nfile/id");
-                    copy("$file/ip",        "$nfile/ip");
-                    copy("$file/speed",     "$nfile/speed");
-                    copy("$file/replyto",   "$nfile/replyto");
-      $filename   = copy("$file/filename",  "$nfile/filename");
-      link               "$file/data",      "$nfile/data"
+        symlink $autodelete,"$nfile/autodelete";
+      }
+      symlink          $keep,             "$nfile/keep";
+                  copy("$file/id",        "$nfile/id");
+                  copy("$file/ip",        "$nfile/ip");
+                  copy("$file/speed",     "$nfile/speed");
+                  copy("$file/replyto",   "$nfile/replyto");
+      $filename = copy("$file/filename",  "$nfile/filename");
+      link             "$file/data",      "$nfile/data"
         or die http_die("cannot create $nfile/data - $!");
       unless ($dkey = readlink("$nfile/dkey") and -l "$dkeydir/$dkey") {
         $dkey = randstring(8);
         unlink "$dkeydir/$dkey";
-        symlink "../$nfile","$dkeydir/$dkey" 
+        symlink "../$nfile","$dkeydir/$dkey"
           or http_die("cannot symlink $dkeydir/$dkey");
         unlink "$nfile/dkey";
-        symlink $dkey,"$nfile/dkey" 
+        symlink $dkey,"$nfile/dkey"
           or http_die("cannot create $nfile/dkey - $!");
       }
-      
+
       if ($nomail or $nomail{$to}) {
         if ($filename) {
           my $url = "$durl/$dkey/".normalize_filename($filename);
@@ -2535,34 +2651,11 @@ sub calcsize {
 }
 
 
-# read one line from STDIN (net socket) and assign it to $_
-# returns number of read bytes
-sub nvt_read {
-  my $len = 0;
-
-  if (defined ($_ = <STDIN>)) {
-    debuglog($_);
-    $len = length;
-    $rb += $len;
-    s/\r?\n//;
-  }
-  return $len;
-}
-
-
-# read forward to given pattern
-sub nvt_skip_to {
-  my $pattern = shift;
-
-  while (&nvt_read) { return if /$pattern/ }
-}
-
-
 # set parameter variables
 sub setparam {
   my ($v,$vv) = @_;
   my ($idf,$to);
-  
+
   $v = uc(despace($v));
 
 #  if ($vv =~ /([<>])/) {
@@ -2584,31 +2677,39 @@ sub setparam {
         'Content-Length: 0',
         ""
       );
-      &reexec;
+    } else {
+      nvt_print(
+        "HTTP/1.1 302 Found",
+        "Location: $ENV{PROTO}://$ENV{HTTP_HOST}/fup",
+        'Content-Length: 0',
+        ""
+      );
     }
+    &reexec;
   } elsif ($v eq 'LOCALE' and $vv =~ /^(\w+)$/) {
     $locale = $1;
   } elsif ($v eq 'REDIRECT' and $vv =~ /^([\w?=]+)$/) {
     $redirect = $1;
-  } elsif (($v eq 'KEY' or $v eq 'SKEY') and $vv =~ /^([\w:]+)$/) { 
+  } elsif ($v eq 'SKEY' and $vv =~ /^([\w:]+)/) {
     $skey = $1;
     $restricted = $v;
-  } elsif ($v eq 'GKEY' and $vv =~ /^([\w:]+)$/) { 
+  } elsif ($v eq 'GKEY' and $vv =~ /^([\w:]+)/) {
     $gkey = $1 unless $nomail;
     $restricted = $v;
-  } elsif ($v eq 'DKEY' and $vv =~ /^(\w+)$/) { 
+  } elsif ($v eq 'DKEY' and $vv =~ /^(\w+)/) {
     $dkey = $1;
-  } elsif ($v eq 'AKEY' and $vv =~ /^(\w+)$/) { 
+  } elsif ($v eq 'AKEY' and $vv =~ /^(\w+)/) {
     $akey = $1;
-  } elsif ($v eq 'FROM' or $v eq 'USER') { 
+  } elsif ($v eq 'FROM' or $v eq 'USER') {
     $from = normalize_email($vv);
     $from = untaint(expand($from));
     checkchars('from address',$from);
-    checkaddress($from) or http_die("FROM $from is no legal e-mail address");
-  } elsif ($v eq 'REPLYTO') { 
+    # maybe FROM=SUBUSER !
+    # checkaddress($from) or http_die("FROM $from is no legal e-mail address");
+  } elsif ($v eq 'REPLYTO') {
     $replyto = normalize_email($vv);
     checkchars('replyto address',$replyto);
-    checkaddress($replyto) or 
+    checkaddress($replyto) or
       http_die("REPLYTO $replyto is no legal e-mail address");
   } elsif ($v eq 'ADDTO') {
     $vv =~ s/\s.*//;
@@ -2618,6 +2719,7 @@ sub setparam {
   } elsif ($v eq 'FEXYOURSELF') {
     $submit = $vv;
     @to = ($from);
+    $specific{'autodelete'} = $autodelete = 'no';
   } elsif ($v eq 'TO') {
     # extract AUTODELETE and KEEP options
     if ($vv =~ s/[\s,]+AUTODELETE=(\w+)//i) {
@@ -2633,9 +2735,15 @@ sub setparam {
     if ($from) {
       if ($to eq '.') {
         $to = $from;
+         unless ($specific{'autodelete'}) {
+           $specific{'autodelete'} = $autodelete = 'no';
+         }
       }
       if ($to eq '//') {
         $to = $from;
+        unless ($specific{'autodelete'}) {
+          $specific{'autodelete'} = $autodelete = 'no';
+        }
         $comment = '//';
       }
     }
@@ -2680,7 +2788,7 @@ sub setparam {
   } elsif ($v eq 'SEEK' and $vv =~ /^(\d+)$/) {
     $seek = $1;
   } elsif ($v eq 'FILESIZE' and $vv =~ /^(\d+)$/) {
-    $filesize = $1; # complete filesize! 
+    $filesize = $1; # complete filesize!
     &check_space($filesize-$seek);
   } elsif ($v eq 'AUTODELETE' and $vv =~ /^(\w+)$/) {
     $specific{'autodelete'} = $autodelete = uc($1);
@@ -2689,19 +2797,19 @@ sub setparam {
     $keep = $keep_max if $keep_max and $keep > $keep_max;
     $specific{'keep'} = $keep;
   } elsif ($v eq 'TIMEOUT' and $vv =~ /^(\d+)$/) {
-    $specific{'timeout'} = $timeout = $1;     
+     $specific{'timeout'} = $timeout = $1;
   }
 }
 
 
 sub id_forgotten {
   my ($id,$to,$subuser,$gm,$skey,$gkey,$url,$fup);
-  
+
   return if $nomail;
-  
+
   $fup = $durl;
   $fup =~ s:/fop:/fup:;
-  
+
   # full user
   if (open $from,'<',"$from/\@") {
     $id = getline($from);
@@ -2718,7 +2826,7 @@ sub id_forgotten {
     )));
     exit;
   }
-  
+
   # sub user
   foreach my $skey (glob("$skeydir/*")) {
     if (-f $skey and open $skey,'<',$skey) {
@@ -2741,7 +2849,7 @@ sub id_forgotten {
       exit;
     }
   }
-  
+
   # group user
   foreach my $gkey (glob("$gkeydir/*")) {
     if (-f $gkey and open $gkey,'<',$gkey) {
@@ -2795,6 +2903,11 @@ sub mail_forgotten {
 # lookup akey, skey and gkey (full and sub user and group)
 sub check_keys {
 
+  if (@to and "@to" ne '_') {
+    http_die("you cannot mix TO and SKEY URL parameters") if $skey;
+    http_die("you cannot mix TO and GKEY URL parameters") if $gkey;
+  }
+
   # only one key can be valid
   $akey = $gkey = '' if $skey;
   $akey = $skey = '' if $gkey;
@@ -2863,7 +2976,7 @@ sub check_keys {
     # sid is not set with web browser
     # akey with sid is set with schwuppdiwupp & co
     $idf = "$akeydir/$akey/@";
-    
+
     if (open $idf,'<',$idf and $id = getline($idf)) {
       close $idf;
       $from = readlink "$akeydir/$akey"
@@ -2886,12 +2999,12 @@ sub check_space {
   my $req = shift;
   my ($df,$free,$uprq);
   local *P;
-  
+
   if (open $df,"df -k $spooldir|") {
     while (<$df>) {
       if (/^.+?\s+\d+\s+\d+\s+(\d+)/ and $req/1024 > $1) {
         $free = int($1/1024);
-        $uprq = int($req/M);
+        $uprq = int($req/$MB);
         if (not $nomail and open P,"|$sendmail -t") {
           pq(P,qq(
             'From: $admin'
@@ -2915,10 +3028,10 @@ sub check_space {
 
 
 # global substitution as a function like in gawk
-sub gsub { 
+sub gsub {
   local $_ = shift;
-  my ($p,$r) = @_; 
-  s/$p/$r/g; 
+  my ($p,$r) = @_;
+  s/$p/$r/g;
   return $_;
 }
 
@@ -2926,17 +3039,12 @@ sub gsub {
 # standard log
 sub fuplog {
   my $msg = "@_";
-  
+
   $msg =~ s/\n/ /g;
   $msg =~ s/\s+$//;
-  
-  if (open $log,'>>',$log) {
-    flock $log,LOCK_EX;
-    seek $log,0,SEEK_END;
-    printf {$log} "%s [%s_%s] %s (%s) %s\n",
-                  isodate(time),$$,$ENV{REQUESTCOUNT},$from,$fra,$msg;
-    close $log;
-  }
+  $msg = sprintf "%s [%s_%s] %s (%s) %s\n",
+                 isodate(time),$$,$ENV{REQUESTCOUNT},$from,$fra,$msg;
+  writelog($log,$msg);
 }
 
 
@@ -2958,19 +3066,17 @@ sub sigexit {
   $msg = @_ ? "@_" : '???';
   $msg =~ s/\n/ /g;
   $msg =~ s/\s+$//;
-  
-  if (open $log,'>>',$log) {
-    printf {$log} 
-           "%s %s (%s) %s %s caught SIGNAL %s %s\n",
-           isodate(time),
-           $from||'-',
-           $fra||'-',
-           $to||'-',
-           encode_Q($file||'-'),
-           $msg,
-           $rb?"(after $rb bytes)":"";
-    close $log;
-  }
+  $msg = sprintf "%s %s (%s) %s %s caught SIGNAL %s %s\n",
+                 isodate(time),
+                 $from||'-',
+                 $fra||'-',
+                 $to||'-',
+                 encode_Q($file||'-'),
+                 $msg,
+                 $RB?"(after $RB bytes)":"";
+
+  writelog($log,$msg);
+
   if ($sig eq 'DIE') {
     shift;
     die "$msg\n";
@@ -2980,24 +3086,18 @@ sub sigexit {
 }
 
 
-sub mtime {
-  my @s = lstat shift;
-  return @s ? $s[9] : undef;
-}
-
-
 sub present_locales {
   my $url = shift;
   my @locales = @::locales; # from fex.ph
   my ($locale,$lang);
-  
-  if ($url =~ /\?/) { 
+
+  if ($url =~ /\?/) {
     $url .= "&";
     $url =~ s/locale=\w+&//g;
-  } else { 
+  } else {
     $url .= "?";
   }
-  
+
   if (@locales) {
     map { $_ = "$FEXHOME/locale/$_" } @locales;
   } else {
@@ -3006,8 +3106,7 @@ sub present_locales {
 
   if (@locales > 1) {
     print "<h3>";
-    foreach (@locales) {
-      $locale = $_;
+    foreach my $locale (my @loop = @locales) {
       if (-x "$locale/cgi-bin/fup") {
         $lang = "$locale/lang.html";
         $locale =~ s:.*/::;
@@ -3027,7 +3126,7 @@ sub present_locales {
 sub check_camel {
   my ($logo,$camel);
   local $/;
-  
+
   if (open $logo,"$docdir/logo.jpg") {
     $camel = md5_hex(<$logo>) eq 'ad8a95bba8dd1a61d70bd38611bc2059';
   }