4 use Fcntl               qw':flock :seek :mode';
 
   8 use Digest::MD5         qw'md5_hex';
 
  11 use Symbol              qw'gensym';
 
  13 # set and untaint ENV if not in CLI (fexsrv provides clean ENV)
 
  15   foreach my $v (keys %ENV) {
 
  16     ($ENV{$v}) = ($ENV{$v} =~ /(.*)/s) if defined $ENV{$v};
 
  18   $ENV{PATH}     = '/usr/local/bin:/bin:/usr/bin';
 
  23 unless ($FEXLIB = $ENV{FEXLIB} and -d $FEXLIB) {
 
  24   die "$0: found no FEXLIB - fexsrv needs full path\n"
 
  30 # $FEXHOME is top-level directory of F*EX installation or vhost
 
  31 # $ENV{HOME} is login-directory of user fex
 
  32 # in default-installation both are equal, but they may differ
 
  33 $FEXHOME = $ENV{FEXHOME} or $ENV{FEXHOME} = $FEXHOME = dirname($FEXLIB);
 
  38 $hostname = gethostname();
 
  39 $tmpdir = $ENV{TMPDIR} || '/var/tmp';
 
  40 $spooldir = $FEXHOME.'/spool';
 
  41 $docdir = $FEXHOME.'/htdocs';
 
  45 $limited_download = 'YES';      # multiple downloads only from same client
 
  46 $fex_yourself = 'YES';          # allow SENDER = RECIPIENT
 
  48 $recipient_quota = 0;           # MB
 
  49 $sender_quota = 0;              # MB
 
  50 $timeout = 30;                  # seconds
 
  51 $bs = 2**16;                    # I/O blocksize
 
  52 $DS = 60*60*24;                 # seconds in a day
 
  53 $MB = 1024*1024;                # binary Mega
 
  55 $sendmail = '/usr/lib/sendmail';
 
  56 $sendmail = '/usr/sbin/sendmail' unless -x $sendmail;
 
  65 $FHS = -f '/etc/fex/fex.ph' and -d '/usr/share/fex/lib';
 
  68   $ENV{FEXHOME} = $FEXHOME = '/usr/share/fex';
 
  69   $spooldir = '/var/spool/fex';
 
  70   $logdir = '/var/log/fex';
 
  71   $docdir = '/var/lib/fex/htdocs';
 
  72   $notify_newrelease = '';
 
  75 # allowed download managers (HTTP User-Agent)
 
  76 $adlm = '^(Axel|fex)';
 
  79 require "$FEXLIB/fex.ph" or die "$0: cannot load $FEXLIB/fex.ph - $!";
 
  81 $fop_auth       = 0 if $fop_auth        =~ /no/i;
 
  82 $mail_authid    = 0 if $mail_authid     =~ /no/i;
 
  83 $force_https    = 0 if $force_https     =~ /no/i;
 
  84 $debug          = 0 if $debug           =~ /no/i;
 
  86 @logdir = ($logdir) unless @logdir;
 
  89 # allowed multi download recipients: from any ip, any times
 
  91   $amdl = '^('.join('|',map { quotewild($_) } @mailing_lists).')$';
 
  96 # check for name based virtual host
 
  97 $vhost = vhost($ENV{'HTTP_HOST'});
 
  99 $RB = 0; # read POST bytes
 
 101 push @doc_dirs,$docdir;
 
 102 foreach my $ld (glob "$FEXHOME/locale/*/htdocs") {
 
 106 $nomail = ($mailmode =~ /^MANUAL|nomail$/i);
 
 108 if (not $nomail and not -x $sendmail) {
 
 109   http_die("found no sendmail");
 
 111 http_die("cannot determine the server hostname") unless $hostname;
 
 113 $ENV{PROTO} = 'http' unless $ENV{PROTO};
 
 114 $keep = $keep_default ||= $keep || 5;
 
 115 $fra = $ENV{REMOTE_ADDR} || '';
 
 116 $sid = $ENV{SID} || '';
 
 118 mkdirp($dkeydir = "$spooldir/.dkeys"); # download keys
 
 119 mkdirp($ukeydir = "$spooldir/.ukeys"); # upload keys
 
 120 mkdirp($akeydir = "$spooldir/.akeys"); # authentification keys
 
 121 mkdirp($skeydir = "$spooldir/.skeys"); # subuser authentification keys
 
 122 mkdirp($gkeydir = "$spooldir/.gkeys"); # group authentification keys
 
 123 mkdirp($xkeydir = "$spooldir/.xkeys"); # extra download keys
 
 124 mkdirp($lockdir = "$spooldir/.locks"); # download lock files
 
 126 if (my $ra = $ENV{REMOTE_ADDR} and $max_fail) {
 
 127   mkdirp("$spooldir/.fail");
 
 128   $faillog = "$spooldir/.fail/$ra";
 
 132   $admin = $ENV{SERVER_ADMIN} ? $ENV{SERVER_ADMIN} : 'fex@'.$hostname;
 
 135 # $ENV{SERVER_ADMIN} may be set empty in fex.ph!
 
 136 $ENV{SERVER_ADMIN} = $admin unless defined $ENV{SERVER_ADMIN};
 
 141   if (my $cookie = $ENV{HTTP_COOKIE}) {
 
 142     if    ($cookie =~ /\bakey=(\w+)/) { $akey = $1 }
 
 143     # elsif ($cookie =~ /\bskey=(\w+)/) { $skey = $1 }
 
 148   if ($default_locale and not grep /^$default_locale$/,@locales) {
 
 149     push @locales,$default_locale;
 
 152     $default_locale = $locales[0];
 
 156 $default_locale ||= 'english';
 
 158 # $durl is first default fop download URL
 
 159 # @durl is optional mandatory fop download URL list (from fex.ph)
 
 163   my $xinetd = '/etc/xinetd.d/fex';
 
 167   } elsif ($ENV{HTTP_HOST} and $ENV{PROTO}) {
 
 169     ($host,$port) = split(':',$ENV{HTTP_HOST}||'');
 
 174       if (open $xinetd,$xinetd) {
 
 176           if (/^\s*port\s*=\s*(\d+)/) {
 
 185     # use same protocal as uploader for download
 
 186     if ($ENV{PROTO} eq 'https' and $port == 443 or $port == 80) {
 
 187       $durl = "$ENV{PROTO}://$host/fop";
 
 189       $durl = "$ENV{PROTO}://$host:$port/fop";
 
 192     if (open $xinetd,$xinetd) {
 
 194         if (/^\s*port\s*=\s*(\d+)/) {
 
 202       $durl = "http://$hostname/fop";
 
 204       $durl = "http://$hostname:$port/fop";
 
 208 @durl = ($durl) unless @durl;
 
 212   exec($FEXHOME.'/bin/fexsrv') if $ENV{KEEP_ALIVE};
 
 219   $cont = shift || 'request accepted: continue';
 
 221   http_header('200 ok');
 
 222   print html_header($head||$ENV{SERVER_NAME});
 
 224     '<script type="text/javascript">'
 
 225     '  window.location.replace("$url");'
 
 228     '  <h3><a href="$url">$cont</a></h3>'
 
 237   print header(),"<pre>\n";
 
 238   print "file = $file\n";
 
 239   foreach $v (keys %ENV) {
 
 240     print $v,' = "',$ENV{$v},"\"\n";
 
 247   foreach (@_) { syswrite STDOUT,"$_\r\n" }
 
 268   return if $HTTP_HEADER;
 
 269   $HTTP_HEADER = $status;
 
 273   nvt_print("HTTP/1.1 $status");
 
 274   nvt_print("X-Message: $msg");
 
 275   # nvt_print("X-SID: $ENV{SID}") if $ENV{SID};
 
 276   nvt_print("Server: fexsrv");
 
 277   nvt_print("Expires: 0");
 
 278   nvt_print("Cache-Control: no-cache");
 
 279   # http://en.wikipedia.org/wiki/Clickjacking
 
 280   nvt_print("X-Frame-Options: SAMEORIGIN");
 
 282     # https://www.owasp.org/index.php/HTTP_Strict_Transport_Security
 
 283     nvt_print("Strict-Transport-Security: max-age=2851200");
 
 287       nvt_print("Set-Cookie: akey=$akey; Max-Age=9999; Discard");
 
 290     #   nvt_print("Set-Cookie: skey=$skey; Max-Age=9999; Discard");
 
 293       nvt_print("Set-Cookie: locale=$locale");
 
 296   unless (grep /^Content-Type:/i,@_) {
 
 297     # nvt_print("Content-Type: text/html; charset=ISO-8859-1");
 
 298     nvt_print("Content-Type: text/html; charset=UTF-8");
 
 307   my $header = 'header.html';
 
 310   # http://www.w3.org/TR/html401/struct/global.html
 
 311   # http://www.w3.org/International/O-charset
 
 315     '  <meta http-equiv="expires" content="0">'
 
 316     '  <meta http-equiv="Content-Type" content="text/html;charset=utf-8">'
 
 317     '  <title>$title</title>'
 
 320   # '<!-- <style type="text/css">\@import "/fex.css";</style> -->'
 
 322   if ($0 =~ /fexdev/) { $head .= "<body bgcolor=\"pink\">\n" } 
 
 323   else                { $head .= "<body>\n" }
 
 325   $title =~ s:F\*EX:<a href="/index.html">F*EX</a>:;
 
 327   if (open $header,'<',"$docdir/$header") {
 
 328     $head .= $_ while <$header>;
 
 332   $head .= &$prolog($title) if defined($prolog);
 
 336       '<h1><a href="%s"><img align=center src="%s" border=0></a>%s</h1>',
 
 337       $H1_extra[0],$H1_extra[1]||'',$title
 
 340     $head .= "<h1>$title</h1>";
 
 352   my $isodate = isodate(time);
 
 354   $msg =~ s/[\s\n]+/ /g;
 
 355   $msg =~ s/<.+?>//g; # remove HTML
 
 356   map { s/<script.*?>//gi } @msg;
 
 360   # cannot send standard HTTP Status-Code 400, because stupid 
 
 361   # Internet Explorer then refuses to display HTML body!
 
 362   http_header("666 Bad Request - $msg");
 
 363   print html_header($error);
 
 364   print 'ERROR: ',join("<p>\n",@msg),"\n";
 
 370     '  <a href="mailto:$ENV{SERVER_ADMIN}">$ENV{SERVER_ADMIN}</a>'
 
 381   unless ($ENV{GATEWAY_INTERFACE}) {
 
 382     warn "$0: @_\n"; # must not die, because of fex_cleanup!
 
 388   # create special error file on upload
 
 390     my $ukey = "$spooldir/.ukeys/$uid";
 
 391     $ukey .= "/error" if -d $ukey;
 
 393     if (open $ukey,'>',$ukey) {
 
 394       print {$ukey} join("\n",@_),"\n";
 
 399   html_error($error||'',@_);
 
 404   if (my $status = readlink '@MAINTENANCE') {
 
 405     my $isodate = isodate(time);
 
 406     http_header('666 MAINTENANCE');
 
 407     print html_header($head||'');
 
 410       "<h1>Server is in maintenance mode</h1>"
 
 414       "<address>$ENV{HTTP_HOST} $isodate</address>"
 
 426   $user .= '@'.$mdomain if $mdomain and $user !~ /@/;
 
 428   if (-e "$user/\@DISABLED") {
 
 429     my $isodate = isodate(time);
 
 430     http_header('666 DISABLED');
 
 431     print html_header($head);
 
 433       "<h3>$user is disabled</h3>"
 
 434       "Contact $ENV{SERVER_ADMIN} for details"
 
 436       "<address>$ENV{HTTP_HOST} $isodate</address>"
 
 445   my @d = localtime shift;
 
 446   return sprintf('%d-%02d-%02d %02d:%02d:%02d',
 
 447                  $d[5]+1900,$d[4]+1,$d[3],$d[2],$d[1],$d[0]);
 
 453   $s =~ s{([\=\x00-\x20\x7F-\xA0])}{sprintf("=%02X",ord($1))}eog;
 
 458 # from MIME::Base64::Perl
 
 467   return '' unless length;
 
 469   for ($i = 0; $i <= $l; $i += 60) {
 
 470     $uu .= "M" . substr($_,$i,60);
 
 473   $uu .= chr(32+(length)*3/4) . $_ if $_;
 
 474   return unpack ("u",$uu);
 
 478 # short base64 encoding
 
 484   $_ = join '',map(pack('u',$_)=~ /^.(\S*)/, ($_[0]=~/(.{1,45})/gs));
 
 485   tr|` -_|AA-Za-z0-9+/|;
 
 486   $x = (3 - length($_[0]) % 3) % 3;
 
 493 # simulate a "rm -rf", but never removes '..'
 
 494 # return number of removed files
 
 503     next if /(^|\/)\.\.$/;
 
 505     if (-d $file and not -l $file) {
 
 507       opendir D,$dir or next;
 
 508       while ($file = readdir D) {
 
 509         next if $file eq '.' or $file eq '..';
 
 510         $dels += rmrf("$dir/$file");
 
 513       rmdir $dir and $dels++;
 
 515       unlink $file and $dels++;
 
 523   my $hostname = hostname;
 
 528     $_ = `hostname 2>/dev/null`;
 
 529     $hostname = /(.+)/ ? $1 : '';
 
 531   if ($hostname !~ /\./ and open my $rc,'/etc/resolv.conf') {
 
 533       if (/^\s*domain\s+([\w.-]+)/) {
 
 537       if (/^\s*search\s+([\w.-]+)/) {
 
 542     $hostname .= ".$domain" if $domain;
 
 544   if ($hostname !~ /\./ and $admin and $admin =~ /\@([\w.-]+)/) {
 
 552 # strip off path names (Windows or UNIX)
 
 556   s/.*\\// if /^([A-Z]:)?\\/;
 
 563 # substitute all critcal chars
 
 567   return '' unless defined $_;
 
 569   # we need perl native utf8 (see perldoc utf8)
 
 570   $_ = decode_utf8($_) unless utf8::is_utf8($_);
 
 573   s/[\x00-\x1F\x80-\x9F]/_/g;
 
 577   return encode_utf8($_);
 
 581 # substitute all critcal chars
 
 585   return '' unless defined $_;
 
 595 # substitute all critcal chars with underscore
 
 596 sub normalize_filename {
 
 601   # we need native utf8
 
 602   $_ = decode_utf8($_) unless utf8::is_utf8($_);
 
 606   # substitute all critcal chars with underscore
 
 607   s/[^a-zA-Z0-9_=.+-]/_/g;
 
 610   return encode_utf8($_);
 
 614 sub normalize_email {
 
 617   s/[^\w_.+=!~#^\@\-]//g;
 
 627   $user = lc(urldecode(despace($user)));
 
 628   $user .= '@'.$mdomain if $mdomain and $user !~ /@/;
 
 629   checkaddress($user) or http_die("$user is not a valid e-mail address");
 
 630   return untaint($user);
 
 636   s/%([a-f0-9]{2})/chr(hex($1))/gie;
 
 653     http_die("\"$1\" is not allowed at beginning of $input");
 
 655   if (/([\/\"\'\\<>;])/) {
 
 656     http_die(sprintf("\"&#%s;\" is not allowed in %s",ord($1),$input));
 
 659     http_die("\"$1\" is not allowed at end of $input");
 
 662     http_die("control characters are not allowed in $input");
 
 673   local ($domain,$dns);
 
 675   $a =~ s/:\w+=.*//; # remove options from address
 
 677   return $a if $a eq 'anonymous';
 
 679   $a .= '@'.$mdomain if $mdomain and $a !~ /@/;
 
 681   $re = '^[.@-]|@.*@|local(host|domain)$|["\'\`\|\s()<>/;,]';
 
 683     debuglog("$a has illegal syntax ($re)");
 
 686   $re = '^[!^=~#_:.+*{}\w\-\[\]]+\@(\w[.\w\-]*\.[a-z]+)$';
 
 690       local $SIG{__DIE__} = sub { die "\n" };
 
 693         $dns = Net::DNS::Resolver->new->query($domain)||mx($domain);
 
 694         unless ($dns or mx('uni-stuttgart.de')) {
 
 695           http_die("Internal error: bad resolver");
 
 702       debuglog("no A or MX DNS record found for $domain");
 
 706     debuglog("$a does not match e-mail regexp ($re)");
 
 712 # check forbidden addresses
 
 718   $a .= '@'.$mdomain if $mdomain and $a !~ /@/;
 
 719   return $a if -d "$spooldir/$a"; # ok, if user already exists
 
 720   if (@forbidden_recipients) {
 
 721     foreach (@forbidden_recipients) {
 
 723       # skip public recipients
 
 724       if (@public_recipients) {
 
 725         foreach $pr (@public_recipients) {
 
 726           return $a if $a eq lc $pr;
 
 729       return '' if $a =~ /^$fr$/i;
 
 738   my @rc = ('A'..'Z','a'..'z',0..9 ); 
 
 742   for (1..$n) { $rs .= $rc[int(rand($rn))] };
 
 754   http_die("cannot mkdir /") unless $dir;
 
 756   if ($pdir =~ s:/[^/]+$::) {
 
 757     mkdirp($pdir) unless -d $pdir;
 
 760     mkdir $dir,0770 or http_die("mkdir $dir - $!");
 
 769   if ($rid and $ENV{SID} and $id =~ /^MD5H:/) {
 
 770     $rid = 'MD5H:'.md5_hex($rid.$ENV{SID});
 
 776 # test if ip is in iplist (ipv4/ipv6)
 
 777 # iplist is an array with ips and ip-ranges
 
 786     if ($ip =~ /\./ and $i =~ /\./ or $ip =~ /:/ and $i =~ /:/) {
 
 787       if ($i =~ /(.+)-(.+)/) {
 
 791         return $ip if $ipe ge $ia and $ipe le $ib;
 
 793         return $ip if $ipe eq ipe($i);
 
 800 # ip expand (ipv4/ipv6)
 
 804   if (/^\d+\.\d+\.\d+\.\d+$/) {
 
 805     s/\b(\d\d?)\b/sprintf "%03d",$1/ge;
 
 806   } elsif (/^[:\w]+:\w+$/) {
 
 807     s/\b(\w+)\b/sprintf "%04s",$1/ge;
 
 809     while (s/::/::0000:/) { last if length > 39 }
 
 822   if (open $file,'<',"$file/filename") {
 
 823     $filename = <$file>||'';
 
 830     $filename =~ s:.*/::;
 
 839   s/(^[.~]|[^\w.,=:~^+-])/sprintf "%%%X",ord($1)/ge;
 
 844 # file and document log
 
 846   my ($log,$file,$s,$size) = @_;
 
 847   my $ra = $ENV{REMOTE_ADDR}||'-';
 
 850   $ra .= '/'.$ENV{HTTP_X_FORWARDED_FOR} if $ENV{HTTP_X_FORWARDED_FOR};
 
 853   $msg = sprintf "%s [%s_%s] %s %s %s/%s\n",
 
 854          isodate(time),$$,$ENV{REQUESTCOUNT},$ra,encode_Q($file),$s,$size;
 
 865   return unless $debug and @_;
 
 866   unless ($debuglog and fileno $debuglog) {
 
 867     my $ddir = "$spooldir/.debug";
 
 868     mkdir $ddir,0770 unless -d $ddir;
 
 870     $prg = untaint($prg);
 
 871     $debuglog = sprintf("%s/%s_%s_%s.%s",
 
 872                         $ddir,time,$$,$ENV{REQUESTCOUNT}||0,$prg);
 
 873     $debuglog =~ s/\s/_/g;
 
 874     # open $debuglog,'>>:encoding(UTF-8)',$debuglog or return;
 
 875     open $debuglog,'>>',$debuglog or return;
 
 876     autoflush $debuglog 1;
 
 877     # printf {$debuglog} "\n### %s ###\n",isodate(time);
 
 879   while ($_ = shift @_) {
 
 881     s/<.+?>//g; # remove HTML
 
 882     print {$debuglog} $_;
 
 883     print "DEBUG: $_" if -t;
 
 892   my $ra = $ENV{REMOTE_ADDR}||'-';
 
 894   $ra .= '/'.$ENV{HTTP_X_FORWARDED_FOR} if $ENV{HTTP_X_FORWARDED_FOR};
 
 897   $msg =~ s/[\r\n]+$//;
 
 898   $msg =~ s/[\r\n]+/ /;
 
 899   $msg =~ s/\s*<p>.*//;
 
 900   $msg = sprintf "%s %s %s %s\n",isodate(time),$prg,$ra,$msg;
 
 902   writelog('error.log',$msg);
 
 910   foreach my $logdir (@logdir) {
 
 911     if (open $log,'>>',"$logdir/$log") {
 
 913       seek $log,0,SEEK_END;
 
 921 # failed authentification log
 
 926   if ($faillog and $max_fail_handler and open $faillog,"+>>$faillog") {
 
 927     flock($faillog,LOCK_EX);
 
 928     seek $faillog,0,SEEK_SET;
 
 929     $n++ while <$faillog>;
 
 930     printf {$faillog} "%s %s\n",isodate(time),$request;
 
 932     &$max_fail_handler($ENV{REMOTE_ADDR}) if $n > $max_fail;
 
 936 # remove all white space
 
 948   my $q = "[\'\"]"; # quote delimiter chars " and '
 
 950   # remove first newline and look for default indention
 
 954   # remove trailing spaces at end
 
 959   # first line have a quote delimiter char?
 
 961     # remove heading spaces and delimiter chars
 
 967     # find the line with the fewest heading spaces (and count them)
 
 971       if (/^( *)\S/ and length($1) < $s) { $s = length($1) };
 
 979   return join("\n",@s)."\n";
 
 986   if (@_ > 1 and defined fileno $_[0]) { $H = shift }
 
 992 sub check_sender_quota {
 
 994   my $squota = $sender_quota||0;
 
 996   my ($file,$size,%file,$data,$upload);
 
 999   if (open $qf,'<',"$sender/\@QUOTA") {
 
1002       $squota = $1 if /sender.*?(\d+)/i;
 
1007   foreach $file (glob "*/$sender/*") {
 
1008     $data = "$file/data";
 
1009     $upload = "$file/upload";
 
1010     if (not -l $data and $size = -s $data) {
 
1011       # count hard links only once (= same inode)
 
1012       my $i = (stat($data))[1]||0;
 
1013       unless ($file{$i}) {
 
1017     } elsif (-f $upload) {
 
1018       # count hard links only once (= same inode)
 
1019       my $i = (stat($upload))[1]||0;
 
1020       unless ($file{$i}) {
 
1021         $size = readlink "$file/size" and $du += $size;
 
1027   return($squota,int($du/1024/1024));
 
1031 # check recipient quota
 
1032 sub check_recipient_quota {
 
1033   my $recipient = shift;
 
1034   my $rquota = $recipient_quota||0;
 
1039   if (open my $qf,'<',"$recipient/\@QUOTA") {
 
1042       $rquota = $1 if /recipient.*?(\d+)/i;
 
1047   foreach $file (glob "$recipient/*/*") {
 
1048     if (-f "$file/upload" and $size = readlink "$file/size") {
 
1050     } elsif (not -l "$file/data" and $size = -s "$file/data") {
 
1055   return($rquota,int($du/1024/1024));
 
1062   chomp($_ = <$file>||'');
 
1067 # (shell) wildcard matching
 
1070   my $p = quotemeta shift;
 
1083   if    ($skey) { $logout = "/fup?logout=skey:$skey" }
 
1084   elsif ($gkey) { $logout = "/fup?logout=gkey:$gkey" }
 
1085   elsif ($akey) { $logout = "/fup?logout=akey:$akey" }
 
1086   else          { $logout = "/fup?logout" }
 
1089     '<form name="logout" action="$logout">'
 
1090     '  <input type="submit" name="logout" value="logout">'
 
1097 # print data dump of global or local variables in HTML
 
1098 # input musst be a string, eg: '%ENV'
 
1104   $_ = eval(qq(use Data::Dumper;Data::Dumper->Dump([\\$v])));
 
1108   print "<pre>\n$_\n</pre>\n";
 
1113   my ($file,$link) = @_;
 
1115   return symlink untaint($link),$file;
 
1119 # copy file (and modify) or symlink
 
1120 # returns chomped file contents or link name
 
1121 # preserves permissions and time stamps
 
1123   my ($from,$to,$mod) = @_;
 
1128   $to .= '/'.basename($from) if -d $to;
 
1130   if (defined($link = readlink $from)) {
 
1131     mksymlink($to,$link);
 
1134     open $from,'<',$from or return;
 
1135     open $to,'>',$to or return;
 
1140     close $to or http_die("internal error: $to - $!");
 
1141     if (my @s = stat($from)) { 
 
1143       utime @s[8,9],$to unless $mod;
 
1156   if (open $file,$file) {
 
1165 # read one line from STDIN (net socket) and assign it to $_
 
1166 # return number of read bytes
 
1167 # also set global variable $RB (read bytes)
 
1171   if (defined ($_ = <STDIN>)) {
 
1181 # read forward to given pattern
 
1183   my $pattern = shift;
 
1185   while (&nvt_read) { return if /$pattern/ }
 
1189 # HTTP GET and POST parameters
 
1191 # fills global variable %PARAM :
 
1192 # normal parameter is $PARAM{$parameter}
 
1193 # file parameter is $PARAM{$parameter}{filename} $PARAM{$parameter}{data}
 
1194 sub parse_parameters {
 
1195   my $cl = $ENV{X_CONTENT_LENGTH} || $ENV{CONTENT_LENGTH} || 0;
 
1200   if ($cl > 128*$MB) {
 
1201     http_die("request too large");
 
1204   foreach (split('&',$ENV{QUERY_STRING})) {
 
1205     if (/(.+?)=(.*)/) { $PARAM{$1} = $2 }
 
1206     else              { $PARAM{$_} = $_ }
 
1208   $_ = $ENV{CONTENT_TYPE}||'';
 
1209   if ($ENV{REQUEST_METHOD} eq 'POST' and /boundary=\"?([\w\-\+\/_]+)/) {
 
1211     while ($RB<$cl and &nvt_read) { last if /^--\Q$boundary/ }
 
1212     # continuation lines are not checked!
 
1213     while ($RB<$cl and &nvt_read) {
 
1215       if (/^Content-Disposition:.*\s*filename="(.+?)"/i) {
 
1218       if (/^Content-Disposition:\s*form-data;\s*name="(.+?)"/i) {
 
1220         # skip rest of mime part header
 
1221         while ($RB<$cl and &nvt_read) { last if /^\s*$/ }
 
1224           if ($p =~ /password/i) {
 
1225             debuglog('*' x length)
 
1230           last if /^--\Q$boundary/;
 
1233         unless (defined $_) { die "premature end of HTTP POST\n" }
 
1234         $data =~ s/\r?\n$//;
 
1236           $PARAM{$p}{filename} = $filename;
 
1237           $PARAM{$p}{data} = $data;
 
1241         last if /^--\Q$boundary--/;
 
1248 # name based virtual host?
 
1250   my $hh = shift; # HTTP_HOST
 
1252   my $locale = $ENV{LOCALE};
 
1254   # memorized vhost? (default is in fex.ph)
 
1255   %vhost = split(':',$ENV{VHOST}) if $ENV{VHOST};
 
1257   if (%vhost and $hh and $hh =~ s/^([\w\.-]+).*/$1/) {
 
1258     if ($vhost = $vhost{$hh} and -f "$vhost/lib/fex.ph") {
 
1259       $ENV{VHOST} = "$hh:$vhost"; # memorize vhost for next run
 
1260       $ENV{FEXLIB} = $FEXLIB = "$vhost/lib";
 
1261       $logdir = $spooldir    = "$vhost/spool";
 
1262       $docdir                = "$vhost/htdocs";
 
1263       @logdir = ($logdir);
 
1264       if ($locale and -e "$vhost/locale/$locale/lib/fex.ph") {
 
1265         $ENV{FEXLIB} = $FEXLIB = "$vhost/locale/$locale/lib";
 
1267       require "$FEXLIB/fex.ph" or die "$0: cannot load $FEXLIB/fex.ph - $!";
 
1268       $ENV{SERVER_NAME} = $hostname;
 
1269       @doc_dirs = ($docdir);
 
1270       foreach my $ld (glob "$FEXHOME/locale/*/htdocs") {
 
1280   my ($plain,$to,$keyring,$from) = @_;
 
1281   my ($pid,$pi,$po,$pe,$enc,$err);
 
1286   $pid = open3($po,$pi,$pe,
 
1287     "gpg --batch --trust-model always --keyring $keyring".
 
1288     "    -a -e -r $bcc -r $to"
 
1294   $enc .= $_ while <$pi>;
 
1295   $err .= $_ while <$pe>;
 
1296   errorlog("($from --> $to) $err") if $err;
 
1307   my @s = stat(shift) or return;
 
1312 # wildcard * to perl regexp
 
1314   local $_ = quotemeta shift;
 
1315   s/\\\*/.*/g; # allow wildcard *
 
1320 # extract locale functions into hash of subroutine references
 
1321 # e.g. \&german ==> $notify{german}
 
1322 sub locale_functions {
 
1327   if ($locale and open my $fexpp,"$FEXHOME/locale/$locale/lib/fex.pp") {
 
1329     s/.*\n(\#\#\# locale functions)/$1/s;
 
1330     # sub xx {} ==> xx{$locale} = sub {}
 
1331     s/\nsub (\w+)/\n\$$1\{$locale\} = sub/gs; 
 
1340   my $status = shift || 'new';
 
1341   my ($to,$keep,$locale,$file,$filename,$comment,$autodelete,$replyto,$mtime);
 
1344   if ($dkey =~ m:/.+/.+/:) {
 
1346     $dkey = readlink("$file/dkey");
 
1348     $file = readlink("$dkeydir/$dkey") 
 
1349       or http_die("internal error: no DKEY $DKEY");
 
1352   $filename = filename($file);
 
1355   $mtime = mtime("$file/data") or http_die("internal error: no $file/data");
 
1356   $comment = slurp("$file/comment") || '';
 
1357   $replyto = readlink "$file/replyto" || '';
 
1358   $autodelete = readlink "$file/autodelete" 
 
1359              || readlink "$to/\@AUTODELETE" 
 
1361   $keep = readlink "$file/keep" 
 
1362        || readlink "$to/\@KEEP" 
 
1365   $locale = readlink "$to/\@LOCALE" || readlink "$file/locale" || 'english';
 
1366   $_ = untaint("$FEXHOME/locale/$locale/lib/lf.pl");
 
1368   unless ($notify{$locale}) {
 
1369     $locale = 'english';
 
1370     $notify{$locale} ||= \¬ify;
 
1372   return &{$notify{$locale}}(
 
1375     filename   => $filename,
 
1376     keep       => $keep-int((time-$mtime)/$DS),
 
1377     comment    => $comment,
 
1378     autodelete => $autodelete,
 
1379     replyto    => $replyto,
 
1383 ########################### locale functions ###########################
 
1384 # Will be extracted by install process and saved in $FEXHOME/lib/lf.pl #
 
1385 # You cannot modify them here without re-installing!                   #
 
1386 ########################################################################
 
1390   # my ($status,$dkey,$filename,$keep,$warn,$comment,$autodelete) = @_;
 
1392   my ($to,$from,$file,$mimefilename,$receiver,$warn,$comment,$autodelete);
 
1393   my ($size,$bytes,$days,$header,$data,$replyto,$uurl);
 
1394   my ($mfrom,$mto,$dfrom,$dto);
 
1399   my $fua = $ENV{HTTP_USER_AGENT}||'';
 
1401   my $disclaimer = '';
 
1404   my $boundary = randstring(16);
 
1405   my ($body,$enc_body);
 
1409   $warn = $P{warn}||2;
 
1410   $comment = encode_utf8($P{comment}||'');
 
1411   $comment =~ s/^!\*!//; # multi download allow flag
 
1412   $autodelete = $P{autodelete}||$::autodelete;
 
1414   $file = untaint(readlink("$dkeydir/$P{dkey}"));
 
1415   $file =~ s/^\.\.\///;
 
1416   # make download protocal same as upload protocol
 
1417   if ($uurl = readlink("$file/uurl") and $uurl =~ /^(\w+):/) {
 
1419     $durl =~ s/^\w+::/$proto::/;
 
1421   $index = "$proto://$hostname/index.html";
 
1422   ($to,$from,$file) = split('/',$file);
 
1423   $filename = strip_path($P{filename});
 
1426   $mfrom .= '@'.$mdomain if $mdomain and $mfrom !~ /@/;
 
1427   $mto .=   '@'.$mdomain if $mdomain and $mto   !~ /@/;
 
1428   $keyring = $to.'/@GPG';
 
1429   # $to = '' if $to eq $from; # ???
 
1430   $replyto = $P{replyto}||$mfrom;
 
1431   $header = "From: <$mfrom> ($mfrom via F*EX service $hostname)\n";
 
1432   $header .= "Reply-To: <$replyto>\n" if $replyto ne $mfrom;
 
1433   $header .= "To: <$mto>\n";
 
1434   $data = "$dkeydir/$P{dkey}/data";
 
1435   $size = $bytes = -s $data;
 
1436   return unless $size;
 
1441       "Please avoid download with Internet Explorer, ".
 
1442       "because it has too many bugs.\n".
 
1443       "We recommend Firefox or wget.";
 
1445   if ($filename =~ /\.(tar|zip|7z|arj|rar)$/) {
 
1447       "$filename is a container file.\n".
 
1448       "You can unpack it for example with 7zip ".
 
1449       "(http://www.7-zip.org/download.html)";
 
1451   if ($limited_download =~ /^y/i) {
 
1453       'This download link only works for you, you cannot distribute it.';
 
1456     $size = "$size Bytes";
 
1457   } elsif ($size/1024 < 2048) {
 
1458     $size = int($size/1024)." kB";
 
1460     $size = int($size/1024/1024)." MB";
 
1462   if ($autodelete eq 'YES') {
 
1463     $autodelete = "WARNING: After download (or view with a web browser!), "
 
1464                 . "the file will be deleted!";
 
1465   } elsif ($autodelete eq 'DELAY') {
 
1466     $autodelete = "WARNING: When you download the file it will be deleted "
 
1467                 . "soon afterwards!";
 
1475     $mimefilename = $filename;
 
1476     if ($mimefilename =~ s/([_\?\=\x00-\x1F\x7F-\xFF])/sprintf("=%02X",ord($1))/eog) {
 
1477       $mimefilename =~ s/ /_/g;
 
1478       $mimefilename = '=?UTF-8?Q?'.$mimefilename.'?=';
 
1482   unless ($fileid = readlink("$dkeydir/$P{dkey}/id")) {
 
1483     my @s = stat($data);
 
1484     $fileid =  @s ? $s[1].$s[9] : 0;
 
1487   if ($P{status} eq 'new') {
 
1489     $header .= "Subject: F*EX-upload: $mimefilename\n";
 
1492     $header .= "Subject: reminder F*EX-upload: $mimefilename\n";
 
1494   $header .= "X-FEX-Client-Address: $fra\n" if $fra;
 
1495   $header .= "X-FEX-Client-Agent: $fua\n"   if $fua;
 
1496   foreach my $u (@durl?@durl:($durl)) {
 
1497     my $durl = sprintf("%s/%s/%s",$u,$P{dkey},normalize_filename($filename));
 
1498     $header .= "X-FEX-URL: $durl\n" unless -s $keyring;
 
1499     $download .= "$durl\n";
 
1502     "X-FEX-Filesize: $bytes\n".
 
1503     "X-FEX-File-ID: $fileid\n".
 
1504     "X-FEX-Fexmaster: $ENV{SERVER_ADMIN}\n".
 
1506     "MIME-Version: 1.0\n";
 
1507   if ($comment =~ s/^\[(\@(.*?))\]\s*//) { 
 
1508     $receiver = "group $1";
 
1509     if ($_ = readlink "$from/\@GROUP/$2" and m:^../../(.+?)/:) {
 
1510       $receiver .= " (maintainer: $1)";
 
1515   if ($days == 1) { $days .= " day" }
 
1516   else            { $days .= " days" }
 
1518   # explicite sender set in fex.ph?
 
1520     map { s/^From: <$mfrom/From: <$sender_from/ } $header;
 
1521     open $sendmail,'|-',$sendmail,$mto,$bcc
 
1522       or http_die("cannot start sendmail - $!");
 
1524     # for special remote domains do not use same domain in From, 
 
1525     # because remote MTA will probably reject this e-mail
 
1526     $dfrom = $1 if $mfrom =~ /@(.+)/;
 
1527     $dto   = $1 if $mto   =~ /@(.+)/;
 
1528     if ($dfrom and $dto and @remote_domains and 
 
1530           $dfrom =~ /(^|\.)$_$/ and $dto =~ /(^|\.)$_$/ 
 
1533       $header =~ s/(From: <)\Q$mfrom\E(.*?)\n/$1$admin$2\nReply-To: $mfrom\n/;
 
1534       open $sendmail,'|-',$sendmail,$mto,$bcc
 
1535         or http_die("cannot start sendmail - $!");
 
1537       open $sendmail,'|-',$sendmail,'-f',$mfrom,$mto,$bcc
 
1538         or http_die("cannot start sendmail - $!");
 
1541   if ($comment =~ s/^!(shortmail|\.)!\s*//i 
 
1542     or (readlink "$to/\@NOTIFICATION"||'') =~ /short/i
 
1551     $comment = "Comment: $comment\n" if $comment;
 
1552     $disclaimer = slurp("$from/\@DISCLAIMER") || qqq(qq(
 
1555       'F*EX is not an archive, it is a transfer system for personal files.'
 
1556       'For more information see $index'
 
1558       'Questions? ==> F*EX admin: $admin'
 
1560     $disclaimer .= "\n" . $::disclaimer if $::disclaimer;
 
1562       '$from has uploaded the file'
 
1564       '($size) for $receiver. Use'
 
1567       'to download this file within $days.'
 
1575   $body =~ s/\n\n+/\n\n/g;
 
1577     $enc_body = gpg_encrypt($body,$to,$keyring,$from);
 
1582       'Content-Type: multipart/encrypted; protocol="application/pgp-encrypted";'
 
1583       '\tboundary="$boundary"'
 
1584       'Content-Disposition: inline'
 
1588       'Content-Type: application/pgp-encrypted'
 
1589       'Content-Disposition: attachment'
 
1594       'Content-Type: application/octet-stream'
 
1595       'Content-Disposition: inline; filename="fex.pgp"'
 
1602       "Content-Type: text/plain; charset=UTF-8\n".
 
1603       "Content-Transfer-Encoding: 8bit\n";
 
1605   print {$sendmail} $header,"\n",$body;
 
1606   close $sendmail and return $to;
 
1607   http_die("cannot send notification e-mail (sendmail error $!)");
 
1613   my ($expire,$user) = @_;
 
1614   my $fexsend = "$FEXHOME/bin/fexsend";
 
1619     $fexsend .= " -M -D -k 30 -C"
 
1620                ." 'Your F*EX account has been inactive for $expire days,"
 
1621                ." you must download this file to reactivate it."
 
1622                ." Otherwise your account will be deleted.'"
 
1623                ." $FEXLIB/reactivation.txt $user";
 
1624     # on error show STDOUT and STDERR
 
1625     system "$fexsend >/dev/null 2>&1";
 
1631     warn "$0: cannot execute $fexsend for reactivation()\n";