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);
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
47 $recipient_quota = 0; # MB
48 $sender_quota = 0; # MB
49 $timeout = 30; # seconds
50 $bs = 2**16; # I/O blocksize
52 $sendmail = '/usr/lib/sendmail';
53 $sendmail = '/usr/sbin/sendmail' unless -x $sendmail;
62 $FHS = -f '/etc/fex/fex.ph' and -d '/usr/share/fex/lib';
65 $ENV{FEXHOME} = $FEXHOME = '/usr/share/fex';
66 $spooldir = '/var/spool/fex';
67 $logdir = '/var/log/fex';
68 $docdir = '/var/lib/fex/htdocs';
69 $notify_newrelease = '';
72 # allowed download managers (HTTP User-Agent)
73 $adlm = '^(Axel|fex)';
75 # allowed multi download recipients
76 $amdl = '^(anonymous|_fexmail_)';
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 # check for name based virtual host
87 $vhost = vhost($ENV{'HTTP_HOST'});
89 push @doc_dirs,$docdir;
90 foreach my $ld (glob "$FEXHOME/locale/*/htdocs") {
94 $nomail = ($mailmode =~ /^MANUAL|nomail$/i);
96 if (not $nomail and not -x $sendmail) {
97 http_die("found no sendmail\n");
99 http_die("cannot determine the server hostname") unless $hostname;
101 $ENV{PROTO} = 'http' unless $ENV{PROTO};
102 $keep = $keep_default ||= $keep || 5;
103 $fra = $ENV{REMOTE_ADDR} || '';
104 $sid = $ENV{SID} || '';
106 mkdirp($dkeydir = "$spooldir/.dkeys"); # download keys
107 mkdirp($ukeydir = "$spooldir/.ukeys"); # upload keys
108 mkdirp($akeydir = "$spooldir/.akeys"); # authentification keys
109 mkdirp($skeydir = "$spooldir/.skeys"); # subuser authentification keys
110 mkdirp($gkeydir = "$spooldir/.gkeys"); # group authentification keys
111 mkdirp($xkeydir = "$spooldir/.xkeys"); # extra download keys
112 mkdirp($lockdir = "$spooldir/.locks"); # download lock files
114 if (my $ra = $ENV{REMOTE_ADDR} and $max_fail) {
115 mkdirp("$spooldir/.fail");
116 $faillog = "$spooldir/.fail/$ra";
120 $admin = $ENV{SERVER_ADMIN} ? $ENV{SERVER_ADMIN} : 'fex@'.$hostname;
123 # $ENV{SERVER_ADMIN} may be set empty in fex.ph!
124 $ENV{SERVER_ADMIN} = $admin unless defined $ENV{SERVER_ADMIN};
129 if (my $cookie = $ENV{HTTP_COOKIE}) {
130 if ($cookie =~ /\bakey=(\w+)/) { $akey = $1 }
131 # elsif ($cookie =~ /\bskey=(\w+)/) { $skey = $1 }
136 if ($default_locale and not grep /^$default_locale$/,@locales) {
137 push @locales,$default_locale;
140 $default_locale = $locales[0];
144 $default_locale ||= 'english';
150 ($host,$port) = split(':',$ENV{HTTP_HOST}||'');
155 if (open my $xinetd,'<',"/etc/xinetd.d/fex") {
157 if (/^\s*port\s*=\s*(\d+)/) {
166 # use same protocal as uploader for download
167 if ($ENV{PROTO} eq 'https' and $port == 443 or $port == 80) {
168 $durl = "$ENV{PROTO}://$host/fop";
170 $durl = "$ENV{PROTO}://$host:$port/fop";
174 @durl = ($durl) unless @durl;
178 exec($FEXHOME.'/bin/fexsrv') if $ENV{KEEP_ALIVE};
185 $cont = shift || 'request accepted: continue';
187 http_header('200 ok');
188 print html_header($head||$ENV{SERVER_NAME});
190 '<script type="text/javascript">'
191 ' window.location.replace("$url");'
194 ' <h3><a href="$url">$cont</a></h3>'
203 print header(),"<pre>\n";
204 print "file = $file\n";
205 foreach $v (keys %ENV) {
206 print $v,' = "',$ENV{$v},"\"\n";
213 foreach (@_) { syswrite STDOUT,"$_\r\n" }
234 return if $HTTP_HEADER;
235 $HTTP_HEADER = $status;
239 nvt_print("HTTP/1.1 $status");
240 nvt_print("X-Message: $msg");
241 # nvt_print("X-SID: $ENV{SID}") if $ENV{SID};
242 nvt_print("Server: fexsrv");
243 nvt_print("Expires: 0");
244 nvt_print("Cache-Control: no-cache");
245 # http://en.wikipedia.org/wiki/Clickjacking
246 nvt_print("X-Frame-Options: SAMEORIGIN");
248 # https://www.owasp.org/index.php/HTTP_Strict_Transport_Security
249 nvt_print("Strict-Transport-Security: max-age=2851200");
253 nvt_print("Set-Cookie: akey=$akey; Max-Age=9999; Discard");
256 # nvt_print("Set-Cookie: skey=$skey; Max-Age=9999; Discard");
259 nvt_print("Set-Cookie: locale=$locale");
262 unless (grep /^Content-Type:/i,@_) {
263 # nvt_print("Content-Type: text/html; charset=ISO-8859-1");
264 nvt_print("Content-Type: text/html; charset=UTF-8");
273 my $header = 'header.html';
276 # http://www.w3.org/TR/html401/struct/global.html
277 # http://www.w3.org/International/O-charset
281 ' <meta http-equiv="expires" content="0">'
282 ' <meta http-equiv="Content-Type" content="text/html;charset=utf-8">'
283 ' <title>$title</title>'
286 # '<!-- <style type="text/css">\@import "/fex.css";</style> -->'
288 if ($0 =~ /fexdev/) { $head .= "<body bgcolor=\"pink\">\n" }
289 else { $head .= "<body>\n" }
291 $title =~ s:F\*EX:<a href="/index.html">F*EX</a>:;
293 if (open $header,'<',"$docdir/$header") {
294 $head .= $_ while <$header>;
298 $head .= &$prolog($title) if defined($prolog);
302 '<h1><a href="%s"><img align=center src="%s" border=0></a>%s</h1>',
303 $H1_extra[0],$H1_extra[1]||'',$title
306 $head .= "<h1>$title</h1>";
318 my $isodate = isodate(time);
320 $msg =~ s/[\s\n]+/ /g;
321 $msg =~ s/<.+?>//g; # remove HTML
322 map { s/<script.*?>//gi } @msg;
326 # cannot send standard HTTP Status-Code 400, because stupid
327 # Internet Explorer then refuses to display HTML body!
328 http_header("666 Bad Request - $msg");
329 print html_header($error);
330 print 'ERROR: ',join("<p>\n",@msg),"\n";
336 ' <a href="mailto:$ENV{SERVER_ADMIN}">$ENV{SERVER_ADMIN}</a>'
347 die "$0: @_\n" unless $ENV{GATEWAY_INTERFACE};
351 # create special error file on upload
353 my $ukey = "$spooldir/.ukeys/$uid";
354 $ukey .= "/error" if -d $ukey;
356 if (open $ukey,'>',$ukey) {
357 print {$ukey} join("\n",@_),"\n";
362 html_error($error||'',@_);
367 if (my $status = readlink '@MAINTENANCE') {
368 my $isodate = isodate(time);
369 http_header('666 MAINTENANCE');
370 print html_header($head||'');
373 "<h1>Server is in maintenance mode</h1>"
377 "<address>$ENV{HTTP_HOST} $isodate</address>"
389 $user .= '@'.$mdomain if $mdomain and $user !~ /@/;
391 if (-e "$user/\@DISABLED") {
392 my $isodate = isodate(time);
393 http_header('666 DISABLED');
394 print html_header($head);
396 "<h3>$user is disabled</h3>"
397 "Contact $ENV{SERVER_ADMIN} for details"
399 "<address>$ENV{HTTP_HOST} $isodate</address>"
408 my @d = localtime shift;
409 return sprintf('%d-%02d-%02d %02d:%02d:%02d',
410 $d[5]+1900,$d[4]+1,$d[3],$d[2],$d[1],$d[0]);
416 $s =~ s{([\=\x00-\x20\x7F-\xA0])}{sprintf("=%02X",ord($1))}eog;
421 # from MIME::Base64::Perl
430 return '' unless length;
432 for ($i = 0; $i <= $l; $i += 60) {
433 $uu .= "M" . substr($_,$i,60);
436 $uu .= chr(32+(length)*3/4) . $_ if $_;
437 return unpack ("u",$uu);
441 # short base64 encoding
447 $_ = join '',map(pack('u',$_)=~ /^.(\S*)/, ($_[0]=~/(.{1,45})/gs));
448 tr|` -_|AA-Za-z0-9+/|;
449 $x = (3 - length($_[0]) % 3) % 3;
456 # simulate a "rm -rf", but never removes '..'
457 # return number of removed files
466 next if /(^|\/)\.\.$/;
468 if (-d $file and not -l $file) {
470 opendir D,$dir or next;
471 while ($file = readdir D) {
472 next if $file eq '.' or $file eq '..';
473 $dels += rmrf("$dir/$file");
476 rmdir $dir and $dels++;
478 unlink $file and $dels++;
486 my $hostname = hostname;
491 $_ = `hostname 2>/dev/null`;
492 $hostname = /(.+)/ ? $1 : '';
494 if ($hostname !~ /\./ and open my $rc,'/etc/resolv.conf') {
496 if (/^\s*domain\s+([\w.-]+)/) {
500 if (/^\s*search\s+([\w.-]+)/) {
505 $hostname .= ".$domain" if $domain;
507 if ($hostname !~ /\./ and $admin and $admin =~ /\@([\w.-]+)/) {
515 # strip off path names (Windows or UNIX)
519 s/.*\\// if /^([A-Z]:)?\\/;
526 # substitute all critcal chars
530 return '' unless defined $_;
532 # we need perl native utf8 (see perldoc utf8)
533 $_ = decode_utf8($_) unless utf8::is_utf8($_);
536 s/[\x00-\x1F\x80-\x9F]/_/g;
540 return encode_utf8($_);
544 # substitute all critcal chars
548 return '' unless defined $_;
558 # substitute all critcal chars with underscore
559 sub normalize_filename {
564 # we need native utf8
565 $_ = decode_utf8($_) unless utf8::is_utf8($_);
569 # substitute all critcal chars with underscore
570 s/[^a-zA-Z0-9_=.+-]/_/g;
573 return encode_utf8($_);
577 sub normalize_email {
580 s/[^\w_.+=!~#^\@\-]//g;
599 http_die("\"$1\" is not allowed at beginning of $input");
601 if (/([\/\"\'\\<>;])/) {
602 http_die(sprintf("\"&#%s;\" is not allowed in %s",ord($1),$input));
605 http_die("\"$1\" is not allowed at end of $input");
608 http_die("control characters are not allowed in $input");
619 local ($domain,$dns);
621 $a =~ s/:\w+=.*//; # remove options from address
623 return $a if $a eq 'anonymous';
625 $re = '^[.@]|@.*@|local(host|domain)$|["\'\`\|\s()<>/;,]';
627 debuglog("$a has illegal syntax ($re)");
630 $re = '^[!^=~#_:.+*{}\w\-\[\]]+\@(\w[.\w\-]*\.[a-z]+)$';
634 local $SIG{__DIE__} = sub { die "\n" };
637 $dns = Net::DNS::Resolver->new->query($domain)||mx($domain);
638 unless ($dns or mx('uni-stuttgart.de')) {
639 http_die("Internal error: bad resolver");
646 debuglog("no A or MX DNS record found for $domain");
650 debuglog("$a does not match e-mail regexp ($re)");
656 # check forbidden addresses
662 $a .= '@'.$mdomain if $mdomain and $a !~ /@/;
663 return $a if -d "$spooldir/$a"; # ok, if user already exists
664 if (@forbidden_recipients) {
665 foreach (@forbidden_recipients) {
667 $fr =~ s/\\\*/.*/g; # allow wildcard *
668 # skip public recipients
669 if (@public_recipients) {
670 foreach $pr (@public_recipients) {
671 return $a if $a eq lc $pr;
674 return '' if $a =~ /^$fr$/i;
683 my @rc = ('A'..'Z','a'..'z',0..9 );
687 for (1..$n) { $rs .= $rc[int(rand($rn))] };
699 http_die("cannot mkdir /\n") unless $dir;
701 if ($pdir =~ s:/[^/]+$::) {
702 mkdirp($pdir) unless -d $pdir;
705 mkdir $dir,0770 or http_die("mkdir $dir - $!\n");
714 if ($rid and $ENV{SID} and $id =~ /^MD5H:/) {
715 $rid = 'MD5H:'.md5_hex($rid.$ENV{SID});
721 # test if ip is in iplist (ipv4/ipv6)
722 # iplist is an array with ips and ip-ranges
731 if ($ip =~ /\./ and $i =~ /\./ or $ip =~ /:/ and $i =~ /:/) {
732 if ($i =~ /(.+)-(.+)/) {
736 return $ip if $ipe ge $ia and $ipe le $ib;
738 return $ip if $ipe eq ipe($i);
745 # ip expand (ipv4/ipv6)
749 if (/^\d+\.\d+\.\d+\.\d+$/) {
750 s/\b(\d\d?)\b/sprintf "%03d",$1/ge;
751 } elsif (/^[:\w]+:\w+$/) {
752 s/\b(\w+)\b/sprintf "%04s",$1/ge;
754 while (s/::/::0000:/) { last if length > 39 }
767 if (open $file,'<',"$file/filename") {
768 $filename = <$file>||'';
775 $filename =~ s:.*/::;
784 s/(^[.~]|[^\w.,=:~^+-])/sprintf "%%%X",ord($1)/ge;
789 # file and document log
791 my ($log,$file,$s,$size) = @_;
794 if (open $log,'>>',$log) {
796 seek $log,0,SEEK_END;
797 $ra = $ENV{REMOTE_ADDR}||'-';
798 $ra .= '/'.$ENV{HTTP_X_FORWARDED_FOR} if $ENV{HTTP_X_FORWARDED_FOR};
802 "%s [%s_%s] %s %s %s/%s\n",
803 isodate(time),$$,$ENV{REQUESTCOUNT},$ra,encode_Q($file),$s,$size;
814 return unless $debug and @_;
815 unless ($debuglog and fileno $debuglog) {
816 mkdir "$logdir/.debug",0770 unless -d "$logdir/.debug";
818 $prg = untaint($prg);
819 $debuglog = sprintf("%s/.debug/%s_%s_%s.%s",
820 $logdir,time,$$,$ENV{REQUESTCOUNT}||0,$prg);
821 $debuglog =~ s/\s/_/g;
822 # open $debuglog,'>>:encoding(UTF-8)',$debuglog or return;
823 open $debuglog,'>>',$debuglog or return;
824 autoflush $debuglog 1;
825 # printf {$debuglog} "\n### %s ###\n",isodate(time);
827 while ($_ = shift @_) {
829 s/<.+?>//g; # remove HTML
830 print {$debuglog} $_;
831 print "DEBUG: $_" if -t;
839 my $log = "$logdir/error.log";
843 $msg =~ s/[\r\n]+$//;
844 $msg =~ s/[\r\n]+/ /;
845 $msg =~ s/\s*<p>.*//;
847 if (open $log,'>>',$log) {
849 seek $log,0,SEEK_END;
850 $ra = $ENV{REMOTE_ADDR}||'-';
851 $ra .= '/'.$ENV{HTTP_X_FORWARDED_FOR} if $ENV{HTTP_X_FORWARDED_FOR};
853 printf {$log} "%s %s %s %s\n",isodate(time),$prg,$ra,$msg;
859 # failed authentification log
864 if ($faillog and $max_fail_handler and open $faillog,"+>>$faillog") {
865 flock($faillog,LOCK_EX);
866 seek $faillog,0,SEEK_SET;
867 $n++ while <$faillog>;
868 printf {$faillog} "%s %s\n",isodate(time),$request;
870 &$max_fail_handler($ENV{REMOTE_ADDR}) if $n > $max_fail;
874 # remove all white space
886 my $q = "[\'\"]"; # quote delimiter chars " and '
888 # remove first newline and look for default indention
892 # remove trailing spaces at end
897 # first line have a quote delimiter char?
899 # remove heading spaces and delimiter chars
905 # find the line with the fewest heading spaces (and count them)
909 if (/^( *)\S/ and length($1) < $s) { $s = length($1) };
917 return join("\n",@s)."\n";
924 if (@_ > 1 and defined fileno $_[0]) { $H = shift }
930 sub check_sender_quota {
932 my $squota = $sender_quota||0;
934 my ($file,$size,%file,$data);
937 if (open $qf,'<',"$sender/\@QUOTA") {
940 $squota = $1 if /sender.*?(\d+)/i;
945 foreach $file (glob "*/$sender/*") {
946 $data = "$file/data";
947 if (not -l $data and $size = -s $data) {
948 # count hard links only once (= same inode)
949 my $i = (stat($data))[1]||0;
954 } elsif (-f "$file/upload" and $size = readlink "$file/size") {
959 return($squota,int($du/1024/1024));
963 # check recipient quota
964 sub check_recipient_quota {
965 my $recipient = shift;
966 my $rquota = $recipient_quota||0;
971 if (open my $qf,'<',"$recipient/\@QUOTA") {
974 $rquota = $1 if /recipient.*?(\d+)/i;
979 foreach $file (glob "$recipient/*/*") {
980 if (-f "$file/upload" and $size = readlink "$file/size") {
982 } elsif (not -l "$file/data" and $size = -s "$file/data") {
987 return($rquota,int($du/1024/1024));
994 chomp($_ = <$file>||'');
999 # (shell) wildcard matching
1002 my $p = quotemeta shift;
1015 if ($skey) { $logout = "/fup?logout=skey:$skey" }
1016 elsif ($gkey) { $logout = "/fup?logout=gkey:$gkey" }
1017 elsif ($akey) { $logout = "/fup?logout=akey:$akey" }
1018 else { $logout = "/fup?logout" }
1021 '<form name="logout" action="$logout">'
1022 ' <input type="submit" name="logout" value="logout">'
1029 # print data dump of global or local variables in HTML
1030 # input musst be a string, eg: '%ENV'
1036 $_ = eval(qq(use Data::Dumper;Data::Dumper->Dump([\\$v])));
1040 print "<pre>\n$_\n</pre>\n";
1045 my ($file,$link) = @_;
1047 return symlink untaint($link),$file;
1051 # copy file (and modify) or symlink
1052 # returns chomped file contents or link name
1053 # preserves permissions and time stamps
1055 my ($from,$to,$mod) = @_;
1060 $to .= '/'.basename($from) if -d $to;
1062 if (defined($link = readlink $from)) {
1063 mksymlink($to,$link);
1066 open $from,'<',$from or return;
1067 open $to,'>',$to or return;
1072 close $to or http_die("internal error: $to - $!");
1073 if (my @s = stat($from)) {
1075 utime @s[8,9],$to unless $mod;
1088 if (open $file,$file) {
1097 # name based virtual host?
1099 my $hh = shift; # HTTP_HOST
1101 my $locale = $ENV{LOCALE};
1103 # memorized vhost? (default is in fex.ph)
1104 %vhost = split(':',$ENV{VHOST}) if $ENV{VHOST};
1106 if (%vhost and $hh and $hh =~ s/^([\w\.-]+).*/$1/) {
1107 if ($vhost = $vhost{$hh} and -f "$vhost/lib/fex.ph") {
1108 $ENV{VHOST} = "$hh:$vhost"; # memorize vhost for next run
1109 $ENV{FEXLIB} = $FEXLIB = "$vhost/lib";
1110 $logdir = $spooldir = "$vhost/spool";
1111 $docdir = "$vhost/htdocs";
1112 if ($locale and -e "$vhost/locale/$locale/lib/fex.ph") {
1113 $ENV{FEXLIB} = $FEXLIB = "$vhost/locale/$locale/lib";
1115 require "$FEXLIB/fex.ph" or die "$0: cannot load $FEXLIB/fex.ph - $!";
1116 $ENV{SERVER_NAME} = $hostname;
1117 @doc_dirs = ($docdir);
1118 foreach my $ld (glob "$FEXHOME/locale/*/htdocs") {
1128 my ($plain,$to,$keyring,$from) = @_;
1129 my ($pid,$pi,$po,$pe,$enc,$err);
1134 $pid = open3($po,$pi,$pe,
1135 "gpg --batch --trust-model always --keyring $keyring".
1136 " -a -e -r $bcc -r $to"
1142 $enc .= $_ while <$pi>;
1143 $err .= $_ while <$pe>;
1144 errorlog("($from --> $to) $err") if $err;
1154 # extract locale functions into hash of subroutine references
1155 # e.g. \&german ==> $notify{german}
1156 sub locale_functions {
1161 if ($locale and open my $fexpp,"$FEXHOME/locale/$locale/lib/fex.pp") {
1163 s/.*\n(\#\#\# locale functions)/$1/s;
1164 # sub xx {} ==> xx{$locale} = sub {}
1165 s/\nsub (\w+)/\n\$$1\{$locale\} = sub/gs;
1174 my $status = shift || 'new';
1175 my ($to,$keep,$locale,$file,$filename,$comment,$autodelete,$replyto,$mtime);
1178 if ($dkey =~ m:/.+/.+/:) {
1180 $dkey = readlink("$file/dkey");
1182 $file = readlink("$dkeydir/$dkey")
1183 or http_die("internal error: no DKEY $DKEY");
1186 $filename = filename($file);
1189 $mtime = mtime("$file/data") or http_die("internal error: no $file/data");
1190 $comment = slurp("$file/comment") || '';
1191 $replyto = readlink "$file/replyto" || '';
1192 $autodelete = readlink "$file/autodelete"
1193 || readlink "$to/\@AUTODELETE"
1195 $keep = readlink "$file/keep"
1196 || readlink "$to/\@KEEP"
1199 $locale = readlink "$to/\@LOCALE" || readlink "$file/locale" || 'english';
1200 $_ = untaint("$FEXHOME/locale/$locale/lib/lf.pl");
1202 unless ($notify{$locale}) {
1203 $locale = 'english';
1204 $notify{$locale} ||= \¬ify;
1206 return &{$notify{$locale}}(
1209 filename => $filename,
1210 keep => $keep-int((time-$mtime)/DS),
1211 comment => $comment,
1212 autodelete => $autodelete,
1213 replyto => $replyto,
1217 ### locale functions ###
1218 # will be extracted by install process and saved in $FEXHOME/lib/lf.pl
1219 # you cannot modify them here without re-installing!
1222 # my ($status,$dkey,$filename,$keep,$warn,$comment,$autodelete) = @_;
1224 my ($to,$from,$file,$mimefilename,$receiver,$warn,$comment,$autodelete);
1225 my ($size,$bytes,$days,$header,$data,$replyto);
1226 my ($mfrom,$mto,$dfrom,$dto);
1229 my $fua = $ENV{HTTP_USER_AGENT}||'';
1231 my $disclaimer = '';
1234 my $boundary = randstring(16);
1235 my ($body,$enc_body);
1239 $warn = $P{warn}||2;
1240 $comment = encode_utf8($P{comment}||'');
1241 $comment =~ s/^!\*!//; # multi download allow flag
1242 $autodelete = $P{autodelete}||$::autodelete;
1244 $index =~ s/fop/index.html/;
1246 (undef,$to,$from,$file) = split('/',untaint(readlink("$dkeydir/$P{dkey}")));
1247 $filename = strip_path($P{filename});
1250 $mfrom .= '@'.$mdomain if $mdomain and $mfrom !~ /@/;
1251 $mto .= '@'.$mdomain if $mdomain and $mto !~ /@/;
1252 $keyring = $to.'/@GPG';
1253 # $to = '' if $to eq $from; # ???
1254 $replyto = $P{replyto}||$mfrom;
1255 $header = "From: <$mfrom> ($mfrom via F*EX service $hostname)\n";
1256 $header .= "Reply-To: <$replyto>\n" if $replyto ne $mfrom;
1257 $header .= "To: <$mto>\n";
1258 $data = "$dkeydir/$P{dkey}/data";
1259 $size = $bytes = -s $data;
1260 return unless $size;
1262 "Please avoid download with Internet Explorer, ".
1263 "because it has too many bugs.\n".
1264 "We recommend Firefox or wget.";
1265 if ($filename =~ /\.(tar|zip|7z|arj|rar)$/) {
1267 "$filename is a container file.\n".
1268 "You can unpack it for example with 7zip ".
1269 "(http://www.7-zip.org/download.html)";
1271 if ($limited_download =~ /^y/i) {
1273 'This download link only works for you, you cannot distribute it.';
1276 $size = "$size Bytes";
1277 } elsif ($size/1024 < 2048) {
1278 $size = int($size/1024)." kB";
1280 $size = int($size/1024/1024)." MB";
1282 if ($autodelete eq 'YES') {
1283 $autodelete = "WARNING: After download (or view with a web browser!), "
1284 . "the file will be deleted!";
1285 } elsif ($autodelete eq 'DELAY') {
1286 $autodelete = "WARNING: When you download the file it will be deleted "
1287 . "soon afterwards!";
1291 $mimefilename = $filename;
1292 if ($mimefilename =~ s{([_\?\=\x00-\x1F\x7F-\xFF])}{sprintf("=%02X",ord($1))}eog) {
1293 $mimefilename =~ s/ /_/g;
1294 $mimefilename = '=?UTF-8?Q?'.$mimefilename.'?=';
1297 unless ($fileid = readlink("$dkeydir/$P{dkey}/id")) {
1298 my @s = stat($data);
1299 $fileid = @s ? $s[1].$s[9] : 0;
1302 if ($P{status} eq 'new') {
1304 $header .= "Subject: F*EX-upload: $mimefilename\n";
1307 $header .= "Subject: reminder F*EX-upload: $mimefilename\n";
1309 $header .= "X-FEX-Client-Address: $fra\n" if $fra;
1310 $header .= "X-FEX-Client-Agent: $fua\n" if $fua;
1311 foreach my $u (@durl) {
1312 my $durl = sprintf("%s/%s/%s",$u,$P{dkey},normalize_filename($filename));
1313 $header .= "X-FEX-URL: $durl\n" unless -s $keyring;
1314 $download .= "$durl\n";
1317 "X-FEX-Filesize: $bytes\n".
1318 "X-FEX-File-ID: $fileid\n".
1319 "X-FEX-Fexmaster: $ENV{SERVER_ADMIN}\n".
1321 "MIME-Version: 1.0\n";
1322 if ($comment =~ s/^\[(\@(.*?))\]\s*//) {
1323 $receiver = "group $1";
1324 if ($_ = readlink "$from/\@GROUP/$2" and m:^../../(.+?)/:) {
1325 $receiver .= " (maintainer: $1)";
1330 if ($days == 1) { $days .= " day" }
1331 else { $days .= " days" }
1333 # explicite sender set in fex.ph?
1335 map { s/^From: <$mfrom/From: <$sender_from/ } $header;
1336 open $sendmail,'|-',$sendmail,$mto,$bcc
1337 or http_die("cannot start sendmail - $!\n");
1339 # for special remote domains do not use same domain in From,
1340 # because remote MTA will probably reject this e-mail
1341 $dfrom = $1 if $mfrom =~ /@(.+)/;
1342 $dto = $1 if $mto =~ /@(.+)/;
1343 if ($dfrom and $dto and @remote_domains and
1345 $dfrom =~ /(^|\.)$_$/ and $dto =~ /(^|\.)$_$/
1348 $header =~ s/(From: <)\Q$mfrom\E(.*?)\n/$1$admin$2\nReply-To: $mfrom\n/;
1349 open $sendmail,'|-',$sendmail,$mto,$bcc
1350 or http_die("cannot start sendmail - $!\n");
1352 open $sendmail,'|-',$sendmail,'-f',$mfrom,$mto,$bcc
1353 or http_die("cannot start sendmail - $!\n");
1356 if ($comment =~ s/^!(shortmail|\.)!\s*//i
1357 or (readlink "$to/\@NOTIFICATION"||'') =~ /short/i
1366 $comment = "Comment: $comment\n" if $comment;
1367 $disclaimer = slurp("$from/\@DISCLAIMER") || qqq(qq(
1370 'F*EX is not an archive, it is a transfer system for personal files.'
1371 'For more information see $index'
1373 'Questions? ==> F*EX admin: $admin'
1375 $disclaimer .= "\n" . $::disclaimer if $::disclaimer;
1377 '$from has uploaded the file'
1379 '($size) for $receiver. Use'
1382 'to download this file within $days.'
1391 $enc_body = gpg_encrypt($body,$to,$keyring,$from);
1396 'Content-Type: multipart/encrypted; protocol="application/pgp-encrypted";'
1397 '\tboundary="$boundary"'
1398 'Content-Disposition: inline'
1402 'Content-Type: application/pgp-encrypted'
1403 'Content-Disposition: attachment'
1408 'Content-Type: application/octet-stream'
1409 'Content-Disposition: inline; filename="fex.pgp"'
1416 "Content-Type: text/plain; charset=UTF-8\n".
1417 "Content-Transfer-Encoding: 8bit\n";
1419 print {$sendmail} $header,"\n",$body;
1421 or $! and http_die("cannot send notification e-mail (sendmail error $!)\n");
1427 my ($expire,$user) = @_;
1428 my $fexsend = "$FEXHOME/bin/fexsend";
1433 $fexsend .= " -M -D -k 30 -C"
1434 ." 'Your F*EX account has been inactive for $expire days,"
1435 ." you must download this file to reactivate it."
1436 ." Otherwise your account will be deleted.'"
1437 ." $FEXLIB/reactivation.txt $user";
1438 # on error show STDOUT and STDERR
1439 system "$fexsend >/dev/null 2>&1";
1445 warn "$0: cannot execute $fexsend for reactivation()\n";