3 # cleanup for F*EX service
5 # run this program via cron-job once at night!
7 # Author: Ulli Horlacher <framstag@rus.uni-stuttgart.de>
14 use Digest::MD5 'md5_hex';
16 use constant DS => 60*60*24;
19 exit if $ENV{SCRIPT_NAME};
21 unless ($FEXLIB = $ENV{FEXLIB}) {
23 $FEXLIB = $ENV{FEXHOME}.'/lib';
24 } elsif (-f '/usr/share/fex/lib/fex.ph') {
25 $FEXLIB = '/usr/share/fex/lib';
27 $FEXLIB = dirname(dirname(abs_path($0))).'/lib';
29 $ENV{FEXLIB} = $FEXLIB;
31 die "$0: no FEXLIB\n" unless -r "$FEXLIB/fex.pp";
39 # use fex.ph for site configuration!
41 our ($spooldir,@logdir,$docdir);
42 our ($akeydir,$ukeydir,$dkeydir,$skeydir,$gkeydir,$xkeydir,$lockdir);
43 our ($durl,$debug,$autodelete,$hostname,$admin,$admin_pw,$bcc);
44 our $keep_default = 5;
45 our $purge = $keep_default*3;
47 # load common code, local config : $HOME/lib/fex.ph
48 require "$FEXLIB/fex.pp" or die "$0: cannot load $FEXLIB/fex.pp - $!\n";
50 my $logdir = $logdir[0];
53 # (needed for reminder and account reactivation e-mails)
54 foreach my $lf (glob "$FEXHOME/locale/*/lib/lf.pl") { require $lf }
56 # default locale functions (from fex.pp)
57 $notify{english} = \¬ify;
58 $reactivation{english} = \&reactivation;
62 $opt_v = $opt_V = $opt_d = 0;
64 $opt_v = $opt_d if $opt_d; # debug mode, no real action
67 $isodate = isodate($today);
69 chdir $spooldir or die "$0: $spooldir - $!\n";
70 # open L,">>$logdir/cleanup.log";
72 # clean up regular spool
73 opendir $spooldir,'.' or die "$0: $spooldir - $!\n";
74 while ($to = readdir $spooldir) {
76 next if $to !~ /@/ or $_ = readlink($to) and not /\//;
78 if (@demo and -f "$to/.demo" and time > lmtime("$to/.demo")+$demo[1]*DS) {
79 logdel($to,"demo user $to deleted");
82 unless (opendir TO,$to) {
83 warn "$0: $spooldir/$to - $!\n";
86 while ($from = readdir TO) {
88 if ($from eq '@GROUP') {
89 foreach $group (glob "$to/$from/*") {
90 if (readlink $group and not -f $group) {
91 logdel($group,"$group deleted (master has gone)");
95 if (-d "$to/$from" and $from !~ /^\./) {
96 unless (opendir FROM,"$to/$from") {
97 warn "$0: $spooldir/$to/$from - $!\n";
100 while ($file = readdir FROM) {
101 next if $file eq '.' or $file eq '..';
102 if (-d "$to/$from/$file" and $file !~ /^\./) {
103 cleanup($to,$from,$file);
104 rmdir "$to/$from/$file" unless $opt_d;
108 rmdir "$to/$from" unless $opt_d;
113 unless (-f "$to/\@PERSISTENT" or $to eq $admin) {
114 @glob = glob "$to/*/* $to/\@MAINUSER/* $to/\@GROUP/*";
115 unless (@glob or -f "$to/\@") {
116 logdel($to,"$to deleted");
119 if ($login_check and -l "$user/.login") {
120 my $lc = &$login_check(readlink("$user/.login"));
122 if (-f "$user/\@~" and not "$user/@") {
123 rename "$user/\@~","$user/@" unless $opt_d;
124 logv("$user reanimated (login_check)");
127 rename "$user/@","$user/\@~" unless $opt_d;
128 logv("$user deactivated (login_check)");
135 # clean up download key lookup directory
136 if (chdir $dkeydir and opendir D,'.') {
137 while ($file = readdir D) {
138 if ($link = readlink $file and
139 (not -l "$link/dkey" or readlink "$link/dkey" ne $file)) {
140 logdel($file,".dkeys/$file deleted");
146 # clean up upload key lookup directory
147 if (chdir $ukeydir and opendir D,'.') {
148 while ($file = readdir D) {
149 next if $file eq '.' or $file eq '..';
150 if (($link = readlink $file and not -e "$link/upload"
151 or -f $file and time > lmtime($file)+DS)) {
152 logdel($file,".ukeys/$file deleted");
158 # clean up authorization key lookup directory
159 if (chdir $akeydir and opendir D,'.') {
160 while ($file = readdir D) {
161 if (-l $file and time > (lmtime($file)||0)+DS) {
162 logdel($file,".akeys/$file deleted");
168 # clean up extra download key lookup directory
169 if (chdir $xkeydir and opendir D,'.') {
170 while ($file = readdir D) {
171 next if $file eq '.' or $file eq '..';
172 if (-l $file and not (-f "$file/upload" or -f "$file/data")) {
173 logdel($file,".xkeys/$file deleted");
179 # clean up lock directory
180 if (chdir $lockdir and opendir D,'.') {
181 while ($file = readdir D) {
182 if (-f $file and time > lmtime($file)+DS) {
183 logdel($file,".locks/$file deleted");
189 # clean up error directory
190 if (chdir "$spooldir/.error" and opendir D,'.') {
191 while ($file = readdir D) {
193 $mtime = lmtime($file);
194 if ($mtime and $today > 10*$keep_default*DS+$mtime) {
195 if ($opt_d) { print "unlink .error/$file\n" }
196 else { logdel($file,".error/$file deleted") }
203 # clean up debug directory
204 if (chdir "$spooldir/.debug" and opendir D,'.') {
205 while ($file = readdir D) {
207 $mtime = lmtime($file);
208 if ($mtime and $today > $keep_default*DS+$mtime) {
209 # logdel($file,".debug/$file deleted");
210 if ($opt_d) { print "unlink .debug/$file\n" }
211 else { unlink $file }
218 # clean up subuser keys directory
219 if (chdir $skeydir and opendir D,'.') {
220 while ($file = readdir D) {
221 if (-f $file and open F,$file) {
223 $from = $to = $id = '';
226 $from = $2 if $1 eq 'from';
227 $to = $2 if $1 eq 'to';
228 $id = $2 if $1 eq 'id';
232 if ($from and $to and $id and open F,"$spooldir/$to/\@SUBUSER") {
234 if (/^\Q$from:$id\E$/) {
242 logdel($file,".skeys/$file deleted");
249 # clean up orphan subuser links
251 foreach $subuser (glob '*/@MAINUSER/*') {
252 if ($skey = readlink $subuser and not -f "$skeydir/$skey") {
253 logdel($subuser,"$subuser deleted");
256 foreach $subuser (glob '*/@MAINUSER') {
257 unlink $subuser unless $opt_d;
262 foreach my $okey (glob '*/@OKEY/*') {
263 if (time > lmtime($okey)+30*DS) {
264 logdel($okey,"$okey deleted");
269 # clean up group keys directory
270 if (chdir $gkeydir and opendir D,'.') {
271 while ($gkey = readdir D) {
272 if (-f $gkey and open F,$gkey) {
274 $from = $group = $id = '';
277 $from = $2 if $1 eq 'from';
278 $group = $2 if $1 eq 'to';
279 $id = $2 if $1 eq 'id';
284 $gf = "$spooldir/$from/\@GROUP/$group";
285 if ($from and $group and $id and open F,$gf) {
287 if (/^\Q$from:$id\E$/) {
295 logdel($gkey,".gkeys/$gkey deleted");
296 logdel($gf,"$gf deleted") if -l $gf;
303 # clean up self registration directory
304 if (chdir "$spooldir/.reg" and opendir D,'.') {
305 while ($file = readdir D) {
307 $mtime = lmtime($file);
308 if ($mtime and $today > $mtime+DS) {
309 logdel($file,".reg/$file deleted");
316 # send account expiration warning
317 if ($account_expire and $account_expire =~ /^(\d+)/) {
319 if (chdir $spooldir) {
320 chomp($admin_pw = slurp("$admin/\@")||'');
322 warn "create new fex account for $admin\n";
323 $admin_pw = randstring(8);
324 system("$FEXHOME/bin/fac -u $admin $admin_pw");
326 my $fid = "$FEXHOME/.fex/id";
328 mkdir "$FEXHOME/.fex",0700;
329 if (open $fid,'>',$fid) {
330 if ($durl =~ m{(https?://.+?)/}) {
333 print {$fid} "$hostname\n";
335 print {$fid} "$admin\n";
336 print {$fid} "$admin_pw\n";
339 warn"$0: cannot create $fid - $!";
343 opendir $spooldir,'.';
344 while ($user = readdir $spooldir) {
345 next unless -f "$user/\@";
346 next if -e "$user/$admin/reactivation.txt";
347 next if -e "$user/\@PERSISTENT";
348 next if $user !~ /@/ or -l $user;
349 next if $user =~ /^(fexmaster|fexmail)/ or $user eq $admin;
350 next if -l "$user/.login";
352 if (time > lmtime($user)+$expire*DS) {
353 # print "$spooldir/$user\n";
354 local $locale = readlink "$user/\@LOCALE";
355 $locale = 'english' unless $locale and $reactivation{$locale};
356 &{$reactivation{$locale}}($expire,$user);
367 foreach $vhost (keys %vhost) {
368 my $fexlib = $vhost{$vhost}.'/lib';
369 if (-f "$fexlib/fex.ph") {
370 warn "run $0 for $vhost :\n" if -t or $opt_v;
371 my $cmd = "HTTP_HOST=$vhost FEXLIB=$fexlib $_0 -V @_ARGV";
372 if ($opt_d) { print "$cmd\n" }
378 if ($notify_newrelease and $notify_newrelease !~ /^no$/i
379 or not defined $notify_newrelease) {
380 $notify_newrelease ||= $admin;
382 $snew = $FEXHOME.'/doc/new';
383 $new = slurp($snew)||'';
384 $_ = slurp("$FEXHOME/doc/version")||'';
385 if (/(\d+)/) { $qn = "new?$hostname:$1" }
386 else { $qn = "new?$hostname:0" }
387 print "checking for new F*EX release\n" if $opt_v;
390 $newnew = `wget -qO- http://fex.belwue.de/$qn 2>/dev/null`;
391 last if $newnew =~ /release/;
392 # $newnew = `wget -qO- http://fex.rus.uni-stuttgart.de/$qn 2>/dev/null`;
393 # last if $newnew =~ /release/;
395 if ($newnew =~ /release/) {
396 if ($newnew ne $new) {
397 if (open $sendmail,"|$sendmail $notify_newrelease $bcc") {
399 'From: fex\@$hostname'
400 'To: $notify_newrelease'
401 'Subject: new F*EX release'
406 if (open $snew,'>',$snew) {
407 print {$snew} $newnew;
420 my ($to,$from,$file) = @_;
421 my ($data,$download,$notify,$mtime,$warn,$dir,$filename,$dkey,$delay);
422 my $keep = $keep_default;
423 my $purge = $::purge || 3*$keep;
425 my $kf = "$to/$from/$file/keep";
426 my $ef = "$to/$from/$file/error";
429 $keep = readlink $kf || readlink "$to/\@KEEP" || $keep_default;
431 $file = "$to/$from/$file";
432 $data = "$file/data";
433 $download = "$file/download";
434 $notify = "$file/notify";
436 if ($file =~ /\/ADDRESS_BOOK/) {
437 logdel($file,"$file deleted");
438 } elsif (-d $file and not -f $data) {
439 if ($mtime = lmtime("$file/upload")) {
440 if ($today > $mtime+DS) {
441 verbose("rmrf $file (today=$today mtime_upload=$mtime)");
442 logdel($file,"$file deleted");
444 } elsif ($mtime = lmtime("$file/error")) {
445 $purge = $1*$keep if $purge =~ /(\d+).*keep/;
446 if ($today > $purge*DS+$mtime) {
447 verbose("rmrf $file (today=$today mtime_error=$mtime keep=$keep purge=$purge)");
448 logdel($file,"$file deleted");
451 logdel($file,"$file deleted");
453 } elsif (-s $download and -s $data and autodelete($file) !~ /NO/i) {
454 $delay = autodelete($file);
455 $delay = 1 if $delay !~ /^\d+$/;
457 $mtime = lmtime($download);
458 if ($mtime and $today > $delay*DS+$mtime
459 and logdel($data,"$data deleted")) {
460 if (open $ef,'>',$ef) {
461 printf {$ef} "%s has been autodeleted after download at %s\n",
462 filename($file),isodate(lmtime($download));
467 my $reactivation = $file =~ m{/\Q$admin/reactivation.txt\E$};
468 $warn = $reactivation ? $keep-5 : $keep-2;
469 $mtime = lmtime("$file/filename") || lmtime($data) || 0;
470 if ($today > $mtime+$keep*DS) {
471 if ($account_expire and $reactivation) {
472 if ($account_expire =~ /delete/) {
473 logdel($to,"$to removed - expired");
475 if (open $sendmail,"|$sendmail $admin $bcc") {
476 $account_expire =~ /(\d+)/;
477 my $expire = $1 || 0;
479 'From: fex\@$hostname'
481 'Subject: user $to expired'
483 'F*EX user $to has been inactive for $expire days'
484 'and has ignored the account reactivation mail.'
485 'You may want to delete this account.'
490 warn "$0: cannot send mail - $!\n";
494 if ($file =~ /^anonymous.*\/afex_\d/ or $to =~ /^_.+_/) {
496 logdel($file,"$file deleted") and
497 verbose("rmrf $file (today=$today mtime_upload=$mtime)");
498 } elsif (logdel($data,"$data deleted")) {
499 verbose("unlink $data (today=$today mtime=$mtime keep=$keep)");
500 if (open $ef,'>',$ef) {
502 $filename =~ s:.*/::;
503 print $ef "$filename is expired";
509 elsif ($file !~ /STDFEX$/ and
510 $mtime+$warn*DS < $today and
511 $dkey = readlink("$file/dkey") and
514 (readlink("$to/\@REMINDER")||'yes') ne 'no')
516 my $locale = readlink "$to/\@LOCALE" || readlink "$file/\@LOCALE";
517 $locale = 'english' unless $locale and $notify{$locale};
518 if (open my $c,"$file/comment") {
519 chomp ($comment = <$c>||'');
522 if (&{$notify{$locale}}(
525 filename => filename($file),
528 warn => int(($mtime-$today)/DS)+$keep,
529 autodelete => autodelete($file),
531 open $notify,'>',$notify;
533 print "sent reminder for $file\n" if -t or $opt_v;
535 warn "$0: reminder notification for $file failed\n";
543 my $adf = "$file/autodelete";
547 $autodelete = readlink $adf || '';
548 } elsif (open $adf,$adf) {
549 chomp($autodelete = <$adf>||'');
553 return $autodelete||$::autodelete;
557 my ($file,$msg) = @_;
563 if ($status = rmrf($file)) {
566 logv("$file DEL FAILED : $!");
567 warn "$file DEL FAILED : $!\n" if -t or $opt_v;
578 print "$msg\n" if -t or $opt_v;
581 foreach my $ld (@logdir) {
582 if (open my $log,">>$ld/cleanup.log") {
583 print {$log} "$isodate $msg\n";
594 while ($_ = shift @_) {
603 my @s = lstat(shift);