From 3aae246cf7f4af7ae49da09e5ed0c180f31f0c12 Mon Sep 17 00:00:00 2001 From: fextracker Date: Tue, 20 Sep 2016 04:00:06 +0200 Subject: [PATCH] Original release 20160919 2016-09-19: dop: do not show return value of <<perl-code;>> in dynamic html 2016-09-19: file if this code ends with a ";" 2016-09-07: new fex.ph config variable $purge 2016-09-01: dop: removed (forgotten) CGI::Carp 2016-08-29: fexsend,fexget: update function aborts if new version is not newer 2016-08-03: added timeout to error output, fixes hanging fup 2016-08-03: fexsend: fixed bug dangling symlinks raise an error in archive mode 2016-07-21: fexsrv: map http client headers HTTP-HOST HTTP-VERSION PROXY* to 2016-07-21: HTTP_X_HOST HTTP_X_VERSION HTTP_X_PROXY* 2016-07-11: added missing fex.png fexit.png to distribution 2016-05-31: fur: fixed bug no external user registration possible --- bin/ezz | 1074 +++++++++++++++++++++++++-- bin/fbm | 2 +- bin/fex_cleanup | 11 +- bin/fexget | 17 +- bin/fexsend | 37 +- bin/fexsrv | 22 +- bin/fpg | 42 +- bin/l | 96 ++- bin/logwatch | 7 +- bin/sexsend | 2 +- bin/zz | 1067 +++++++++++++++++++++++++- cgi-bin/fac | 7 +- cgi-bin/fup | 10 +- cgi-bin/fur | 3 +- doc/Changes | 11 + doc/SSL | 46 +- doc/concept | 42 +- doc/new | 7 +- doc/version | 2 +- htdocs/FAQ/admin.faq | 2 +- htdocs/FAQ/admin.html | 5 +- htdocs/FAQ/all.html | 5 +- htdocs/FAQ/faq.pl | 2 + htdocs/FAQ/local.html | 5 +- htdocs/FAQ/meta.faq | 2 +- htdocs/FAQ/meta.html | 5 +- htdocs/FAQ/misc.html | 5 +- htdocs/FAQ/user.html | 5 +- htdocs/FAQ/xx.html | 15 + htdocs/FAQ/xx.pl | 6 + htdocs/FAQ/zz.pl | 1 + htdocs/download/fexget | 17 +- htdocs/download/fexsend | 37 +- htdocs/download/sexsend | 2 +- htdocs/fex.png | Bin 0 -> 7188 bytes htdocs/fexit.html | 5 +- htdocs/fexit.png | Bin 0 -> 23729 bytes htdocs/version | 2 +- install | 13 +- lib/dop | 14 +- lib/fex.ph | 11 + lib/fex.pp | 10 + locale/czech/htdocs/FAQ/FAQ.html | 5 +- locale/french/htdocs/FAQ/FAQ.html | 5 +- locale/galician/htdocs/FAQ/FAQ.html | 5 +- locale/german/htdocs/FAQ/FAQ.html | 5 +- locale/italian/htdocs/FAQ/FAQ.html | 5 +- locale/spanish/htdocs/FAQ/FAQ.html | 5 +- locale/translations | 2 +- 49 files changed, 2469 insertions(+), 237 deletions(-) create mode 100644 htdocs/FAQ/xx.html create mode 100755 htdocs/FAQ/xx.pl create mode 100644 htdocs/FAQ/zz.pl create mode 100644 htdocs/fex.png create mode 100644 htdocs/fexit.png diff --git a/bin/ezz b/bin/ezz index 523dc8c..39dc91e 100755 --- a/bin/ezz +++ b/bin/ezz @@ -1,56 +1,1042 @@ -#!/bin/sh +#!/usr/bin/perl -w +# +# vv : visual versioning +# zz : generic shell clip board +# ezz : clip board editor +# +# http://fex.rus.uni-stuttgart.de/fstools/vv.html +# http://fex.rus.uni-stuttgart.de/fstools/zz.html +# +# by Ulli Horlacher +# +# Perl Artistic Licence +# +# vv is a script to handle file versions: +# list, view, recover, diff, purge, migrate, save, delete +# +# vv is an extension to emacs idea of backup~ files +# +# File versions are stored in local subdirectory .versions/ +# +# To use vv with jed, install to your jed library path: +# +# http://fex.rus.uni-stuttgart.de/sw/share/jedlib/vv.sl +# +# To use vv with vim, add to your .vimrc: +# +# autocmd BufWritePre * execute '! vv -s ' . shellescape(@%) +# autocmd BufWritePost * execute '! vv -b ' . shellescape(@%) +# +# To use vv with emacs, add to your .emacs: +# +# (add-hook 'before-save-hook (lambda () (shell-command ( +# concat "vv -s " (shell-quote-argument (buffer-file-name)))))) +# (add-hook 'after-save-hook (lambda () (shell-command ( +# concat "vv -b " (shell-quote-argument (buffer-file-name)))))) +# (setq make-backup-files nil) +# +# To use vv with ANY editor, first set: +# +# export EDITOR=your_favourite_editor +# alias ve='vv -e' +# +# and then edit your file with: +# +# ve file +# +# $HOME/.vvrc is the config file for vv -ZZ=${ZZ:-$HOME/.zz} +# 2013-04-15 initial version +# 2013-04-16 added options -m and -v +# 2013-04-18 added option -s +# 2013-04-22 realfilename() fixes symlink problematics +# 2013-04-22 use rsync instead of cp +# 2013-04-23 added option -I +# 2013-04-23 renamed from jedv to vv +# 2013-04-24 added options -e -0 +# 2013-05-09 added option -R +# 2013-05-22 modified option -d to double argument +# 2013-05-22 added vvrc with $exclude and @diff +# 2013-07-05 fixed bug potential endless loop in rotate() +# 2014-04-16 added change-file-test for opt_s (needs .versions/$file) +# 2014-04-18 added option -b : save backup +# 2014-05-02 fixed bug wrong file ownership when using as root +# 2014-06-18 options -r -d -v : parameter is optional, default is 1 +# 2014-06-18 fixed (stupid!) bug option -s does only sometimes saving +# 2014-06-20 options -d -v : argument is optional, default is last file +# 2014-07-22 fixed bug no (new) backup version 0 on option -r +# 2014-11-14 added option -D : delete last saved version +# 2014-11-14 make .versions/ mode 777 if parent directory is world writable +# 2015-03-19 allow write access by root even if file and .versions/ have different owners +# 2015-03-20 better error formating for jed +# 2015-06-02 added option -r . to restore last saved backup +# 2016-03-07 added options -M -L +# 2016-03-08 renamed option -I to -H +# 2016-05-02 added -A option to preserve ACLs with rsync +# 2016-06-07 option -v : use PAGER=cat if STDOUT is not a tty +# 2016-06-08 added zz, ezz and installer vvzz +# 2016-07-06 avoid empty $ZZ versioning +# 2016-09-12 added option -q quiet mode -usage() { - exec cat<; + $_ = <$prg>; + while (<$prg>) { + last if /^\s*$/ or /^#\s*\d\d\d\d-\d\d-\d\d/; + print; + } + exit; +} + +if ($opt_r) { + die "usage: $0 -r version-number file\n" unless @ARGV; + if ($ARGV[0] =~ /^(\d\d?|\.)$/) { $opt_r = shift } + else { $opt_r = 1 } + die "usage: $0 -r version-number file\n" if scalar @ARGV != 1; +} + +if ($opt_d) { + if (@ARGV and $ARGV[0] =~ /^\d\d?(:\d\d?)?$/) { $opt_d = shift } + else { $opt_d = 1 } + &check_ARGV; + die "usage: $0 -d version-number file\n" unless @ARGV; +} + +if ($opt_v) { + if (@ARGV and $ARGV[0] =~ /^\d\d?$/) { $opt_v = shift } + else { $opt_v = 1 } + &check_ARGV; + die "usage: $0 -v version-number file\n" unless @ARGV; +} + +if ($0 eq 've' or $opt_e) { + $a = pop @ARGV or die $usage; + $opt_e = 1; +} else { + $a = shift @ARGV; + die $usage if not $opt_r and @ARGV; +} + +unless (-e $vvrc) { + open $vvrc,'>',$vvrc or die "$0: cannot write $vvrc - $!\n"; + print {$vvrc} q{ +$exclude = q( + \.tmp$ + ^mutt-.+-\d+ + ^#.*#$ +); + +@diff = qw'diff -u'; + +}; + close $vvrc; +} + +require $vvrc; + +if ($a) { + + $file = realfilename($a); + $ofile = "$file~"; + $bfile = basename($file); + $dir = dirname($file); + $vdir = "$dir/.versions"; + $vfile = "$vdir/$bfile"; + $vfile0 = "$vfile~0~"; + $vfile1 = "$vfile~1~"; + $vfile01 = "$vfile~01~"; + + # change eugid if root and version directory belongs user + my @s = stat($vdir); + if ($> == 0 and (not @s or $s[4])) { + if (my @s = stat($a)) { + $) = $s[5]; + $> = $s[4]; + } + } + + if ($opt_r ne '.' and not ($opt_M or $opt_L)) { + if (not -e $file and -s $vfile) { + warn "$0: $a does not exist any more\n"; + print "found $vfile - recover it? "; + $_ = ; + copy($vfile,$file,'.') if /^y/i; + exit 0; + } + die "$0: $a does not exist\n" unless -e $file; + die "$0: $a is not a regular file\n" if -l $file or not -f $file; + } +} else { + $file = '*'; + $vdir = ".versions"; +} + +if ($opt_M) { + if (-d $opt_M and not -l $opt_M) { + my $vvv = "$opt_M/.versions"; + mkdir $vvv; + die "$0: cannot mkdir $vvv - $!\n" unless -d $vvv; + opendir $vvv,$vvv or die "$0: cannot opendir $vvv - $!\n"; + while (my $v = readdir($vvv)) { + mv100("$opt_M/$1") if -f "$vvv/$v" and $v =~ /(.+)~1~$/; + } + close $vvv; + $vvv .= "/.versions"; + unless (-d $vvv) { + mkdir $vvv or die "$0: cannot mkdir $vvv - $!\n"; + } + $vvv .= "/n"; + unlink $vvv; + symlink 100,$vvv or die "$0: cannot create $vvv - $!\n"; + } else { + die "usage: $0 -M file\n" if @ARGV or $opt_r; + mv100($opt_M); + } + exit; +} + +if ($opt_L) { + if (-d $opt_L and not -l $opt_L) { + my $vvv = "$opt_L/.versions"; + mkdir $vvv; + die "$0: cannot mkdir $vvv - $!\n" unless -d $vvv; + opendir $vvv,$vvv or die "$0: cannot opendir $vvv - $!\n"; + while (my $v = readdir($vvv)) { + mv10("$opt_L/$1") if -f "$vvv/$v" and $v =~ /(.+)~01~$/; + } + closedir $vvv; + $vvv .= "/.versions"; + unless (-d $vvv) { + mkdir $vvv or die "$0: cannot mkdir $vvv - $!\n"; + } + $vvv .= "/n"; + unlink $vvv; + symlink 10,$vvv or die "$0: cannot create $vvv - $!\n"; + } else { + die "usage: $0 -L file\n" if @ARGV or $opt_r; + mv10($opt_L); + } + exit; +} + +if ($opt_e) { + die $usage unless $a; + $editor = $ENV{EDITOR} or die "$0: environment variable EDITOR not set\n"; + system(qw'vv -s',$file) if -f $file; # save current version + system($editor,@ARGV,$file); exit $? if $?; + unlink $ofile; # delete new file~ created by editor + system(qw'vv -0',$file); # post rotating + system(qw'vv -b',$file); # save backup + exit; +} + +if ($opt_v) { + die "$0: no such file $bfile\n" unless $bfile; + if (-f "$vfile~0$opt_v~") { $vfile .= "~0$opt_v~" } + else { $vfile .= "~$opt_v~" } + if (-f $vfile) { + if (-t STDOUT) { + if (($ENV{EDITOR}||$0) =~ /jed/) { + $ENV{JEDINIT} = "SAVE_STATE=0"; + exec 'jed',$vfile,qw'-tmp -f set_readonly(1)'; + } elsif ($ENV{PAGER}) { + exec $ENV{PAGER},$vfile; + } else { + exec 'view',$vfile; + } + } else { + exec 'cat',$vfile; + } + } else { + die "$0: no $vfile\n"; + } + exit; +} + +if ($opt_p) { + opendir $vdir,$vdir or die "$0: no $vdir\n"; + while ($vfile = readdir($vdir)) { + next unless -f "$vdir/$vfile"; + $bfile = $vfile; + $bfile =~ s/~\d\d?~$//; + if (not -f $bfile or -l $bfile) { + unlink "$vdir/$vfile"; + $purge{$bfile}++; + } + } + if (@purge = keys %purge) { + foreach $p (@purge) { + printf "%2d %s~ purged\n",$purge{$p},$p; + } + } + exit; +} + +if ($opt_m) { + migrate('.'); + exit; +} + +if (length($opt_r)) { + die "$0: no such file $bfile\n" unless $bfile; + if ($opt_r eq '.') { + die "$0: no $vfile\n" unless -f $vfile; + copy($vfile,$file,$opt_r); + } else { + if ($opt_r =~ /^\d$/ and -f "$vfile~0$opt_r~") { + $vfile .= "~0$opt_r~" + } else { + $vfile .= "~$opt_r~" + } + die "$0: no version $opt_r for $file\n" unless -f $vfile; + if ($nfile = shift @ARGV) { + copy($vfile,$nfile); + } else { + copy($file,$vfile0) if mtime($file) > mtime($vfile0); + copy($vfile,$file); + } + } + exit; +} + +if (length($opt_d)) { + die "$0: no such file $bfile\n" unless $bfile; + @diff = qw'diff -u' unless @diff; + if ($opt_d =~ /^(\d\d?):(\d\d?)$/) { + if (-f "$vdir/$bfile~0$1~" and -f "$vdir/$bfile~0$2~") { + exec @diff,"$vdir/$bfile~0$2~","$vdir/$bfile~0$1~" + } else { + exec @diff,"$vdir/$bfile~$2~","$vdir/$bfile~$1~" + } + } else { + if (-f "$vdir/$bfile~0$opt_d~") { + exec @diff,"$vdir/$bfile~0$opt_d~",$file; + } else { + exec @diff,"$vdir/$bfile~$opt_d~",$file; + } + } + exit $!; +} + +if ($opt_s) { + die $usage unless $file; + if ($exclude) { + $exclude =~ s/^\s+//; + $exclude =~ s/\s+$//; + $exclude =~ s/\s+/|/g; + if ($bfile =~ /$exclude/) { + warn "\r\n$0: ignoring $bfile\n"; + exit; + } + } + unless (-d $vdir) { + mkdir $vdir or die "$0: cannot mkdir $vdir - $!\n"; + } + chmod 0777,$vdir if (stat $dir)[2] & 00002; + + # migrate old file~ to versions + if (-f $ofile and not -l $ofile and -r $ofile) { + $vfn = rotate($vfile); + rename($ofile,$vfn); + } + + # rotate and save if file has changed + if (-f $vfile1) { + if (md5f($vfile1) ne md5f($file)) { + $vfn = rotate($vfile); + copy($file,$vfn); + } + exit; + } + # rotate and save if file has changed + if (-f $vfile01) { + if (md5f($vfile01) ne md5f($file)) { + $vfn = rotate($vfile); + copy($file,$vfn); + } + exit; + } + # save new file + if ((readlink("$vdir/.versions/n")||10) == 100) { + copy($file,$vfile01); + } else { + copy($file,$vfile1); + } + exit; +} + +# backup version +if ($opt_b) { + die $usage unless $file; + unless (-d $vdir) { + mkdir $vdir or die "\r\n$0: cannot mkdir $vdir - $!\n"; + } + copy($file,$vfile); + if ($ENV{VIMRUNTIME}) { + print "\n"; + } else { + warn "$file --> $vfile\n" unless $opt_q; + } + exit; +} + +# special post rotating from -e +if ($opt_0) { + my @sb = stat $file or die "$0: $file - $!\n"; + if (-f $vfile1) { + while (my @sv = stat $vfile1) { + # no version change? + if ($sb[7] == $sv[7] and $sb[9] == $sv[9]) { + # rotate back + rb10($vfile); + } else { + last; + } + } + } + if (-f $vfile01) { + while (my @sv = stat $vfile01) { + # no version change? + if ($sb[7] == $sv[7] and $sb[9] == $sv[9]) { + # rotate back + rb10($vfile); + } else { + last; + } + } + } + exit; +} + +# delete last version, roll back +if ($opt_D) { + die "usage: $0 -D file\n" unless $vfile1 or $vfile01; + stat $file or die "$0: $file - $!\n"; + # 0 version? + if (-f $vfile0) { + unlink $vfile0; + } else { + # rotate back + rb10($vfile) if -f $vfile1; + rb100($vfile) if -f $vfile01; + } + exec $0,'-l',$file; + exit; +} + +# default! +if ($opt_l) { + `stty -a` =~ /columns (\d+)/; + $tw = ($1 || 80)-36; + if (opendir $vdir,$vdir) { + while ($vfile = readdir($vdir)) { + if (-f "$vdir/$vfile") { + if ($bfile) { + if ($vfile =~ /^\Q$bfile\E~(\d\d?)~$/) { + push @{$v{$file}},$1; + } + } else { + if ($vfile =~ /^(.+)~(\d\d?)~$/) { + push @{$v{$1}},$2; + } else { + push @{$v{$vfile}},0; + } + } + } + } + closedir $vdir; + $ct = ''; + foreach $file (sort keys %v) { + if (not -f $file or -l $file) { + warn "$0: orphaned $file~\n"; + next; + } + @v = sort @{$v{$file}}; + if ($bfile) { + @stat = stat $file or die "$0: $file - $!\n"; + print "version bytes date time"; + if (${'opt_+'}) { + print " content"; + $ct = content($file); + $ct =~ s/(.{$tw}).+/$1*/; + } + print "\n"; + if (length($v[0]) == 1) { $lf = "%s %10s %s %s\n" } + else { $lf = "%2s %10s %s %s\n" } + printf $lf,'.',size($stat[7]),isodate($stat[9]),$ct; + foreach $v (@v) { + $vfile = "$vdir/$bfile~$v~"; + @stat = stat $vfile or next; + if (${'opt_+'}) { + $ct = content($vfile); + $ct =~ s/(.{$tw}).+/$1*/; + } + printf $lf,int($v),size($stat[7]),isodate($stat[9]),$ct; + } + } else { + my $n = scalar(@v); + $n-- if $v[0] == 0; # do not count zero version + printf "%d %s\n",$n,$file; + } + } + } + exit; +} + + +sub size { + my $s = shift; + if ($s > 9999999999) { $s = int($s/2**30).'G' } + elsif ($s > 9999999) { $s = int($s/2**20).'M' } + elsif ($s > 9999) { $s = int($s/2**10).'k' } + return $s; +} + + +sub content { + my $file = shift; + my $ct; + local $_; + + chomp ($ct = `file $file`); + $ct =~ s/.*?: //; + $ct =~ s/,.*//; + + if ($ct =~ /text/ and open $file,$file) { + read $file,$_,1024; + close $file; + s/[\x00-\x20]+/ /g; + s/^ //; + s/ $//; + $ct = '"'.$_.'"'; + } + + return $ct; +} + + +sub isodate { + my @d = localtime shift; + return sprintf('%d-%02d-%02d %02d:%02d:%02d', + $d[5]+1900,$d[4]+1,$d[3],$d[2],$d[1],$d[0]); +} + +sub rotate { + my $vf = shift; # version base file + my $vf1 = "$vf~1~"; + my $vf01 = "$vf~01~"; + my ($vfi,$vfn); + + if (-f $vf1) { + for (my $i = 8; $i >= 0; $i--) { + $vfi = sprintf("%s~%d~",$vf,$i); + $vfn = sprintf("%s~%d~",$vf,$i+1); + if (-e $vfi) { + rename $vfi,$vfn or die "$0: $vfi --> $vfn : $!\n"; + } + } + # was there a version 0? + if (-e $vf1) { + my $bf = $vf; + $bf =~ s:/\.versions/:/:; + my @sb = stat $bf; + my @sv = stat $vf1; + # version change? (other size or mtime) + if (@sb and @sv and $sb[7] == $sv[7] and $sb[9] == $sv[9]) { + # same version + unlink $vf1; + } else { + # other version + rotate($vf); + } + } + return "$vf~1~"; + } elsif (-f $vf01) { + for (my $i = 98; $i >= 0; $i--) { + $vfi = sprintf("%s~%02d~",$vf,$i); + $vfn = sprintf("%s~%02d~",$vf,$i+1); + if (-e $vfi) { + rename $vfi,$vfn or die "$0: $vfi --> $vfn : $!\n"; + } + } + # was there a version 0? + if (-e $vf01) { + my $bf = $vf; + $bf =~ s:/\.versions/:/:; + my @sb = stat $bf; + my @sv = stat $vf01; + # version change? (other size or mtime) + if (@sb and @sv and $sb[7] == $sv[7] and $sb[9] == $sv[9]) { + # same version + unlink $vf01; + } else { + # other version + rotate($vf); + } + } + return "$vf~01~"; + } + + return "$vf~1~"; +} + +sub copy { + my ($from,$to,$restore) = @_; + + unless ($restore) { + if (-l $file or not -f $file) { + die "$0: $file is not a regular file\n"; + } + } + + if (open $to,'>>',$to) { + close $to; + if (system(qw'rsync -aA',$from,$to) == 0) { + if ($ENV{VIMRUNTIME}) { + print "\n"; + } else { + warn "$from --> $to\n" unless $opt_q; + } + } else { + exit $?; + } + } else { + die "\r\n$0: cannot write $to - $!\n"; + } +} + +sub realfilename { + my $file = shift; + + return $file unless -e $file; + + if (-l $file) { + my $link = readlink($file); + if ($link !~ /^\// and $file =~ m:(.*/).:) { + $link = $1 . $link; + } + return realfilename($link); + } else { + return $file; + } +} + +sub migrate { + my $dir = shift; + my $vdir = "$dir/.versions"; + my $dfile; + + opendir $dir,$dir or die "$0: cannot read directory $dir - $!\n"; + while ($file = readdir($dir)) { + $dfile = "$dir/$file"; + next if -l $dfile or $file eq '.' or $file eq '..'; + if (-d $dfile and $opt_R and $file ne '.versions') { + migrate($dfile); + } elsif (-f $dfile and $file =~ /~$/) { + if (-d $vdir) { + for ($i = 8; $i > 0; $i--) { + $n = $i+1; + rename "$vdir/$file$i~","$vdir/$file$n~"; + } + } else { + mkdir $vdir or die "$0: cannot mkdir $vdir - $!\n"; + } + $nfile = sprintf("%s/%s1~",$vdir,$file); + rename $dfile,$nfile or die "$0: cannot move $dfile to $nfile - $!\n"; + warn "$dfile --> $nfile\n" unless $opt_q; + } + } + closedir $dir; +} + +sub mtime { + my @s = stat shift; + return @s ? $s[9] : 0; +} + +sub md5f { + my $file = shift; + my $md5 = 0; + local $/; + + if (open $file,$file) { + $md5 = md5_hex(<$file>); + close $file; + } + return $md5; +} + + +# if ARGV is empty use last saved file as default file argument +sub check_ARGV { + local $_; + local *V; + + if (not @ARGV) { + if (-d '.versions' and open V,'ls -at .versions|') { + while () { + chomp; + if (-f) { + close V; + s/~\d+~$//; + @ARGV = ($_); + return; + } + } + } + } + +} + + +sub mv10 { + my $file = shift; + my $vfile = dirname($file).'/.versions/'.basename($file); + + die "$0: $file has no extended versions\n" unless -f "$vfile~01~"; + for (my $i=1; $i<10; $i++) { + my $vfile1 = "$vfile~$i~"; + my $vfile2 = "$vfile~0$i~"; + if (-f $vfile2) { + warn "$vfile2 --> $vfile1\n" unless $opt_q; + rename $vfile2,$vfile1 or die "$0: $!\n"; + } + } + for (my $i=10; $i<100; $i++) { + unlink "$vfile~$i~"; + } +} + +sub mv100 { + my $file = shift; + my $vfile = dirname($file).'/.versions/'.basename($file); + + die "$0: $file has already extended versions\n" if -f "$vfile~01~"; + die "$0: $file has no versions\n" unless -f "$vfile~1~"; + for (my $i=1; $i<10; $i++) { + my $vfile1 = "$vfile~$i~"; + my $vfile2 = "$vfile~0$i~"; + if (-f $vfile1) { + warn "$vfile1 --> $vfile2\n" unless $opt_q; + rename $vfile1,$vfile2 or die "$0: $!\n"; + } + } +} + + +# rotate back +sub rb10 { + my $vfile = shift; + + for (my $i = 1; $i <= 8; $i++) { + my $vfi = sprintf("%s~%d~",$vfile,$i); + my $vfn = sprintf("%s~%d~",$vfile,$i+1); + if (-f $vfn) { + rename $vfn,$vfi; + } else { + unlink $vfi if $i == 1; + last; + } + } +} + + +# rotate back +sub rb100 { + my $vfile = shift; + + for (my $i = 1; $i <= 98; $i++) { + my $vfi = sprintf("%s~%02d~",$vfile,$i); + my $vfn = sprintf("%s~%02d~",$vfile,$i+1); + if (-f $vfn) { + rename $vfn,$vfi; + } else { + unlink $vfi if $i == 1; + last; + } + } +} + + + +sub pathsearch { + my $prg = shift; + + foreach my $dir (split(':',$ENV{PATH})) { + return "$dir/$prg" if -x "$dir/$prg"; + } +} + + +# zz is the generic clip board program +# +# to use zz with vim, write to your .vimrc: +# +# noremap zz> :w !zz +# noremap zz< :r !zz -- +sub zz { + my $bs = 2**16; + my $wm = '>'; + my ($file,$tee,$x); + + if ("@ARGV" =~ /^(-h|--help)$/) { + print <<'EOD'; +zz is the generic clip board program. It can hold any data, ASCII or binary. +The clip board itself is $ZZ (default: $HOME/.zz). +See also the clip board editor "ezz". +Limitation: zz does not work across accounts or hosts! Use xx instead. + +Options and modes are: + + "zz" show content of $ZZ + "zz file(s)" copy file(s) content into $ZZ + "zz -" write STDIN (keyboard, mouse buffer) to $ZZ + "zz +" add STDIN (keyboard, mouse buffer) to $ZZ + "... | zz" write STDIN from pipe to $ZZ + "... | zz +" add STDIN from pipe to $ZZ + "... | zz -" write STDIN from pipe to $ZZ and STDOUT + "zz | ..." write $ZZ to pipe + "... | zz | ..." save pipe data to $ZZ (like tee) + "zz --" write $ZZ to STDOUT + "zz -v" show clip board versions (history) + "zz -1" write $ZZ version 1 to STDOUT + "zz -9" write $ZZ version 9 to STDOUT + +Examples: + + zz *.txt + ls -l | zz + zz | wc -l + (within vi) :w !zz + (within vi) :r !zz + (within mutt) |zz +EOD + exit; + } + + if ("@ARGV" eq '-v') { + exec qw'vv -+l',$ZZ; + } + + if ("@ARGV" =~ /^-(\d)$/) { + exec "vv -v $1 '$ZZ' | cat"; + } + + # read mode + if (-t STDIN and not @ARGV or "@ARGV" eq '--') { + exec 'cat',$ZZ; + } + + # write mode + system "vv -s '$ZZ' >/dev/null 2>&1" if -s $ZZ; + + if (@ARGV and $ARGV[0] eq '+') { + shift @ARGV; + $wm = '>>'; + } + + if ("@ARGV" eq '-') { + @ARGV = (); + $tee = 1 unless -t STDIN; + } + + $tee = 1 unless @ARGV or -t STDIN or -t STDOUT; + $bs = 2**12 if $tee; + + open $ZZ,$wm,$ZZ or die "$0: cannot write $ZZ - $!\n"; + + if (@ARGV) { + while ($file = shift @ARGV) { + if (-f $file) { + if (open $file,$file) { + while (read($file,$x,$bs)) { + my $s = syswrite $ZZ,$x; + defined($s) or die "$0: cannot write to $ZZ - $!\n"; + } + close $file; + } else { + warn "$0: cannot read $file - $!\n"; + } + } elsif (-e $file) { + warn "$0: $file is not a regular file\n"; + } else { + warn "$0: $file does not exist\n"; + } + } + close $ZZ; + $ZZ1 = $ZZ.'~1~'; + $ZZ1 =~ s:(.*)/(.*):$1/.versions/$2:; + if (-e $ZZ and not -s $ZZ and -s $ZZ1 ) { + system qw'rsync -aA',$ZZ1,$ZZ; + } + } else { + while (read(STDIN,$x,$bs)) { + syswrite $ZZ,$x; + syswrite STDOUT,$x if $tee; + } + } + + exit; +} + + +sub ezz { + my $bs = 2**16; + my $wm = '>'; + my $editor = $ENV{EDITOR} || 'vi'; + my ($out,$file,$x); + + $ENV{JEDINIT} = "SAVE_STATE=0"; + + if ("@ARGV" =~ /^(-h|--help)$/) { + print <<'EOD'; +ezz is the edit helper for the zz clip board program. +The clip board itself is $ZZ (default: $HOME/.zz). + +Options and modes are: + + "ezz" edit $ZZ with $EDITOR + "... | ezz" write STDIN from pipe to $ZZ and call $EDITOR + "... | ezz +" add STDIN from pipe to $ZZ and call $EDITOR + "ezz 'perl commands'" execute perl commands on $ZZ + "ezz - 'perl commands'" execute perl commands on $ZZ and show result + "ezz filter [args]" run filter [with args] on $ZZ + "ezz - filter [args]" run filter [with args] on $ZZ and show result Examples: ls -l | ezz - ezz "s/ /_/g" + ezz 's/ /_/g' ezz head -3 ezz - head -3 - -Limitation: zz does not work across different accounts! EOD + exit; + } + + system "vv -s '$ZZ' >/dev/null 2>&1" if -s $ZZ; + + unless (-t STDIN) { + if ("@ARGV" eq '+') { + @ARGV = (); + $wm = '>>'; + } + open $ZZ,$wm,$ZZ or die "$0: cannot write $ZZ - $!\n"; + syswrite $ZZ,$x while read(STDIN,$x,$bs); + close $ZZ; + } + + if (@ARGV) { + $out = shift @ARGV if $ARGV[0] eq '-'; + $cmd = shift @ARGV or exec 'cat',$ZZ; + rename $ZZ,"$ZZ~" or die "$0: cannot move $ZZ to $ZZ~ - $!\n"; + $cmd = quotemeta $cmd; + @ARGV = map { quotemeta } @ARGV; + if (pathsearch($cmd)) { + system "$cmd @ARGV <'$ZZ~'>'$ZZ'"; + } else { + system "perl -pe $cmd @ARGV <'$ZZ~'>'$ZZ'"; + } + if ($? == 0) { unlink "$ZZ~" } + else { rename "$ZZ~",$ZZ } + exec 'cat',$ZZ if $out; + } else { + exec $editor,$ZZ; + } + exit; } -JEDINIT="SAVE_STATE=0"; export JEDINIT - -if [ ! -t 0 ]; then - if [ x"$1"x = x+x ]; then - shift - cat >>$ZZ - else - cat >$ZZ - fi -fi - -test -z "$1" && exec ${EDITOR:-vi} $ZZ - -case "X$*" in - X-h) usage;; - X-r) exec mv $ZZ~ $ZZ;; -esac - -OUT="$1" -test "X$OUT" = X- && shift -test -z "$1" && exec cat $ZZ -mv $ZZ $ZZ~ -case `type "$1" 2>&1` in - *not\ found) perl -pe "$@" <$ZZ~>$ZZ || mv $ZZ~ $ZZ;; - *) "$@" <$ZZ~>$ZZ || mv $ZZ~ $ZZ;; -esac -test "X$OUT" = X- && exec cat $ZZ + +sub install { + my ($dir); + local $| = 1; + + print "Installation directory: "; + $dir = ||''; + chomp $dir; + $dir =~ s:/+$::; + $dir ||= '.'; + if ($dir eq '.') { + unlink qw'zz ezz vv'; + link $prg,'zz' or die "$0: cannot create zz - $!\n"; + link $prg,'ezz' or die "$0: cannot create ezz - $!\n"; + rename $prg,'vv' or die "$0: cannot create vv - $!\n"; + } else { + die "$0: $dir does not exist\n" unless -e $dir; + die "$0: $dir is not a directory\n" unless -d $dir; + die "$0: $dir is not writable\n" unless -w $dir; + chdir $dir or die "$0: cannot cd $dir - $!\n"; + unlink qw'zz ezz vv'; + system qw'rsync -a',$prg,'vv'; + exit $? if $?; + link 'vv','zz' or die "$0: cannot create $dir/zz - $!\n"; + link 'vv','ezz' or die "$0: cannot create $dir/ezz - $!\n"; + } + print "Installation completed. See:\n"; + print "\t$dir/vv -h\n"; + print "\t$dir/zz -h\n"; + print "\t$dir/ezz -h\n"; + exit; +} diff --git a/bin/fbm b/bin/fbm index 1d9c10d..e6d81aa 100755 --- a/bin/fbm +++ b/bin/fbm @@ -20,7 +20,7 @@ use constant M => 2**20; our ($SH,$windoof,$sigpipe,$useragent); our ($FEXSERVER); -our $version = 20160328; +our $version = 20160919; # server defaults my $server = 'fex.rus.uni-stuttgart.de'; diff --git a/bin/fex_cleanup b/bin/fex_cleanup index 1d87dcf..1f38d6b 100755 --- a/bin/fex_cleanup +++ b/bin/fex_cleanup @@ -41,7 +41,8 @@ our ($FEXHOME); our ($spooldir,@logdir,$docdir); our ($akeydir,$ukeydir,$dkeydir,$skeydir,$gkeydir,$xkeydir,$lockdir); our ($durl,$debug,$autodelete,$hostname,$admin,$admin_pw,$bcc); -$keep_default = 5; +our $keep_default = 5; +our $purge = $keep_default*3; # load common code, local config : $HOME/lib/fex.ph require "$FEXLIB/fex.pp" or die "$0: cannot load $FEXLIB/fex.pp - $!\n"; @@ -418,8 +419,9 @@ exit; sub cleanup { my ($to,$from,$file) = @_; my ($data,$download,$notify,$mtime,$warn,$dir,$filename,$dkey,$delay); - my $comment = ''; my $keep = $keep_default; + my $purge = $::purge || 3*$keep; + my $comment = ''; my $kf = "$to/$from/$file/keep"; my $ef = "$to/$from/$file/error"; local $_; @@ -440,8 +442,9 @@ sub cleanup { logdel($file,"$file deleted"); } } elsif ($mtime = lmtime("$file/error")) { - if ($today > 3*$keep*DS+$mtime) { - verbose("rmrf $file (today=$today mtime_error=$mtime keep=$keep)"); + $purge = $1*$keep if $purge =~ /(\d+).*keep/; + if ($today > $purge*DS+$mtime) { + verbose("rmrf $file (today=$today mtime_error=$mtime keep=$keep purge=$purge)"); logdel($file,"$file deleted"); } } else { diff --git a/bin/fexget b/bin/fexget index b0616a1..3ac605f 100755 --- a/bin/fexget +++ b/bin/fexget @@ -13,6 +13,7 @@ use strict qw'vars subs'; use Config; use POSIX; use Encode; +use Cwd 'abs_path'; use Getopt::Std; use File::Basename; use Socket; @@ -30,7 +31,7 @@ our $SH; our ($fexhome,$idf,$tmpdir,$windoof,$useragent); our ($xv,%autoview); our $bs = 2**16; # blocksize for tcp-reading and writing file -our $version = 20160328; +our $version = 20160919; our $CTYPE = 'ISO-8859-1'; our $fexsend = $ENV{FEXSEND} || 'fexsend'; our $DEBUG = $ENV{DEBUG}; @@ -89,6 +90,7 @@ usage: $0 [-v] [-m limit] [-s filename] [-o] [-k] [-X] [-P proxy:port] F*EX-URL( or: $0 [-v] -a or: $0 -l [-i tag] or: $0 -H + or: $0 -V options: -v verbose mode -m limit kB/s -s save to filename (-s- means: write to STDOUT/pipe) @@ -102,6 +104,7 @@ options: -v verbose mode -i tag alternate server/account, see: $fexsend -h -P use Proxy for connection to the F*EX server -H show hints and examples + -V show version and ask for upgrade argument: F*EX-URL may be file number (see: $0 -l) EOD @@ -167,10 +170,15 @@ if ($opt_V) { $_ = ||''; if (/^y/i) { my $new = `wget -nv -O- http://fex.belwue.de/download/fexget`; - if ($new !~ /upgrade fexget/) { + my $newversion = $1 if $new =~ /version = (\d+)/; + if ($new !~ /upgrade fexget/ or not $newversion) { die "$0: bad update\n"; } - system qw'cp -a',$_0,$_0.'_old'; + if ($newversion <= $version) { + die "$0: no newer version\n"; + } + $_0 = abs_path($_0); + system qw'rsync -a',$_0,$_0.'_old'; exit $? if $?; open $_0,'>',$_0 or die "$0: cannot write $_0. - $!\n"; print {$_0} $new; @@ -178,6 +186,7 @@ if ($opt_V) { exec $_0,qw'-V .'; } } + exit; exit if "@ARGV" eq '.'; } @@ -328,7 +337,7 @@ URL: foreach my $url (@ARGV) { ($file) = grep { $_ = $1 if /^X-File:\s+(.+)/ } @r; $file = $url unless $file; $file =~ s:.*/::; - printf "%s deleted\n",urldecode($file); + printf "%s deleted\n",locale(decode_utf8(urldecode($file))); } else { s:HTTP/[\d\. ]+::; die "$0: server response: $_"; diff --git a/bin/fexsend b/bin/fexsend index f47bed4..fae77e9 100755 --- a/bin/fexsend +++ b/bin/fexsend @@ -17,10 +17,10 @@ use IO::Handle; use IO::Socket::INET; use Getopt::Std; use File::Basename; -use Cwd qw'abs_path'; +use Cwd 'abs_path'; use Fcntl qw':flock :mode'; -use Digest::MD5 qw'md5_hex'; # encrypted ID / SID -use Time::HiRes qw'time'; +use Digest::MD5 'md5_hex'; # encrypted ID / SID +use Time::HiRes 'time'; # use Smart::Comments; use constant k => 2**10; use constant M => 2**20; @@ -37,7 +37,7 @@ our ($tpid,$frecipient); our ($FEXID,$FEXXX,$HOME); our (%alias); our $chunksize = 0; -our $version = 20160328; +our $version = 20160919; our $_0 = $0; our $DEBUG = $ENV{DEBUG}; @@ -248,6 +248,14 @@ Partner program xx is an internet clipboard. See: xx -h Partner program fexget is for downloading. See: fexget -h +fexsend stores the login data (server, user and auth-ID) in the file +$HOME/.fex/id +The format of this file is ([data] is optional): + +server-URL[!proxy[:port[:chunk-size]] +e-mail-address +auth-ID + For temporary usage of a HTTP proxy use: $0 -P your_proxy:port:chunksize_in_MB file recipient Example: @@ -361,10 +369,15 @@ if ($xx) { $_ = ||''; if (/^y/i) { my $new = `wget -nv -O- http://fex.belwue.de/download/fexsend`; - if ($new !~ /upgrade fexsend/) { + my $newversion = $1 if $new =~ /version = (\d+)/; + if ($new !~ /upgrade fexsend/ or not $newversion) { die "$0: bad update\n"; } - system qw'cp -aL',$_0,$_0.'_old'; + if ($newversion <= $version) { + die "$0: no newer version\n"; + } + $_0 = abs_path($_0); + system qw'rsync -a',$_0,$_0.'_old'; exit $? if $?; open $_0,'>',$_0 or die "$0: cannot write $_0. - $!\n"; print {$_0} $new; @@ -372,6 +385,7 @@ if ($xx) { exec $_0,qw'-V .'; } } + exit; exit if "@ARGV" eq '.'; } @@ -3206,9 +3220,10 @@ sub query_sid { $sid = $id; if ($port eq 443 or $proxy) { + return if $opt_d; return if $features; # early return if we know enough - $req = "OPTIONS /FEX HTTP/1.1"; - $req = "HEAD /index.html HTTP/1.1"; + $req = "OPTIONS /FEX HTTP/1.1"; # does not work with (some) proxies + $req = "GET /SID HTTP/1.1"; # needed as FEATURES query } else { $req = "GET /SID HTTP/1.1"; } @@ -3469,7 +3484,8 @@ sub readahead { sub fileid { my $file = shift; - my @s = stat($file); + my $dirmode = shift; + my @s = $dirmode ? lstat($file) : stat($file); if (@s) { return md5_hex($file.$s[0].$s[1].$s[7].$s[9]); @@ -3528,6 +3544,9 @@ sub fmd { next if $file eq '..'; if ($file eq '.') { $fmd .= fileid($dir); + } elsif (-l "$dir/$file") { + # hack for dangling symlinks: do not raise an error + $fmd .= fileid("$dir/$file",'dirmode'); } else { $fmd .= fmd("$dir/$file"); } diff --git a/bin/fexsrv b/bin/fexsrv index 8bef7fc..2843167 100755 --- a/bin/fexsrv +++ b/bin/fexsrv @@ -15,6 +15,7 @@ BEGIN { # stunnel workaround $SIG{CHLD} = "DEFAULT"; $ENV{PERLINIT} = q{ + $ENV{LC_ALL} = 'en_US.UTF-8'; unshift @INC,(getpwuid($<))[7].'/perl'; # web error handler $SIG{__DIE__} = $SIG{__WARN__} = sub { @@ -92,7 +93,7 @@ foreach my $lib ( # import from fex.pp our ($hostname,$debug,$timeout,$max_error,$max_error_handler); our ($spooldir,@logdir,$docdir,$xkeydir,$akeydir,$lockdir); -our ($force_https,$default_locale,$bs,$MB,$adlm); +our ($force_https,$default_locale,$bs,$MB,$adlm,@forbidden_user_agents); our (@locales); # load common code (local config: $FEXHOME/lib/fex.ph) @@ -144,7 +145,7 @@ else { if ($ssl_ra) { $ENV{PROTO} = 'https'; $ENV{REMOTE_ADDR} = $ra = $ssl_ra; - if ($ssl_ra =~ /\w:\w/) { + if ($ssl_ra =~ /[\w:]:\w/) { # ($rh) = `host $ssl_ra 2>/dev/null` =~ /name pointer (.+)\.$/; $^W = 0; eval 'use Socket6'; $^W = 1; http_error(503) if $@; @@ -368,6 +369,8 @@ REQUEST: while (*STDIN) { if ($uri =~ /\\|%5c/i) { badchar("\\") } } + my $fua = join('|',@forbidden_user_agents); + while ($_ = shift @header) { # header inquisition! @@ -381,12 +384,8 @@ REQUEST: while (*STDIN) { exit; } - if ($header =~ /\nRange:/ and /^User-Agent: (FDM)/) { - disconnect($1,"499 Download Manager $1 Not Supported",30); - } - - if (/^User-Agent: (Java\/[\d\.]+)/) { - disconnect($1,"499 User-Agent $1 Not Supported",30); + if ($fua and /^User-Agent: ($fua)/) { + disconnect($1,"499 User Agent $1 Not Supported",30); } if (/^Range:.*,/) { @@ -436,7 +435,7 @@ REQUEST: while (*STDIN) { } # HTTP header ==> environment variables - if (/^([\w\-]+):\s*(.+)/s) { + if (/^([\w\-_]+):\s*(.+)/s) { $http_var = $1; $http_val = $2; $http_var =~ s/-/_/g; @@ -448,7 +447,10 @@ REQUEST: while (*STDIN) { } else { $http_val =~ s/\s+/ /g; if ($http_var =~ /^HTTP_(HOST|VERSION)$/) { - $http_var = 'X-'.$http_var; + $http_var = 'HTTP_X_'.$1; + } elsif ($http_var =~ /^PROXY/) { + # http://cert.at/warnings/all/20160718.html + $http_var = 'HTTP_X_'.$http_var; } elsif ($http_var !~ /^CONTENT_/) { $http_var = 'HTTP_'.$http_var; } diff --git a/bin/fpg b/bin/fpg index 5f2f751..7e616bd 100755 --- a/bin/fpg +++ b/bin/fpg @@ -2,7 +2,7 @@ # # Programname: fpg - Frams' Perl grep # Author: framstag@rus.uni-stuttgart.de -# Copyright: GPL +# Licence: Perl Artistic # # History: # 2003-02-27 Framstag initial version @@ -18,13 +18,16 @@ # -n ==> -S, new -n option # 2008-10-14 Framstag added option -M # 2008-11-23 Framstag added option -~ +# 2016-06-12 Framstag option -o respects (match) use Getopt::Std; use Term::ReadLine; use locale; -sub usage { - die <30 and not /\\w/' script #See "perldoc perlre" for help on regular expressions. -} -$0 =~ s:.*/::; -$| = 1; $maxlen = 0; @@ -67,10 +67,15 @@ $opt_S = 4; $opt_x = $opt_X = ''; $opt_R = "\n"; -usage() if !getopts('hirvlLFMopscQen~S:R:C:x:X:') or $opt_h and not @ARGV; +getopts('hirvlLFMopscQen~S:R:C:x:X:') or die $usage; + +if ($opt_h) { + print $usage; + exit; +} unless ($opt_Q) { - $exp = shift or usage(); + $exp = shift or die $usage; } if ($opt_C and ($opt_l or $opt_L or $opt_s or $opt_v or $opt_p or $opt_M)) { @@ -129,7 +134,7 @@ sub scan { } else { $exp =~ s/([\@\$\%\^\&\*\(\)\+\[\]\{\}\\\|\.\?])/\\$1/g if $opt_F; $exp = '(?i)'.$exp if $opt_i; - $exp = '(?s)'.$exp if $opt_p or $opt_R; + $exp = '(?s)'.$exp if $opt_p or $opt_R ne "\n"; #? $exp =~ s/\.\*\*/[.\n]*/g; } @@ -277,12 +282,19 @@ sub grepf { else { $n++ while /$exp/omg } } else { if ($opt_o) { - my $m = ''; - while (s/($exp)//) { - $n++; - $m .= "$1\n"; + if ($exp =~ /\([^?]+\)/) { + if (/$exp/) { + $n++; + $_ = "$1\n"; + } + } else { + my $m = ''; + while (s/($exp)//) { + $n++; + $m .= "$1\n"; + } + $_ = $m; } - $_ = $m; } elsif ($opt_Q) { $n += s/($exp)/$B$1$N/mg; } else { diff --git a/bin/l b/bin/l index affd4a2..203e310 100755 --- a/bin/l +++ b/bin/l @@ -13,7 +13,7 @@ use Getopt::Std; # the name of the game $0 =~ s:.*/::; -$ENV{LC_CTYPE} = 'C'; +$ENV{LC_ALL} = 'C'; # unshift @ARGV,split /\s+/,$ENV{'l_opt'} if $ENV{'l_opt'}; @@ -32,9 +32,20 @@ $opt_l = 1 if $0 eq 'll'; $opt_l = $opt_i = $opt_a = $opt_S = 1 if $0 eq 'lll'; &examples if $opt_E; if ($0 eq 'lf' or $0 eq 'llf') { - $opt_F ||= shift or usage(1); - $opt_R ||= scalar(@ARGV) || ($opt_F eq '.'); - $opt_l ||= $0 eq 'llf'; + $opt_l = $0 eq 'llf'; + if (scalar(@ARGV) == 0) { + die usage(1); + } elsif (scalar(@ARGV) == 1) { + $opt_F = shift; + $opt_R = $opt_F if $opt_F eq '.'; + } elsif (-d $ARGV[-1]) { + $opt_R = pop(@ARGV); + $opt_F = join('|',@ARGV); + } else { + $opt_F = join('|',@ARGV); + } + @ARGV = (); + @ARGV = ($opt_R) if -d $opt_R; } $postsort = $opt_t||$opt_s; @@ -138,6 +149,8 @@ sub collect { my @files = @_; my $f; + getacl(@files) if $opt_l and not $opt_n; + # loop over all argument files/directories foreach $f (@files) { @@ -175,8 +188,8 @@ sub collect { $f =~ s:/$:: if $opt_d; # on trailing / list subdirs, too - if ($f =~ m:/$:) { &list(&getfiles($f)) } - elsif ($f eq '') { &list('/') } + if ($f =~ m:/$:) { list(getfiles($f)) } + elsif ($f eq '') { list('/') } else { if ($opt_L) { unless (-e $f) { @@ -262,6 +275,8 @@ sub list { elsif ($i eq 'a') { $line .= sprintf '%10s %10s %10s ', $dates{'a'},$dates{'m'},$dates{'c'} } } else { + # $mode =~ s/(....)(...)/sprintf($1.uc($2))/e if $ACL{$file}; + substr($mode,4,3) = uc(substr($mode,4,3)) if $ACL{$file}; if ($i eq 'm') { $line .= $mode.' ' } elsif ($i eq 'u') { $line .= sprintf '%-8s ', $uid } elsif ($i eq 'g') { $line .= sprintf '%-8s ', $gid } @@ -283,15 +298,22 @@ sub list { } else { if ($opt_n) { - if ($opt_l) { $line .= sprintf "%06o %6d %6d $z%15s %10d ", - $mode,$uid,$gid,$size,$date } - else { $line .= sprintf "%06o $z%15s %10d ", - $mode,$size,$date } + if ($opt_l) { + $line .= sprintf "%06o %6d %6d $z%15s %10d ", + $mode,$uid,$gid,$size,$date; + } else { + $line .= sprintf "%06o $z%15s %10d ",$mode,$size,$date; + } } else { - if ($opt_l) { $line .= sprintf "%s %-8s %-8s $z%19s %s ", - $mode,$uid,$gid,$size,$date } - else { $line .= sprintf "%s $z%19s %s ", - $mode,$size,substr($date,0,-3) } + if ($opt_l) { + # $mode .= $ACL{$file} ? '+' : ' '; + # $mode =~ s/(....)(...)/sprintf($1.uc($2))/e if $ACL{$file}; + substr($mode,4,3) = uc(substr($mode,4,3)) if $ACL{$file}; + $line .= sprintf "%s %-8s %-8s $z%19s %s ", + $mode,$uid,$gid,$size,$date; + } else { + $line .= sprintf "%s $z%19s %s ",$mode,$size,substr($date,0,-3); + } } if ($opt_i) { $line .= sprintf '%3s %10s ',$links,$inode } @@ -375,13 +397,14 @@ sub info { substr($mode,8,1) =~ tr/-x/Tt/ if -k _; $mode = $type.$mode; } else { - # with short list display only effektive file access modes + # with short list display only effective file access modes + use filetest 'access'; # respect ACLs ==> cannot use pseudofile _ $mode = $type - . (-r _ ? 'R' : '-') - . (-w _ ? 'W' : '-') - . (-x _ ? 'X' : '-'); - substr($mode,2,1) =~ tr/-x/Ss/ if -u _ or -g _; - substr($mode,3,1) =~ tr/-x/Tt/ if -k _; + . (-r $file ? 'R' : '-') + . (-w $file ? 'W' : '-') + . (-x $file ? 'X' : '-'); + substr($mode,2,1) =~ tr/-x/Ss/ if -u $file or -g $file; + substr($mode,3,1) =~ tr/-x/Tt/ if -k $file; } } @@ -425,6 +448,25 @@ sub info { return ($linkname,$inode,$links,$size,$mode,$uid,$gid,$date,%dates); } +# get ACLs +# +# INPUT: filenames +# +# GLOBAL: @ACL +sub getacl { + my @files; + + $getfacl ||= pathsearch('getfacl') or return; + # warn "### @_\n"; + foreach my $file (@_) { push @files,$file if -e $file } + if (@files and open my $acl,'-|',$getfacl,'-ps',@files) { + while (<$acl>) { + $ACL{$1} = $1 if /^# file: (.+)/; + } + close $acl; + } +} + # reformat integer into 3-digit doted format # (when non-numerical mode is set) @@ -482,6 +524,7 @@ sub getfiles { warn "$0: cannot read $dir : $!\n"; } + getacl(@dirs,@files) if $opt_l and not $opt_n; return (@dirs,@files); } @@ -497,6 +540,15 @@ sub nodes { } +sub pathsearch { + my $prg = shift; + + foreach my $dir (split(':',$ENV{PATH})) { + return "$dir/$prg" if -x "$dir/$prg"; + } +} + + # reformat timetick to ISO date string # # INPUT: timetick @@ -552,7 +604,7 @@ sub usage { print OUT "usage: $0 $opts [-F regexp] [file...]\n"; } $opts =~ s/R//; - print OUT "usage: lf $opts regexp [directory...]\n"; + print OUT "usage: lf $opts regexp [regexp...] [directory]\n"; print OUT <) { s/\r//; + s/[^\x09\x20-\xFF]/_/g; if (/^Content-Disposition:.*name="FILE".*filename="(.+)"/i) { print " FILE=\"$1\"\n"; } elsif (/^Content-Disposition:.*name="(\w+)"/i) { diff --git a/bin/sexsend b/bin/sexsend index 9a7d48b..8b9e1d1 100755 --- a/bin/sexsend +++ b/bin/sexsend @@ -19,7 +19,7 @@ use constant M => 2**20; eval 'use Net::INET6Glue::INET_is_INET6'; -our $version = 20160328; +our $version = 20160919; our $DEBUG = $ENV{DEBUG}; my %SSL = (SSL_version => 'TLSv1'); diff --git a/bin/zz b/bin/zz index 0317412..39dc91e 100755 --- a/bin/zz +++ b/bin/zz @@ -1,55 +1,1042 @@ -#!/bin/sh +#!/usr/bin/perl -w +# +# vv : visual versioning +# zz : generic shell clip board +# ezz : clip board editor +# +# http://fex.rus.uni-stuttgart.de/fstools/vv.html +# http://fex.rus.uni-stuttgart.de/fstools/zz.html +# +# by Ulli Horlacher +# +# Perl Artistic Licence +# +# vv is a script to handle file versions: +# list, view, recover, diff, purge, migrate, save, delete +# +# vv is an extension to emacs idea of backup~ files +# +# File versions are stored in local subdirectory .versions/ +# +# To use vv with jed, install to your jed library path: +# +# http://fex.rus.uni-stuttgart.de/sw/share/jedlib/vv.sl +# +# To use vv with vim, add to your .vimrc: +# +# autocmd BufWritePre * execute '! vv -s ' . shellescape(@%) +# autocmd BufWritePost * execute '! vv -b ' . shellescape(@%) +# +# To use vv with emacs, add to your .emacs: +# +# (add-hook 'before-save-hook (lambda () (shell-command ( +# concat "vv -s " (shell-quote-argument (buffer-file-name)))))) +# (add-hook 'after-save-hook (lambda () (shell-command ( +# concat "vv -b " (shell-quote-argument (buffer-file-name)))))) +# (setq make-backup-files nil) +# +# To use vv with ANY editor, first set: +# +# export EDITOR=your_favourite_editor +# alias ve='vv -e' +# +# and then edit your file with: +# +# ve file +# +# $HOME/.vvrc is the config file for vv + +# 2013-04-15 initial version +# 2013-04-16 added options -m and -v +# 2013-04-18 added option -s +# 2013-04-22 realfilename() fixes symlink problematics +# 2013-04-22 use rsync instead of cp +# 2013-04-23 added option -I +# 2013-04-23 renamed from jedv to vv +# 2013-04-24 added options -e -0 +# 2013-05-09 added option -R +# 2013-05-22 modified option -d to double argument +# 2013-05-22 added vvrc with $exclude and @diff +# 2013-07-05 fixed bug potential endless loop in rotate() +# 2014-04-16 added change-file-test for opt_s (needs .versions/$file) +# 2014-04-18 added option -b : save backup +# 2014-05-02 fixed bug wrong file ownership when using as root +# 2014-06-18 options -r -d -v : parameter is optional, default is 1 +# 2014-06-18 fixed (stupid!) bug option -s does only sometimes saving +# 2014-06-20 options -d -v : argument is optional, default is last file +# 2014-07-22 fixed bug no (new) backup version 0 on option -r +# 2014-11-14 added option -D : delete last saved version +# 2014-11-14 make .versions/ mode 777 if parent directory is world writable +# 2015-03-19 allow write access by root even if file and .versions/ have different owners +# 2015-03-20 better error formating for jed +# 2015-06-02 added option -r . to restore last saved backup +# 2016-03-07 added options -M -L +# 2016-03-08 renamed option -I to -H +# 2016-05-02 added -A option to preserve ACLs with rsync +# 2016-06-07 option -v : use PAGER=cat if STDOUT is not a tty +# 2016-06-08 added zz, ezz and installer vvzz +# 2016-07-06 avoid empty $ZZ versioning +# 2016-09-12 added option -q quiet mode + +use Getopt::Std; +use File::Basename; +use Digest::MD5 'md5_hex'; +use Cwd 'abs_path'; + +$prg = abs_path($0); +$0 =~ s:.*/::; + +$ZZ = $ENV{ZZ} || "$ENV{HOME}/.zz"; + +&install if $0 eq 'vvzz'; +&zz if $0 eq 'zz'; +&ezz if $0 eq 'ezz'; + +# vv +$usage = <; + $_ = <$prg>; + while (<$prg>) { + last if /^\s*$/ or /^#\s*\d\d\d\d-\d\d-\d\d/; + print; + } + exit; +} + +if ($opt_r) { + die "usage: $0 -r version-number file\n" unless @ARGV; + if ($ARGV[0] =~ /^(\d\d?|\.)$/) { $opt_r = shift } + else { $opt_r = 1 } + die "usage: $0 -r version-number file\n" if scalar @ARGV != 1; +} + +if ($opt_d) { + if (@ARGV and $ARGV[0] =~ /^\d\d?(:\d\d?)?$/) { $opt_d = shift } + else { $opt_d = 1 } + &check_ARGV; + die "usage: $0 -d version-number file\n" unless @ARGV; +} + +if ($opt_v) { + if (@ARGV and $ARGV[0] =~ /^\d\d?$/) { $opt_v = shift } + else { $opt_v = 1 } + &check_ARGV; + die "usage: $0 -v version-number file\n" unless @ARGV; +} + +if ($0 eq 've' or $opt_e) { + $a = pop @ARGV or die $usage; + $opt_e = 1; +} else { + $a = shift @ARGV; + die $usage if not $opt_r and @ARGV; +} + +unless (-e $vvrc) { + open $vvrc,'>',$vvrc or die "$0: cannot write $vvrc - $!\n"; + print {$vvrc} q{ +$exclude = q( + \.tmp$ + ^mutt-.+-\d+ + ^#.*#$ +); + +@diff = qw'diff -u'; + +}; + close $vvrc; +} + +require $vvrc; + +if ($a) { + + $file = realfilename($a); + $ofile = "$file~"; + $bfile = basename($file); + $dir = dirname($file); + $vdir = "$dir/.versions"; + $vfile = "$vdir/$bfile"; + $vfile0 = "$vfile~0~"; + $vfile1 = "$vfile~1~"; + $vfile01 = "$vfile~01~"; + + # change eugid if root and version directory belongs user + my @s = stat($vdir); + if ($> == 0 and (not @s or $s[4])) { + if (my @s = stat($a)) { + $) = $s[5]; + $> = $s[4]; + } + } + + if ($opt_r ne '.' and not ($opt_M or $opt_L)) { + if (not -e $file and -s $vfile) { + warn "$0: $a does not exist any more\n"; + print "found $vfile - recover it? "; + $_ = ; + copy($vfile,$file,'.') if /^y/i; + exit 0; + } + die "$0: $a does not exist\n" unless -e $file; + die "$0: $a is not a regular file\n" if -l $file or not -f $file; + } +} else { + $file = '*'; + $vdir = ".versions"; +} + +if ($opt_M) { + if (-d $opt_M and not -l $opt_M) { + my $vvv = "$opt_M/.versions"; + mkdir $vvv; + die "$0: cannot mkdir $vvv - $!\n" unless -d $vvv; + opendir $vvv,$vvv or die "$0: cannot opendir $vvv - $!\n"; + while (my $v = readdir($vvv)) { + mv100("$opt_M/$1") if -f "$vvv/$v" and $v =~ /(.+)~1~$/; + } + close $vvv; + $vvv .= "/.versions"; + unless (-d $vvv) { + mkdir $vvv or die "$0: cannot mkdir $vvv - $!\n"; + } + $vvv .= "/n"; + unlink $vvv; + symlink 100,$vvv or die "$0: cannot create $vvv - $!\n"; + } else { + die "usage: $0 -M file\n" if @ARGV or $opt_r; + mv100($opt_M); + } + exit; +} + +if ($opt_L) { + if (-d $opt_L and not -l $opt_L) { + my $vvv = "$opt_L/.versions"; + mkdir $vvv; + die "$0: cannot mkdir $vvv - $!\n" unless -d $vvv; + opendir $vvv,$vvv or die "$0: cannot opendir $vvv - $!\n"; + while (my $v = readdir($vvv)) { + mv10("$opt_L/$1") if -f "$vvv/$v" and $v =~ /(.+)~01~$/; + } + closedir $vvv; + $vvv .= "/.versions"; + unless (-d $vvv) { + mkdir $vvv or die "$0: cannot mkdir $vvv - $!\n"; + } + $vvv .= "/n"; + unlink $vvv; + symlink 10,$vvv or die "$0: cannot create $vvv - $!\n"; + } else { + die "usage: $0 -L file\n" if @ARGV or $opt_r; + mv10($opt_L); + } + exit; +} + +if ($opt_e) { + die $usage unless $a; + $editor = $ENV{EDITOR} or die "$0: environment variable EDITOR not set\n"; + system(qw'vv -s',$file) if -f $file; # save current version + system($editor,@ARGV,$file); exit $? if $?; + unlink $ofile; # delete new file~ created by editor + system(qw'vv -0',$file); # post rotating + system(qw'vv -b',$file); # save backup + exit; +} + +if ($opt_v) { + die "$0: no such file $bfile\n" unless $bfile; + if (-f "$vfile~0$opt_v~") { $vfile .= "~0$opt_v~" } + else { $vfile .= "~$opt_v~" } + if (-f $vfile) { + if (-t STDOUT) { + if (($ENV{EDITOR}||$0) =~ /jed/) { + $ENV{JEDINIT} = "SAVE_STATE=0"; + exec 'jed',$vfile,qw'-tmp -f set_readonly(1)'; + } elsif ($ENV{PAGER}) { + exec $ENV{PAGER},$vfile; + } else { + exec 'view',$vfile; + } + } else { + exec 'cat',$vfile; + } + } else { + die "$0: no $vfile\n"; + } + exit; +} + +if ($opt_p) { + opendir $vdir,$vdir or die "$0: no $vdir\n"; + while ($vfile = readdir($vdir)) { + next unless -f "$vdir/$vfile"; + $bfile = $vfile; + $bfile =~ s/~\d\d?~$//; + if (not -f $bfile or -l $bfile) { + unlink "$vdir/$vfile"; + $purge{$bfile}++; + } + } + if (@purge = keys %purge) { + foreach $p (@purge) { + printf "%2d %s~ purged\n",$purge{$p},$p; + } + } + exit; +} + +if ($opt_m) { + migrate('.'); + exit; +} + +if (length($opt_r)) { + die "$0: no such file $bfile\n" unless $bfile; + if ($opt_r eq '.') { + die "$0: no $vfile\n" unless -f $vfile; + copy($vfile,$file,$opt_r); + } else { + if ($opt_r =~ /^\d$/ and -f "$vfile~0$opt_r~") { + $vfile .= "~0$opt_r~" + } else { + $vfile .= "~$opt_r~" + } + die "$0: no version $opt_r for $file\n" unless -f $vfile; + if ($nfile = shift @ARGV) { + copy($vfile,$nfile); + } else { + copy($file,$vfile0) if mtime($file) > mtime($vfile0); + copy($vfile,$file); + } + } + exit; +} + +if (length($opt_d)) { + die "$0: no such file $bfile\n" unless $bfile; + @diff = qw'diff -u' unless @diff; + if ($opt_d =~ /^(\d\d?):(\d\d?)$/) { + if (-f "$vdir/$bfile~0$1~" and -f "$vdir/$bfile~0$2~") { + exec @diff,"$vdir/$bfile~0$2~","$vdir/$bfile~0$1~" + } else { + exec @diff,"$vdir/$bfile~$2~","$vdir/$bfile~$1~" + } + } else { + if (-f "$vdir/$bfile~0$opt_d~") { + exec @diff,"$vdir/$bfile~0$opt_d~",$file; + } else { + exec @diff,"$vdir/$bfile~$opt_d~",$file; + } + } + exit $!; +} + +if ($opt_s) { + die $usage unless $file; + if ($exclude) { + $exclude =~ s/^\s+//; + $exclude =~ s/\s+$//; + $exclude =~ s/\s+/|/g; + if ($bfile =~ /$exclude/) { + warn "\r\n$0: ignoring $bfile\n"; + exit; + } + } + unless (-d $vdir) { + mkdir $vdir or die "$0: cannot mkdir $vdir - $!\n"; + } + chmod 0777,$vdir if (stat $dir)[2] & 00002; + + # migrate old file~ to versions + if (-f $ofile and not -l $ofile and -r $ofile) { + $vfn = rotate($vfile); + rename($ofile,$vfn); + } + + # rotate and save if file has changed + if (-f $vfile1) { + if (md5f($vfile1) ne md5f($file)) { + $vfn = rotate($vfile); + copy($file,$vfn); + } + exit; + } + # rotate and save if file has changed + if (-f $vfile01) { + if (md5f($vfile01) ne md5f($file)) { + $vfn = rotate($vfile); + copy($file,$vfn); + } + exit; + } + # save new file + if ((readlink("$vdir/.versions/n")||10) == 100) { + copy($file,$vfile01); + } else { + copy($file,$vfile1); + } + exit; +} + +# backup version +if ($opt_b) { + die $usage unless $file; + unless (-d $vdir) { + mkdir $vdir or die "\r\n$0: cannot mkdir $vdir - $!\n"; + } + copy($file,$vfile); + if ($ENV{VIMRUNTIME}) { + print "\n"; + } else { + warn "$file --> $vfile\n" unless $opt_q; + } + exit; +} + +# special post rotating from -e +if ($opt_0) { + my @sb = stat $file or die "$0: $file - $!\n"; + if (-f $vfile1) { + while (my @sv = stat $vfile1) { + # no version change? + if ($sb[7] == $sv[7] and $sb[9] == $sv[9]) { + # rotate back + rb10($vfile); + } else { + last; + } + } + } + if (-f $vfile01) { + while (my @sv = stat $vfile01) { + # no version change? + if ($sb[7] == $sv[7] and $sb[9] == $sv[9]) { + # rotate back + rb10($vfile); + } else { + last; + } + } + } + exit; +} + +# delete last version, roll back +if ($opt_D) { + die "usage: $0 -D file\n" unless $vfile1 or $vfile01; + stat $file or die "$0: $file - $!\n"; + # 0 version? + if (-f $vfile0) { + unlink $vfile0; + } else { + # rotate back + rb10($vfile) if -f $vfile1; + rb100($vfile) if -f $vfile01; + } + exec $0,'-l',$file; + exit; +} + +# default! +if ($opt_l) { + `stty -a` =~ /columns (\d+)/; + $tw = ($1 || 80)-36; + if (opendir $vdir,$vdir) { + while ($vfile = readdir($vdir)) { + if (-f "$vdir/$vfile") { + if ($bfile) { + if ($vfile =~ /^\Q$bfile\E~(\d\d?)~$/) { + push @{$v{$file}},$1; + } + } else { + if ($vfile =~ /^(.+)~(\d\d?)~$/) { + push @{$v{$1}},$2; + } else { + push @{$v{$vfile}},0; + } + } + } + } + closedir $vdir; + $ct = ''; + foreach $file (sort keys %v) { + if (not -f $file or -l $file) { + warn "$0: orphaned $file~\n"; + next; + } + @v = sort @{$v{$file}}; + if ($bfile) { + @stat = stat $file or die "$0: $file - $!\n"; + print "version bytes date time"; + if (${'opt_+'}) { + print " content"; + $ct = content($file); + $ct =~ s/(.{$tw}).+/$1*/; + } + print "\n"; + if (length($v[0]) == 1) { $lf = "%s %10s %s %s\n" } + else { $lf = "%2s %10s %s %s\n" } + printf $lf,'.',size($stat[7]),isodate($stat[9]),$ct; + foreach $v (@v) { + $vfile = "$vdir/$bfile~$v~"; + @stat = stat $vfile or next; + if (${'opt_+'}) { + $ct = content($vfile); + $ct =~ s/(.{$tw}).+/$1*/; + } + printf $lf,int($v),size($stat[7]),isodate($stat[9]),$ct; + } + } else { + my $n = scalar(@v); + $n-- if $v[0] == 0; # do not count zero version + printf "%d %s\n",$n,$file; + } + } + } + exit; +} + + +sub size { + my $s = shift; + if ($s > 9999999999) { $s = int($s/2**30).'G' } + elsif ($s > 9999999) { $s = int($s/2**20).'M' } + elsif ($s > 9999) { $s = int($s/2**10).'k' } + return $s; +} + + +sub content { + my $file = shift; + my $ct; + local $_; + + chomp ($ct = `file $file`); + $ct =~ s/.*?: //; + $ct =~ s/,.*//; + + if ($ct =~ /text/ and open $file,$file) { + read $file,$_,1024; + close $file; + s/[\x00-\x20]+/ /g; + s/^ //; + s/ $//; + $ct = '"'.$_.'"'; + } + + return $ct; +} + +sub isodate { + my @d = localtime shift; + return sprintf('%d-%02d-%02d %02d:%02d:%02d', + $d[5]+1900,$d[4]+1,$d[3],$d[2],$d[1],$d[0]); +} + +sub rotate { + my $vf = shift; # version base file + my $vf1 = "$vf~1~"; + my $vf01 = "$vf~01~"; + my ($vfi,$vfn); + + if (-f $vf1) { + for (my $i = 8; $i >= 0; $i--) { + $vfi = sprintf("%s~%d~",$vf,$i); + $vfn = sprintf("%s~%d~",$vf,$i+1); + if (-e $vfi) { + rename $vfi,$vfn or die "$0: $vfi --> $vfn : $!\n"; + } + } + # was there a version 0? + if (-e $vf1) { + my $bf = $vf; + $bf =~ s:/\.versions/:/:; + my @sb = stat $bf; + my @sv = stat $vf1; + # version change? (other size or mtime) + if (@sb and @sv and $sb[7] == $sv[7] and $sb[9] == $sv[9]) { + # same version + unlink $vf1; + } else { + # other version + rotate($vf); + } + } + return "$vf~1~"; + } elsif (-f $vf01) { + for (my $i = 98; $i >= 0; $i--) { + $vfi = sprintf("%s~%02d~",$vf,$i); + $vfn = sprintf("%s~%02d~",$vf,$i+1); + if (-e $vfi) { + rename $vfi,$vfn or die "$0: $vfi --> $vfn : $!\n"; + } + } + # was there a version 0? + if (-e $vf01) { + my $bf = $vf; + $bf =~ s:/\.versions/:/:; + my @sb = stat $bf; + my @sv = stat $vf01; + # version change? (other size or mtime) + if (@sb and @sv and $sb[7] == $sv[7] and $sb[9] == $sv[9]) { + # same version + unlink $vf01; + } else { + # other version + rotate($vf); + } + } + return "$vf~01~"; + } + + return "$vf~1~"; +} + +sub copy { + my ($from,$to,$restore) = @_; + + unless ($restore) { + if (-l $file or not -f $file) { + die "$0: $file is not a regular file\n"; + } + } + + if (open $to,'>>',$to) { + close $to; + if (system(qw'rsync -aA',$from,$to) == 0) { + if ($ENV{VIMRUNTIME}) { + print "\n"; + } else { + warn "$from --> $to\n" unless $opt_q; + } + } else { + exit $?; + } + } else { + die "\r\n$0: cannot write $to - $!\n"; + } +} + +sub realfilename { + my $file = shift; + + return $file unless -e $file; + + if (-l $file) { + my $link = readlink($file); + if ($link !~ /^\// and $file =~ m:(.*/).:) { + $link = $1 . $link; + } + return realfilename($link); + } else { + return $file; + } +} + +sub migrate { + my $dir = shift; + my $vdir = "$dir/.versions"; + my $dfile; + + opendir $dir,$dir or die "$0: cannot read directory $dir - $!\n"; + while ($file = readdir($dir)) { + $dfile = "$dir/$file"; + next if -l $dfile or $file eq '.' or $file eq '..'; + if (-d $dfile and $opt_R and $file ne '.versions') { + migrate($dfile); + } elsif (-f $dfile and $file =~ /~$/) { + if (-d $vdir) { + for ($i = 8; $i > 0; $i--) { + $n = $i+1; + rename "$vdir/$file$i~","$vdir/$file$n~"; + } + } else { + mkdir $vdir or die "$0: cannot mkdir $vdir - $!\n"; + } + $nfile = sprintf("%s/%s1~",$vdir,$file); + rename $dfile,$nfile or die "$0: cannot move $dfile to $nfile - $!\n"; + warn "$dfile --> $nfile\n" unless $opt_q; + } + } + closedir $dir; +} + +sub mtime { + my @s = stat shift; + return @s ? $s[9] : 0; +} + +sub md5f { + my $file = shift; + my $md5 = 0; + local $/; + + if (open $file,$file) { + $md5 = md5_hex(<$file>); + close $file; + } + return $md5; +} + + +# if ARGV is empty use last saved file as default file argument +sub check_ARGV { + local $_; + local *V; + + if (not @ARGV) { + if (-d '.versions' and open V,'ls -at .versions|') { + while () { + chomp; + if (-f) { + close V; + s/~\d+~$//; + @ARGV = ($_); + return; + } + } + } + } + +} + + +sub mv10 { + my $file = shift; + my $vfile = dirname($file).'/.versions/'.basename($file); + + die "$0: $file has no extended versions\n" unless -f "$vfile~01~"; + for (my $i=1; $i<10; $i++) { + my $vfile1 = "$vfile~$i~"; + my $vfile2 = "$vfile~0$i~"; + if (-f $vfile2) { + warn "$vfile2 --> $vfile1\n" unless $opt_q; + rename $vfile2,$vfile1 or die "$0: $!\n"; + } + } + for (my $i=10; $i<100; $i++) { + unlink "$vfile~$i~"; + } +} + +sub mv100 { + my $file = shift; + my $vfile = dirname($file).'/.versions/'.basename($file); + + die "$0: $file has already extended versions\n" if -f "$vfile~01~"; + die "$0: $file has no versions\n" unless -f "$vfile~1~"; + for (my $i=1; $i<10; $i++) { + my $vfile1 = "$vfile~$i~"; + my $vfile2 = "$vfile~0$i~"; + if (-f $vfile1) { + warn "$vfile1 --> $vfile2\n" unless $opt_q; + rename $vfile1,$vfile2 or die "$0: $!\n"; + } + } +} + + +# rotate back +sub rb10 { + my $vfile = shift; + + for (my $i = 1; $i <= 8; $i++) { + my $vfi = sprintf("%s~%d~",$vfile,$i); + my $vfn = sprintf("%s~%d~",$vfile,$i+1); + if (-f $vfn) { + rename $vfn,$vfi; + } else { + unlink $vfi if $i == 1; + last; + } + } +} + + +# rotate back +sub rb100 { + my $vfile = shift; + + for (my $i = 1; $i <= 98; $i++) { + my $vfi = sprintf("%s~%02d~",$vfile,$i); + my $vfn = sprintf("%s~%02d~",$vfile,$i+1); + if (-f $vfn) { + rename $vfn,$vfi; + } else { + unlink $vfi if $i == 1; + last; + } + } +} + + + +sub pathsearch { + my $prg = shift; + + foreach my $dir (split(':',$ENV{PATH})) { + return "$dir/$prg" if -x "$dir/$prg"; + } +} + + +# zz is the generic clip board program +# # to use zz with vim, write to your .vimrc: # # noremap zz> :w !zz -# noremap zz< :r !zz +# noremap zz< :r !zz -- +sub zz { + my $bs = 2**16; + my $wm = '>'; + my ($file,$tee,$x); -ZZ=${ZZ:-$HOME/.zz} + if ("@ARGV" =~ /^(-h|--help)$/) { + print <<'EOD'; +zz is the generic clip board program. It can hold any data, ASCII or binary. +The clip board itself is $ZZ (default: $HOME/.zz). +See also the clip board editor "ezz". +Limitation: zz does not work across accounts or hosts! Use xx instead. -if [ "$*" = -h -o "$*" = --help ]; then - exec cat</dev/null 2>&1" if -s $ZZ; + + if (@ARGV and $ARGV[0] eq '+') { + shift @ARGV; + $wm = '>>'; + } + + if ("@ARGV" eq '-') { + @ARGV = (); + $tee = 1 unless -t STDIN; + } + + $tee = 1 unless @ARGV or -t STDIN or -t STDOUT; + $bs = 2**12 if $tee; + + open $ZZ,$wm,$ZZ or die "$0: cannot write $ZZ - $!\n"; + + if (@ARGV) { + while ($file = shift @ARGV) { + if (-f $file) { + if (open $file,$file) { + while (read($file,$x,$bs)) { + my $s = syswrite $ZZ,$x; + defined($s) or die "$0: cannot write to $ZZ - $!\n"; + } + close $file; + } else { + warn "$0: cannot read $file - $!\n"; + } + } elsif (-e $file) { + warn "$0: $file is not a regular file\n"; + } else { + warn "$0: $file does not exist\n"; + } + } + close $ZZ; + $ZZ1 = $ZZ.'~1~'; + $ZZ1 =~ s:(.*)/(.*):$1/.versions/$2:; + if (-e $ZZ and not -s $ZZ and -s $ZZ1 ) { + system qw'rsync -aA',$ZZ1,$ZZ; + } + } else { + while (read(STDIN,$x,$bs)) { + syswrite $ZZ,$x; + syswrite STDOUT,$x if $tee; + } + } -Limitation: zz does not work across different accounts or hosts! Use xx instead. + exit; +} + + +sub ezz { + my $bs = 2**16; + my $wm = '>'; + my $editor = $ENV{EDITOR} || 'vi'; + my ($out,$file,$x); + + $ENV{JEDINIT} = "SAVE_STATE=0"; + + if ("@ARGV" =~ /^(-h|--help)$/) { + print <<'EOD'; +ezz is the edit helper for the zz clip board program. +The clip board itself is $ZZ (default: $HOME/.zz). + +Options and modes are: + + "ezz" edit $ZZ with $EDITOR + "... | ezz" write STDIN from pipe to $ZZ and call $EDITOR + "... | ezz +" add STDIN from pipe to $ZZ and call $EDITOR + "ezz 'perl commands'" execute perl commands on $ZZ + "ezz - 'perl commands'" execute perl commands on $ZZ and show result + "ezz filter [args]" run filter [with args] on $ZZ + "ezz - filter [args]" run filter [with args] on $ZZ and show result + +Examples: + + ls -l | ezz + ezz 's/ /_/g' + ezz head -3 + ezz - head -3 EOD -fi - -if [ "$1" = + ]; then - shift - exec cat -- "$@" >>$ZZ -fi - -if [ -t 0 ]; then - if [ -z "$1" ]; then - exec cat -- $ZZ - elif [ "$1" = .. ]; then - exec cat -- $ZZ~ - else - test -f $ZZ && mv $ZZ $ZZ~ - exec cat -- "$@" >$ZZ - fi -else - test -f $ZZ && mv $ZZ $ZZ~ - exec cat >$ZZ -fi + exit; + } + + system "vv -s '$ZZ' >/dev/null 2>&1" if -s $ZZ; + + unless (-t STDIN) { + if ("@ARGV" eq '+') { + @ARGV = (); + $wm = '>>'; + } + open $ZZ,$wm,$ZZ or die "$0: cannot write $ZZ - $!\n"; + syswrite $ZZ,$x while read(STDIN,$x,$bs); + close $ZZ; + } + + if (@ARGV) { + $out = shift @ARGV if $ARGV[0] eq '-'; + $cmd = shift @ARGV or exec 'cat',$ZZ; + rename $ZZ,"$ZZ~" or die "$0: cannot move $ZZ to $ZZ~ - $!\n"; + $cmd = quotemeta $cmd; + @ARGV = map { quotemeta } @ARGV; + if (pathsearch($cmd)) { + system "$cmd @ARGV <'$ZZ~'>'$ZZ'"; + } else { + system "perl -pe $cmd @ARGV <'$ZZ~'>'$ZZ'"; + } + if ($? == 0) { unlink "$ZZ~" } + else { rename "$ZZ~",$ZZ } + exec 'cat',$ZZ if $out; + } else { + exec $editor,$ZZ; + } + exit; +} + + +sub install { + my ($dir); + local $| = 1; + + print "Installation directory: "; + $dir = ||''; + chomp $dir; + $dir =~ s:/+$::; + $dir ||= '.'; + if ($dir eq '.') { + unlink qw'zz ezz vv'; + link $prg,'zz' or die "$0: cannot create zz - $!\n"; + link $prg,'ezz' or die "$0: cannot create ezz - $!\n"; + rename $prg,'vv' or die "$0: cannot create vv - $!\n"; + } else { + die "$0: $dir does not exist\n" unless -e $dir; + die "$0: $dir is not a directory\n" unless -d $dir; + die "$0: $dir is not writable\n" unless -w $dir; + chdir $dir or die "$0: cannot cd $dir - $!\n"; + unlink qw'zz ezz vv'; + system qw'rsync -a',$prg,'vv'; + exit $? if $?; + link 'vv','zz' or die "$0: cannot create $dir/zz - $!\n"; + link 'vv','ezz' or die "$0: cannot create $dir/ezz - $!\n"; + } + print "Installation completed. See:\n"; + print "\t$dir/vv -h\n"; + print "\t$dir/zz -h\n"; + print "\t$dir/ezz -h\n"; + exit; +} diff --git a/cgi-bin/fac b/cgi-bin/fac index 262975d..a6a0012 100755 --- a/cgi-bin/fac +++ b/cgi-bin/fac @@ -205,13 +205,13 @@ if (defined $PARAM{"createUser"}) { if ($PARAM{"editUser"} =~ /^#.*/) { &editRestrictionsForm; } else { + $user = normalize_user($PARAM{"editUser"}); if (defined $PARAM{"delete file"}) { - $user = normalize_user($PARAM{"editUser"}); unlink "$user/\@ALLOWED_RECIPIENTS"; print "upload restrictions for $user have been deleted\n"; &end_html; } else { - editUser($PARAM{"editUser"}); + editUser($user); } } } elsif ($PARAM{"contentBox"} and $PARAM{"ar"}) { @@ -564,8 +564,9 @@ sub saveFile { &end_html; } } else { - system qw'cp -a',$ar,"$ar~"; + system 'mv',$ar,"$ar~"; } + $rf =~ s/^\s+$//; open $ar,'>',$ar or http_die("cannot write $ar - $!"); print {$ar} $rf; close $ar or http_die("cannot write $ar - $!");; diff --git a/cgi-bin/fup b/cgi-bin/fup index 2a799ac..9ba0ef2 100755 --- a/cgi-bin/fup +++ b/cgi-bin/fup @@ -1613,7 +1613,10 @@ sub parse_request { &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'); @@ -1942,7 +1945,10 @@ sub showstatus { 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; diff --git a/cgi-bin/fur b/cgi-bin/fur index bca85af..0ab7be9 100755 --- a/cgi-bin/fur +++ b/cgi-bin/fur @@ -48,7 +48,8 @@ unless (@local_domains or @local_rdomains) { } unless (@local_hosts and ipin($ra,@local_hosts) or - @local_rhosts and ipin($ra,@local_rhosts)) { + @local_rdomains and @local_rhosts and + (not @registration_hosts or ipin($ra,@registration_hosts))) { html_error($error, "Registrations from your host ($ra) are not allowed.", "Contact $ENV{SERVER_ADMIN} for details." diff --git a/doc/Changes b/doc/Changes index cdc834f..2457b74 100644 --- a/doc/Changes +++ b/doc/Changes @@ -1,3 +1,14 @@ +2016-09-19 dop: do not show return value of <> in dynamic html + file if this code ends with a ";" +2016-09-07 new fex.ph config variable $purge +2016-09-01 dop: removed (forgotten) CGI::Carp +2016-08-29 fexsend,fexget: update function aborts if new version is not newer +2016-08-03 added timeout to error output, fixes hanging fup +2016-08-03 fexsend: fixed bug dangling symlinks raise an error in archive mode +2016-07-21 fexsrv: map http client headers HTTP-HOST HTTP-VERSION PROXY* to + HTTP_X_HOST HTTP_X_VERSION HTTP_X_PROXY* +2016-07-11 added missing fex.png fexit.png to distribution +2016-05-31 fur: fixed bug no external user registration possible 2016-03-11 fuc: added MIME headers to notification e-mail 2016-03-08 fexsend: added support for recipient:options 2016-03-07 fexsend,fexget: added update function to option -V diff --git a/doc/SSL b/doc/SSL index 6eb73bf..90e01ef 100644 --- a/doc/SSL +++ b/doc/SSL @@ -2,14 +2,27 @@ # execute this as root! -# Redhat : stunnel-4 does not work! you need to install stunnel-5 -# Debian : stunnel-5.06 does not work! you need to install stunnel-5.18 +# Redhat+CentOS : stunnel does not work! you need to install stunnel-4 +# +# Debian+Ubuntu : stunnel-5 does not work! you need to install stunnel-4 +# +# apt-get install gcc make libssl-dev +# yum install gcc make openssl-devel +# cd /tmp +# wget ftp://ftp.nluug.nl/pub/networking/stunnel/archive/4.x/stunnel-4.57.tar.gz +# tar xvzf stunnel-4.57.tar.gz +# cd stunnel-4.57 +# ./configure --prefix /opt/stunnel-4.57 +# make +# make install +# ln -s /opt/stunnel-4.57/bin/stunnel /usr/local/bin/stunnel4 mkdir /home/fex/etc cd /home/fex/etc/ -openssl req -new -x509 -days 9999 -nodes -out stunnel.pem -keyout stunnel.pem +# create self-signed certificate # see http://www.infodrom.org/Debian/tips/stunnel.html +openssl req -new -x509 -days 9999 -nodes -out stunnel.pem -keyout stunnel.pem dd if=/dev/urandom count=2 | openssl dhparam -rand - 1024 >> stunnel.pem openssl x509 -text -in stunnel.pem chmod 600 stunnel.pem @@ -19,23 +32,26 @@ debug = warning output = /home/fex/spool/stunnel.log cert = /home/fex/etc/stunnel.pem sslVersion = all +fips = no TIMEOUTclose = 1 -exec = perl -execargs = perl -T /home/fex/bin/fexsrv stunnel +exec = /home/fex/bin/fexsrv +execargs = fexsrv stunnel EOD -case $(lsb_release -a 2>/dev/null) in - *CentOS*) echo 'fips = no' >>stunnel.conf;; -esac +## https://www.stunnel.org/pipermail/stunnel-users/2013-October/004414.html +#case $(lsb_release -a 2>/dev/null) in +# *CentOS*) echo 'fips = no' >>stunnel.conf;; +#esac chown -R fex . stunnel=$(which stunnel4) if [ -z "$stunnel" ]; then - echo "no stunnel found" >&2 -else + echo "no stunnel4 found" >&2 + exit +fi - cat </etc/xinetd.d/fexs +cat </etc/xinetd.d/fexs # default: on # description: fex web server with SSL # note: only possible on port 443! @@ -56,11 +72,9 @@ service fexs } EOD - /etc/init.d/xinetd restart - echo 'To enforce https, add to fex.ph:' - echo '$force_https = 1;' - -fi +/etc/init.d/xinetd restart +echo 'To enforce https, add to fex.ph:' +echo '$force_https = 1;' # Hint: on some systems stunnel works not well with xinetd # you can also run stunnel without xinetd, in server daemon mode diff --git a/doc/concept b/doc/concept index fde9902..6203c43 100644 --- a/doc/concept +++ b/doc/concept @@ -51,7 +51,7 @@ program "fac" (F*EX admin control) or http://YOURFEXSERVER/fac Alternativly the users can register theirselves with http://YOURFEXSERVER/fur (F*EX user registration), if the admin allows them to do so. This is done by -setting the variables @local_domains and @local_hosts in FEXHOME/lib/fex.ph +setting the variables @local_domains and @local_hosts in $FEXHOME/lib/fex.ph Example: @local_hosts = qw(127.0.0.1 10.10.100.0-10.10.255.255); @@ -146,7 +146,7 @@ and more than once (until expiration date). If you want "delay autodelete" to be the default behaviour for all users and each transfer then set $autodelete = 'DELAY'; # or 'NO' for no autodelete -in FEXHOME/lib/fex.ph +in $FEXHOME/lib/fex.ph In addition, you can add to the "Recipient(s)" field of the fup CGI: ":autodelete=delay" or ":autodelete=no" or ":keep=x" (where x is the number @@ -193,18 +193,18 @@ The administrator can also forbid a user to fex to any recipient address, but the allowed ones with: fac -r USER -By standard installation the base directory FEXHOME is the same as the -login HOME of user fex, but you can move it if you want. FEXHOME is +By standard installation the base directory $FEXHOME is the same as the +login HOME of user fex, but you can move it if you want. $FEXHOME is determined by the full path of fexsrv as configured in -/etc/xinetd.d/fex . Change this when you move FEXHOME! +/etc/xinetd.d/fex . Change this when you move $FEXHOME! You can also add (name based) virtual hosts with fac. -Do not give write permission to any other user to any file in FEXHOME or +Do not give write permission to any other user to any file in $FEXHOME or below! -FEXHOME contains: +$FEXHOME contains: spool/ spool directory and user data htdocs/ directory for generic download files @@ -273,7 +273,7 @@ A registered full F*EX user is identified by the file $spooldir/$from/@ Only if this file contains his auth-ID this user is able to send files to others. Otherwise he is just an unpriviledged recipient. -You can customize the upload CGI fup by editing FEXHOME/lib/fup.pl +You can customize the upload CGI fup by editing $FEXHOME/lib/fup.pl Additional directories in spool: @@ -362,13 +362,13 @@ For streaming receiving you can use "fexget -s-" or "wget -O-". fexsrv also can do generic document output (via dop) like a normal web -server. For this, your files must be under FEXHOME/htdocs and they must -not have the same name as the CGIs under FEXHOME/cgi-bin, because the CGIs +server. For this, your files must be under $FEXHOME/htdocs and they must +not have the same name as the CGIs under $FEXHOME/cgi-bin, because the CGIs have priority. For security reasons, documents to be delivered by dop: - the file must be readable by group or world -- the file must be in FEXHOME/htdocs or a directory specified by @doc_dirs +- the file must be in $FEXHOME/htdocs or a directory specified by @doc_dirs - the filename must not start with a "." - the filename must not contain a "@" - the filename must not end with "~" @@ -387,15 +387,23 @@ fexsrv. *.html files may contain $VARIABLES$ which will be substituted with the value of the corresponding environment variable. See example -$SERVER_ADMIN$ in FEXHOME/htdocs/index.html +$SERVER_ADMIN$ in $FEXHOME/htdocs/index.html *.html files may contain <> (even multiline) which will be -evaluated and its output will be placed in. Same goes for <<>> -but without output catching. -See example FEXHOME/htdocs/dynamic.html -This perl-code must not contain '>>' strings itself! +evaluated. The output from print and printf statements will be placed +in. If the perl-code does not end with a ";" then its return value is also +added to the output. + +Same goes for <<>> but without output catching. + +This perl-code must not contain ">>" strings itself! + +See example $FEXHOME/htdocs/dynamic.html + +To chainload external perl-code do not use "require" but "do" statement. Pay attention: do not place security relevant data inside << >> because it -will be delivered to the client if the URL ends with '!'! See example: +will be delivered to the client if the URL ends with a "!" character, see +example: http://fex.rus.uni-stuttgart.de/index.html http://fex.rus.uni-stuttgart.de/index.html! diff --git a/doc/new b/doc/new index badf322..bcd8be8 100644 --- a/doc/new +++ b/doc/new @@ -2,11 +2,6 @@ New release on http://fex.belwue.de/fex.html Important changes: -- recipient address can have attached :options (keep,autodelete,locale) - -- added config variable @extra_header with default HTTP security headers, - see: https://securityheaders.io/?q=http%3A%2F%2Ffex.belwue.de - -- dynamic HTML runs in own Perl namespace (DOP) +- new fex.ph config variable $purge - fixed various bugs diff --git a/doc/version b/doc/version index 7b735e5..d9e31cb 100644 --- a/doc/version +++ b/doc/version @@ -1 +1 @@ -fex-20160328 +fex-20160919 diff --git a/htdocs/FAQ/admin.faq b/htdocs/FAQ/admin.faq index 61691fc..1f7c404 100644 --- a/htdocs/FAQ/admin.faq +++ b/htdocs/FAQ/admin.faq @@ -2,7 +2,7 @@ Q: I cannot install a web server like fexsrv, because I have no root permissions A: F*EX is hard bound to fexsrv for several reasons (performance, file size limit, session concept, etc) and cannot be run as CGI under apache. But you might have a look at * https://github.com/FileZ/FileZ - * http://freshmeat.net/projects/eventh/ + * https://github.com/jlmeeker/evh2 * http://www.schaarwaechter.de/sp/projekte/dateiaustausch.html (German only!) which implement a file exchange as pure CGIs, but with a 2 GB file size limit, which F*EX does not have. diff --git a/htdocs/FAQ/admin.html b/htdocs/FAQ/admin.html index a096645..9585c69 100644 --- a/htdocs/FAQ/admin.html +++ b/htdocs/FAQ/admin.html @@ -2,11 +2,14 @@ F*EX FAQ +## << do "./xx.pl" or print $! >> +## << $_ = `pwd` >> + ##
 ## << while (($v,$vv) = each %ENV) { print "$v = $vv\n" } >>
 ## 
-<< require "./faq.pl" or print $! >> +<< do "./faq.pl" or print $! >> diff --git a/htdocs/FAQ/all.html b/htdocs/FAQ/all.html index a096645..9585c69 100644 --- a/htdocs/FAQ/all.html +++ b/htdocs/FAQ/all.html @@ -2,11 +2,14 @@ F*EX FAQ +## << do "./xx.pl" or print $! >> +## << $_ = `pwd` >> + ##
 ## << while (($v,$vv) = each %ENV) { print "$v = $vv\n" } >>
 ## 
-<< require "./faq.pl" or print $! >> +<< do "./faq.pl" or print $! >> diff --git a/htdocs/FAQ/faq.pl b/htdocs/FAQ/faq.pl index 3a2fcff..0e484b9 100644 --- a/htdocs/FAQ/faq.pl +++ b/htdocs/FAQ/faq.pl @@ -140,3 +140,5 @@ sub anchor { s/_+$//; return $_; } + +' '; diff --git a/htdocs/FAQ/local.html b/htdocs/FAQ/local.html index a096645..9585c69 100644 --- a/htdocs/FAQ/local.html +++ b/htdocs/FAQ/local.html @@ -2,11 +2,14 @@ F*EX FAQ +## << do "./xx.pl" or print $! >> +## << $_ = `pwd` >> + ##
 ## << while (($v,$vv) = each %ENV) { print "$v = $vv\n" } >>
 ## 
-<< require "./faq.pl" or print $! >> +<< do "./faq.pl" or print $! >> diff --git a/htdocs/FAQ/meta.faq b/htdocs/FAQ/meta.faq index f910621..09fe46f 100644 --- a/htdocs/FAQ/meta.faq +++ b/htdocs/FAQ/meta.faq @@ -70,7 +70,7 @@ A: For example: * European Commission Institute for Energy and Transport http://fex.jrc.nl * High Performance Computing Center Stuttgart http://fex.hlrs.de * Swiss National Supercomputing Centre http://fex.cscs.ch - * Centre National de la Recherche Scientifique (French National Center for Scientific Research) http://bigfiles.cnrs-gif.fr + * Centre National de la Recherche Scientifique http://bigfiles.cnrs-gif.fr * Institut Pasteur http://dl.pasteur.fr * Justus Liebig University http://fex.hrz.uni-giessen.de * Fiat Chrysler https://fex.fiatitem.com/ diff --git a/htdocs/FAQ/meta.html b/htdocs/FAQ/meta.html index a096645..9585c69 100644 --- a/htdocs/FAQ/meta.html +++ b/htdocs/FAQ/meta.html @@ -2,11 +2,14 @@ F*EX FAQ +## << do "./xx.pl" or print $! >> +## << $_ = `pwd` >> + ##
 ## << while (($v,$vv) = each %ENV) { print "$v = $vv\n" } >>
 ## 
-<< require "./faq.pl" or print $! >> +<< do "./faq.pl" or print $! >> diff --git a/htdocs/FAQ/misc.html b/htdocs/FAQ/misc.html index a096645..9585c69 100644 --- a/htdocs/FAQ/misc.html +++ b/htdocs/FAQ/misc.html @@ -2,11 +2,14 @@ F*EX FAQ +## << do "./xx.pl" or print $! >> +## << $_ = `pwd` >> + ##
 ## << while (($v,$vv) = each %ENV) { print "$v = $vv\n" } >>
 ## 
-<< require "./faq.pl" or print $! >> +<< do "./faq.pl" or print $! >> diff --git a/htdocs/FAQ/user.html b/htdocs/FAQ/user.html index a096645..9585c69 100644 --- a/htdocs/FAQ/user.html +++ b/htdocs/FAQ/user.html @@ -2,11 +2,14 @@ F*EX FAQ +## << do "./xx.pl" or print $! >> +## << $_ = `pwd` >> + ##
 ## << while (($v,$vv) = each %ENV) { print "$v = $vv\n" } >>
 ## 
-<< require "./faq.pl" or print $! >> +<< do "./faq.pl" or print $! >> diff --git a/htdocs/FAQ/xx.html b/htdocs/FAQ/xx.html new file mode 100644 index 0000000..3c8eb43 --- /dev/null +++ b/htdocs/FAQ/xx.html @@ -0,0 +1,15 @@ + +F*EX FAQ + + +## << do "./xx.pl" or print $! >> +## << $_ = `pwd` >> + +##
+## << while (($v,$vv) = each %ENV) { print "$v = $vv\n" } >>
+## 
+ +<< require "./xx.pl"; >> + + + diff --git a/htdocs/FAQ/xx.pl b/htdocs/FAQ/xx.pl new file mode 100755 index 0000000..68e45b4 --- /dev/null +++ b/htdocs/FAQ/xx.pl @@ -0,0 +1,6 @@ +package FAQ; + +print "abc\n"; +printf "1 2 3\n"; +print "___\n"; +1; diff --git a/htdocs/FAQ/zz.pl b/htdocs/FAQ/zz.pl new file mode 100644 index 0000000..0f57817 --- /dev/null +++ b/htdocs/FAQ/zz.pl @@ -0,0 +1 @@ +0; diff --git a/htdocs/download/fexget b/htdocs/download/fexget index b0616a1..3ac605f 100755 --- a/htdocs/download/fexget +++ b/htdocs/download/fexget @@ -13,6 +13,7 @@ use strict qw'vars subs'; use Config; use POSIX; use Encode; +use Cwd 'abs_path'; use Getopt::Std; use File::Basename; use Socket; @@ -30,7 +31,7 @@ our $SH; our ($fexhome,$idf,$tmpdir,$windoof,$useragent); our ($xv,%autoview); our $bs = 2**16; # blocksize for tcp-reading and writing file -our $version = 20160328; +our $version = 20160919; our $CTYPE = 'ISO-8859-1'; our $fexsend = $ENV{FEXSEND} || 'fexsend'; our $DEBUG = $ENV{DEBUG}; @@ -89,6 +90,7 @@ usage: $0 [-v] [-m limit] [-s filename] [-o] [-k] [-X] [-P proxy:port] F*EX-URL( or: $0 [-v] -a or: $0 -l [-i tag] or: $0 -H + or: $0 -V options: -v verbose mode -m limit kB/s -s save to filename (-s- means: write to STDOUT/pipe) @@ -102,6 +104,7 @@ options: -v verbose mode -i tag alternate server/account, see: $fexsend -h -P use Proxy for connection to the F*EX server -H show hints and examples + -V show version and ask for upgrade argument: F*EX-URL may be file number (see: $0 -l) EOD @@ -167,10 +170,15 @@ if ($opt_V) { $_ = ||''; if (/^y/i) { my $new = `wget -nv -O- http://fex.belwue.de/download/fexget`; - if ($new !~ /upgrade fexget/) { + my $newversion = $1 if $new =~ /version = (\d+)/; + if ($new !~ /upgrade fexget/ or not $newversion) { die "$0: bad update\n"; } - system qw'cp -a',$_0,$_0.'_old'; + if ($newversion <= $version) { + die "$0: no newer version\n"; + } + $_0 = abs_path($_0); + system qw'rsync -a',$_0,$_0.'_old'; exit $? if $?; open $_0,'>',$_0 or die "$0: cannot write $_0. - $!\n"; print {$_0} $new; @@ -178,6 +186,7 @@ if ($opt_V) { exec $_0,qw'-V .'; } } + exit; exit if "@ARGV" eq '.'; } @@ -328,7 +337,7 @@ URL: foreach my $url (@ARGV) { ($file) = grep { $_ = $1 if /^X-File:\s+(.+)/ } @r; $file = $url unless $file; $file =~ s:.*/::; - printf "%s deleted\n",urldecode($file); + printf "%s deleted\n",locale(decode_utf8(urldecode($file))); } else { s:HTTP/[\d\. ]+::; die "$0: server response: $_"; diff --git a/htdocs/download/fexsend b/htdocs/download/fexsend index f47bed4..fae77e9 100755 --- a/htdocs/download/fexsend +++ b/htdocs/download/fexsend @@ -17,10 +17,10 @@ use IO::Handle; use IO::Socket::INET; use Getopt::Std; use File::Basename; -use Cwd qw'abs_path'; +use Cwd 'abs_path'; use Fcntl qw':flock :mode'; -use Digest::MD5 qw'md5_hex'; # encrypted ID / SID -use Time::HiRes qw'time'; +use Digest::MD5 'md5_hex'; # encrypted ID / SID +use Time::HiRes 'time'; # use Smart::Comments; use constant k => 2**10; use constant M => 2**20; @@ -37,7 +37,7 @@ our ($tpid,$frecipient); our ($FEXID,$FEXXX,$HOME); our (%alias); our $chunksize = 0; -our $version = 20160328; +our $version = 20160919; our $_0 = $0; our $DEBUG = $ENV{DEBUG}; @@ -248,6 +248,14 @@ Partner program xx is an internet clipboard. See: xx -h Partner program fexget is for downloading. See: fexget -h +fexsend stores the login data (server, user and auth-ID) in the file +$HOME/.fex/id +The format of this file is ([data] is optional): + +server-URL[!proxy[:port[:chunk-size]] +e-mail-address +auth-ID + For temporary usage of a HTTP proxy use: $0 -P your_proxy:port:chunksize_in_MB file recipient Example: @@ -361,10 +369,15 @@ if ($xx) { $_ = ||''; if (/^y/i) { my $new = `wget -nv -O- http://fex.belwue.de/download/fexsend`; - if ($new !~ /upgrade fexsend/) { + my $newversion = $1 if $new =~ /version = (\d+)/; + if ($new !~ /upgrade fexsend/ or not $newversion) { die "$0: bad update\n"; } - system qw'cp -aL',$_0,$_0.'_old'; + if ($newversion <= $version) { + die "$0: no newer version\n"; + } + $_0 = abs_path($_0); + system qw'rsync -a',$_0,$_0.'_old'; exit $? if $?; open $_0,'>',$_0 or die "$0: cannot write $_0. - $!\n"; print {$_0} $new; @@ -372,6 +385,7 @@ if ($xx) { exec $_0,qw'-V .'; } } + exit; exit if "@ARGV" eq '.'; } @@ -3206,9 +3220,10 @@ sub query_sid { $sid = $id; if ($port eq 443 or $proxy) { + return if $opt_d; return if $features; # early return if we know enough - $req = "OPTIONS /FEX HTTP/1.1"; - $req = "HEAD /index.html HTTP/1.1"; + $req = "OPTIONS /FEX HTTP/1.1"; # does not work with (some) proxies + $req = "GET /SID HTTP/1.1"; # needed as FEATURES query } else { $req = "GET /SID HTTP/1.1"; } @@ -3469,7 +3484,8 @@ sub readahead { sub fileid { my $file = shift; - my @s = stat($file); + my $dirmode = shift; + my @s = $dirmode ? lstat($file) : stat($file); if (@s) { return md5_hex($file.$s[0].$s[1].$s[7].$s[9]); @@ -3528,6 +3544,9 @@ sub fmd { next if $file eq '..'; if ($file eq '.') { $fmd .= fileid($dir); + } elsif (-l "$dir/$file") { + # hack for dangling symlinks: do not raise an error + $fmd .= fileid("$dir/$file",'dirmode'); } else { $fmd .= fmd("$dir/$file"); } diff --git a/htdocs/download/sexsend b/htdocs/download/sexsend index 9a7d48b..8b9e1d1 100755 --- a/htdocs/download/sexsend +++ b/htdocs/download/sexsend @@ -19,7 +19,7 @@ use constant M => 2**20; eval 'use Net::INET6Glue::INET_is_INET6'; -our $version = 20160328; +our $version = 20160919; our $DEBUG = $ENV{DEBUG}; my %SSL = (SSL_version => 'TLSv1'); diff --git a/htdocs/fex.png b/htdocs/fex.png new file mode 100644 index 0000000000000000000000000000000000000000..a9ac834648e9db5528199a83d72581c8655dd63c GIT binary patch literal 7188 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 With fexit you also have access to the -F*EX internet clipboard to exchange files -between your Windows or UNIX accounts. + +F*EX internet clipboard +to exchange files between your Windows or UNIX accounts.

diff --git a/htdocs/fexit.png b/htdocs/fexit.png new file mode 100644 index 0000000000000000000000000000000000000000..585afaef4e136087bee6834d380b070b4bb7236b GIT binary patch literal 23729 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 G!u|)#w{eF6 literal 0 HcmV?d00001 diff --git a/htdocs/version b/htdocs/version index 7b735e5..d9e31cb 100644 --- a/htdocs/version +++ b/htdocs/version @@ -1 +1 @@ -fex-20160328 +fex-20160919 diff --git a/install b/install index 7019bee..c300815 100755 --- a/install +++ b/install @@ -163,6 +163,12 @@ if ($FEXHOME !~ /fex/) { exit unless /^y/i; } +# old bug fix +if (-d "$FEXHOME/htdocs/locale") { + chmod 0755,"$FEXHOME/htdocs/locale"; + chmod 0755,grep { -d $_ } glob("$FEXHOME/locale/*/htdocs"); +} + print "Installing:\n"; $pecl = "$FEXHOME/perl/Encode/ConfigLocal.pm"; @@ -175,7 +181,7 @@ unless (-f $pecl) { "1;\n"; close $pecl; print $pecl,"\n"; - chownr('fex:root',"$FEXHOME/perl"); + chownr('fex:0',"$FEXHOME/perl"); } @save = ( @@ -388,7 +394,7 @@ unless (-f $xinetd) { system qw'crontab -u fex fex.cron'; } - chownr('fex:root',$FEXHOME,"$FEXHOME/spool/.","$FEXHOME/htdocs/."); + chownr('fex:0',$FEXHOME,"$FEXHOME/spool/.","$FEXHOME/htdocs/."); chmodr('go-r',"$FEXHOME/lib","$FEXHOME/cgi-bin","$FEXHOME/spool/."); print "\n"; @@ -406,9 +412,6 @@ unless (-f $xinetd) { "< $FEXHOME/doc/newfeatures\n"; } -chmod 0755,"$FEXHOME/htdocs/locale"; -chmod 0755,glob("$FEXHOME/locale/*/htdocs"); - if (@local_rdomains and not @local_rhosts) { print "\nWARNING:\n"; print "In $fph you have \@local_rdomains but not \@local_rhosts!\n"; diff --git a/lib/dop b/lib/dop index dc92d70..4a6ece0 100755 --- a/lib/dop +++ b/lib/dop @@ -8,11 +8,11 @@ # use File::Basename; -use CGI::Carp qw(fatalsToBrowser); use Fcntl qw(:flock :seek :mode); use POSIX qw(strftime locale_h); use Cwd qw(getcwd abs_path); use utf8; +# use CGI::Carp qw(fatalsToBrowser); # import from fex.pp our ($bs,$tmpdir,@doc_dirs); @@ -126,10 +126,16 @@ sub http_output { foreach (@files) { if (/^\// or /\.\.\//) { # absolute path or relative path with parent directory is not allowed + errorlog("$streamfile: $_ is not allowed for streaming"); + http_error(403); + } + unless (-e $_) { + errorlog("$streamfile: $_ does not exist"); http_error(403); } if (@s = stat($_) and not($s[2] & S_IRGRP) or not -r $_) { # file must be readable by user and group + errorlog("$streamfile: $_ is not readable by user and group"); http_error(403); } } @@ -238,8 +244,10 @@ sub http_output { } else { # eval code with output substitution local $__ = ''; + local $^W = 0; tie *STDOUT => "Buffer",\$__; - $__ .= eval('package DOP;' . $pc); + my $r .= eval('package DOP;' . $pc); + $__ .= $r if $pc !~ /;\s*$/; untie *STDOUT; last if $timeout; $dynamic = $htmldoc =~ s/<<(.+?)>>/$__/s; @@ -603,7 +611,7 @@ sub out { return ''; } -# tie STDOUT to buffer variable (redefining print) +# tie STDOUT to buffer variable (redefining print and printf) package Buffer; sub TIEHANDLE { diff --git a/lib/fex.ph b/lib/fex.ph index c69943d..d1596e2 100644 --- a/lib/fex.ph +++ b/lib/fex.ph @@ -85,6 +85,10 @@ $keep_max = 99; ## NO ==> keep until expiration date (see $keep) $autodelete = 'YES'; +## purge: purge files after that number of days after their deletion +## (purge deletes file meta-information) +$purge = '3*$keep'; + ## if the file has been already downloaded then subsequentials ## downloads are only allowed from the same client (uses cookies) ## to prevent unwanted file sharing @@ -137,6 +141,13 @@ $mail_authid = 'YES'; ## optional: forbidden ip addresses for CGIs # @forbidden_hosts = qw(64.124.0.0-64.125.255.255); +# forbidden user agents (sucking "download manager", etc) +@forbidden_user_agents = qw( + FDM + Download.Master + Java/[\d\.]+ +); + ## optional: restrict upload to these IP ranges # @upload_hosts = qw(127.0.0.1 ::1 10.10.100.0-10.10.200.255 129.69.1.129); diff --git a/lib/fex.pp b/lib/fex.pp index 8bfddbf..177baba 100644 --- a/lib/fex.pp +++ b/lib/fex.pp @@ -62,6 +62,7 @@ $fop_auth = 0; $mail_authid = 'yes'; $force_https = 0; $debug = 0; +@forbidden_user_agents = ('FDM'); # https://securityheaders.io/ # https://scotthelme.co.uk/hardening-your-http-response-headers/ @@ -124,6 +125,7 @@ http_die("cannot determine the server hostname") unless $hostname; $ENV{PROTO} = 'http' unless $ENV{PROTO}; $keep = $keep_default ||= $keep || 5; +$purge ||= 3*$keep; $fra = $ENV{REMOTE_ADDR} || ''; $sid = $ENV{SID} || ''; @@ -320,6 +322,8 @@ sub html_header { my $header = 'header.html'; my $head; + binmode(STDOUT,':utf8'); # for text/html ! + # http://www.w3.org/TR/html401/struct/global.html # http://www.w3.org/International/O-charset $head = qqq(qq( @@ -370,6 +374,12 @@ sub html_error { errorlog($msg); + $SIG{ALRM} = sub { + $SIG{__DIE__} = 'DEFAULT'; + die "TIMEOUT\n"; + }; + alarm($timeout); + # cannot send standard HTTP Status-Code 400, because stupid # Internet Explorer then refuses to display HTML body! http_header("666 Bad Request - $msg"); diff --git a/locale/czech/htdocs/FAQ/FAQ.html b/locale/czech/htdocs/FAQ/FAQ.html index a096645..9585c69 100644 --- a/locale/czech/htdocs/FAQ/FAQ.html +++ b/locale/czech/htdocs/FAQ/FAQ.html @@ -2,11 +2,14 @@ F*EX FAQ +## << do "./xx.pl" or print $! >> +## << $_ = `pwd` >> + ##

 ## << while (($v,$vv) = each %ENV) { print "$v = $vv\n" } >>
 ## 
-<< require "./faq.pl" or print $! >> +<< do "./faq.pl" or print $! >> diff --git a/locale/french/htdocs/FAQ/FAQ.html b/locale/french/htdocs/FAQ/FAQ.html index a096645..9585c69 100644 --- a/locale/french/htdocs/FAQ/FAQ.html +++ b/locale/french/htdocs/FAQ/FAQ.html @@ -2,11 +2,14 @@ F*EX FAQ +## << do "./xx.pl" or print $! >> +## << $_ = `pwd` >> + ##
 ## << while (($v,$vv) = each %ENV) { print "$v = $vv\n" } >>
 ## 
-<< require "./faq.pl" or print $! >> +<< do "./faq.pl" or print $! >> diff --git a/locale/galician/htdocs/FAQ/FAQ.html b/locale/galician/htdocs/FAQ/FAQ.html index a096645..9585c69 100644 --- a/locale/galician/htdocs/FAQ/FAQ.html +++ b/locale/galician/htdocs/FAQ/FAQ.html @@ -2,11 +2,14 @@ F*EX FAQ +## << do "./xx.pl" or print $! >> +## << $_ = `pwd` >> + ##
 ## << while (($v,$vv) = each %ENV) { print "$v = $vv\n" } >>
 ## 
-<< require "./faq.pl" or print $! >> +<< do "./faq.pl" or print $! >> diff --git a/locale/german/htdocs/FAQ/FAQ.html b/locale/german/htdocs/FAQ/FAQ.html index a096645..9585c69 100644 --- a/locale/german/htdocs/FAQ/FAQ.html +++ b/locale/german/htdocs/FAQ/FAQ.html @@ -2,11 +2,14 @@ F*EX FAQ +## << do "./xx.pl" or print $! >> +## << $_ = `pwd` >> + ##
 ## << while (($v,$vv) = each %ENV) { print "$v = $vv\n" } >>
 ## 
-<< require "./faq.pl" or print $! >> +<< do "./faq.pl" or print $! >> diff --git a/locale/italian/htdocs/FAQ/FAQ.html b/locale/italian/htdocs/FAQ/FAQ.html index a096645..9585c69 100644 --- a/locale/italian/htdocs/FAQ/FAQ.html +++ b/locale/italian/htdocs/FAQ/FAQ.html @@ -2,11 +2,14 @@ F*EX FAQ +## << do "./xx.pl" or print $! >> +## << $_ = `pwd` >> + ##
 ## << while (($v,$vv) = each %ENV) { print "$v = $vv\n" } >>
 ## 
-<< require "./faq.pl" or print $! >> +<< do "./faq.pl" or print $! >> diff --git a/locale/spanish/htdocs/FAQ/FAQ.html b/locale/spanish/htdocs/FAQ/FAQ.html index a096645..9585c69 100644 --- a/locale/spanish/htdocs/FAQ/FAQ.html +++ b/locale/spanish/htdocs/FAQ/FAQ.html @@ -2,11 +2,14 @@ F*EX FAQ +## << do "./xx.pl" or print $! >> +## << $_ = `pwd` >> + ##
 ## << while (($v,$vv) = each %ENV) { print "$v = $vv\n" } >>
 ## 
-<< require "./faq.pl" or print $! >> +<< do "./faq.pl" or print $! >> diff --git a/locale/translations b/locale/translations index 25f1044..e36d3ab 100644 --- a/locale/translations +++ b/locale/translations @@ -2729,7 +2729,7 @@ Your F*EX account has been inactive for $expire days Ihr F*EX Account ist seit $expire Tagen inaktiv Du hosch dei F*EX Konto seit $expire Tag nemme bnutzt Su cuenta de F*EX ha estado inactivo $expire dias -A súa conta F*EX estivo inactiva durante $expire día +A súa conta F*EX estivo inactiva durante $expire día Il tuo account F*EX è stato inattivo per $expire giorni Váš F*EX účet byl neaktivní $expire dnů Votre compte F*EX a été inactif pendant $expire days -- 2.39.2