#!/usr/bin/perl -U # # v0.01, (c)mahatma, no warranty use CGI qw(:standard); use CGI::Carp qw(fatalsToBrowser); # optional: use MD5; my $use_md5=1; # comment to no encryption my $cgi = new CGI; my $session="$ENV{REMOTE_ADDR} $ENV{HTTP_X_FORWARDED_FOR}"; umask 0177; ### config start ## $auth - password: 0-none/.htpasswd; 1:crypt; 2:unencrypted; 3:md5 ## 2,3 - additionally md5 hashed (if $use_md5==1) my $auth=0; ## don't touch this (better): my ($user,$password,@sig) = $auth==0?($ENV{REMOTE_USER},''):login_get(); my $passwd=''; # passwords filename ($auth!=0) my %hpasswd; # predefined or buffer ## you may use own password database (undef $passwd first): #dbmopen(%hpasswd,'/etc/dbpasswd/dbpasswd',600); ## or you may enter own login: #$hpasswd{'user'}='password'; my $base="./"; my $webbase="/"; my $iam=$ENV{SCRIPT_NAME}; my $secure=0; # 0:none; 1:uid/gid; 2:1+deny chown/chmod my @suid; ## for upload: #$cgi::POST_MAX = 1048576; # max to upload #my $temp='/tmp/'; # undef = direct #my $mv='/bin/mv'; # if $temp defined only ### config end ### alt config - standard multi-user unix hosting example, exec as root #my $auth=1; #my ($user,$password,@sig) = $auth==0?($ENV{REMOTE_USER},''):login_get(); #my $passwd="/etc/shadow"; #my %hpasswd; #my $base="/home/$user/public_html/"; #my $webbase="/~$user/"; #my $iam=$ENV{SCRIPT_NAME}; #my $secure=1; #my @suid=($user,$user); ##$cgi::POST_MAX = 1048576; ##my $temp='/tmp/'; ##my $mv='/bin/mv'; ### alt config end #push (@INC, "/home/~$user"); #$ENV{PATH}="/home/~$user"; my $header="Content-type: text/html\n\n"; my $head=''; my $error; my $path=param('path')||''; my $action=param('action')||'start'; my @item; my $lastpath=''; my $it; for(my $i=0;$i<65535 && defined($it=param("item$i")) && $it ne '';$i++){ push @item,param("item$i"); }; ### install ### install() if($ARGV[0] eq install); if($action eq 'menu'){a_menu();ex(0)} elsif($action eq 'start'){a_start();ex(0)} ### /install ### if(!login()){ $error.='Invalid login
' if($user ne ''); $error.='Not logged in!'; $user=$password=''; @item=(); a_ls(); ex(-2); } seq() if($secure>0); if($action eq 'ls' || $action eq 'reload'){a_lsd()} elsif($action eq 'get'){a_get()} elsif($action eq 'upload'){a_upload()} elsif($action eq 'delete'){a_rm()} elsif($action eq 'mkdir'){a_mkdir()} elsif($action eq 'chmod'){a_chmod()} elsif($action eq 'chown'){a_chown()} elsif($action eq 'link'){a_link()} elsif($action eq 'symlink'){a_symlink()} elsif($action eq 'logoff'){a_logoff()} else{zerr("Invalid command \"$action\"")} ex(0); ### login ### sub login_get(){ return split(/:/,param('sig')); } sub login_js(){ my $t='p'; $t="md5h($t)" if($auth==3); $t="md5h($t+t+s)" if($use_md5 && ($auth!=0)); return qq( var sig=''; function setpass(p,t,s){sig=fm1.document.f.item0.value+':'+$t+':'+t} function sign(f){f.sig.value=sig} function logoff(){sig=''} ) } sub login(){ return 1 if($auth==0); my $u,$p,$l; if(defined($passwd)){ open PF,"<$passwd" or return 0; while(defined($l=)){ ($u,$p)=(split(/:/, $l))[0,1]; if($u eq $user){ $hpasswd{$u}=$p; last; } } close(PF); } return 0 if(!defined($p=$hpasswd{$user})); if($auth==1){ $password=crypt($password,$p) }elsif($use_md5){ $p=MD5->hexhash("$p@sig[0]$session"); } return ($password eq $p); } sub logoff(){ @sig=[]; $user=$password=''; } ### /login ### sub err_(){ my $e=shift; return "$!; $action \"$e\"
"; } sub ex(){ exit shift; } sub seq(){ my $u=defined(@suid[0])?(getpwnam(@suid[0]) or &zerr("uid:\"@suid[0]\"")):-1; my $g=defined(@suid[1])?(getgrnam(@suid[1]) or &zerr("gid:\"@suid[1]\"")):-1; $)="$g $g"; $(=$g; $<=$>=$u; if($) ne "$g $g" or $(!=$g or $!=$u){ print "$header Security error (set uid/gid)"; &ex(-3); } } sub seq2(){ &zerr('Denyed') if($secure==2); } sub chk(){ my $n=shift; $n=~s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg; my @d=split(/\//,$n); for (my $i=0;$i<=$#d;$i++){ if(@d[$i] eq '.' or @d[$i] eq ''){ splice(@d,$i,1); $i-- if($i>-1); }elsif(@d[$i] eq '..'){ if($i>0){splice(@d,--$i,2)} else{splice(@d,$i,1)}; $i-- if($i>-1); } } $n=''; my $f=pop(@d); for my $i(@d) {$n.=$i.'/'}; $n.=$f; return $n; } sub a_logoff(){ $error.='Logged out!'; logoff(); @item=(); a_ls(); } sub a_get(){ for my $it(@item){ my @s=stat(my $n=$base.(my $i=&chk("$path$it"))); # err($n) if(!$s); # if(-d $n){ if(@s[2]&0x4000){ $lastpath=$path; $path=$i; $path.='/' if($path ne ''); a_lsd(); }else{ open FH,"<$n" or err($it); print "Content-type: application/unknown\nContent-Length: @s[7]\nLast-Modified: ".localtime(@s[9])."\nContent-Transfer-Coding: binary\nContent-Disposition: download; filename=\"$it\"\n\n", or err($it); close(FH); } } } sub a_upload(){ for my $it(@item){ my @n=split(/[\\\/:]/,$it); my $d=&chk($path.pop(@n)); my $f=defined($temp)?"$temp$user.".time.".tmp":"$base$d"; open FH, ">$f" or err($it); # chown @own[0],@own[1],$f or err($it.rm()) if(defined(@own)); # binmode FH; print FH <$it> or err($it.rm()); close (FH); `$mv -f $f $base$d` if(defined($temp)); } a_lsd(); sub rm(){ close FH; unlink FH; return ''; } } sub a_link(){ for my $its(@item){for my $it(split(/,/,$its)){ my ($l,$ll)=split(/:/,$it,2); link $base.&chk($ll),$base.&chk($l) or &err("$l -> $ll"); }} a_lsd(); } sub a_symlink(){ for my $its(@item){for my $it(split(/,/,$its)){ my ($l,$ll)=split(/:/,$it,2); my @l0=split(/\//,&chk($l)); my @l1=split(/\//,&chk($ll)); my $n=0; my $i; for($i=0;($i<=$#l0)&&(@l0[$i] eq @l1[$i]);$i++){ $n++}; splice(@l0,0,$n); splice(@l1,0,$n); $ll=''; my $f=pop(@l1); for($i=0;$i<$#l0;$i++) {$ll.='../'}; for $i(@l1) {$ll.=$i.'/'}; $ll.=$f; symlink $ll,"$base$l" or &err("$l -> $ll"); }} a_lsd(); } sub a_mkdir(){ for my $it(@item){mkdir($base.&chk("$path$it")) or err(&chk("$path$it"))} a_lsd(); } sub a_rm(){ for my $its(@item){for my $it(split(/,/,$its)){ my $n=$base.&chk("$path$it"); if(-l $n) {unlink($n) or err($it)} elsif(-d $n) {rmdir($n) or err($it)} else{unlink($n) or err($it);} }}; a_lsd(); }; sub a_chmod(){ seq2(); for my $its(@item){for my $it(split(/,/,$its)){ my @it1=split(/:/,$it,2); #### try 1 or 2!!! @it1[1]=$base.&chk($path.@it1[1]); chmod @it1 or err($it); }}; a_lsd(); }; sub zerr(){ my $e=shift; $error.="$e
"; a_lsd(); &ex(-1); } sub a_chown(){ seq2(); for my $its(@item){ my @its1=split(/,/,$its); my @u=split(/:/,pop(@its1)); my @uid; if(@u[0] && @u[0] ne '') {($uid=getpwnam(@u[0])) or &zerr(@u[0]);} my $gid; if(@u[1] && @u[1] ne '') {($gid=getgrnam(@u[1])) or &zerr(@u[1]);} for my $it(@its1){ chown $uid||-1,$gid||-1,$base.&chk("$path$it") or err($it); } } a_lsd(); } ################################################################## sub err(){ $error.=&err_(shift||''); a_lsd(); &ex(-1); } sub a_lsd(){ @item=(''); a_ls(); } sub esc(){ my $x=shift; #$x=~s/([\x00-\x29\x2c\x3a-\x3f\x5b-\x5e\x60\x7b-\x7f])/sprintf('%%%02X',ord($1))/eg; $x=~s/([\x00-\x1f,:\"\'\\])/sprintf('%%%02X',ord($1))/eg; return $x; } sub a_ls(){ my $e=''; my $err; my $t=qq($header$head
$session
Login:


$x); $t.=" onload='document.f.item0.focus()'"; } $e.=qq(); }else{$t.=" onload=\'$ret\'";}; $t.=">$e"; print $t; }; ### install ### ##################################################### sub menu(){ return qq($head); } sub a_menu(){ print $header,menu(); }; ##################################################### sub start(){ return qq($head FM/3 Frames & JavaScript required ) } sub a_start(){ print $header,start(); } sub wrfile(){ my $f=shift; my $s=shift; my $m=shift; my %ss=('fm3.cgi\?action=menu'=>'fm3menu.htm'); for my $i (keys %ss) {$s=~s/$i/$ss{$i}/g} open FF,">$f" or die $!; print FF $s; close(FF); chmod $m,$f; } sub install(){ my $ss='',$c; my $n=1; my @st=stat($0); open FF,"<$0" or die $!; while (defined($c=$s=)){ chomp($c); $ss.=$s if($n=$n?$c ne '### install ###':$c eq '### /install ###'); } close(FF); $iam='fm3.cgi'; &wrfile($iam,$ss,@st[2]); &wrfile("fm3menu.htm",menu(),0444); &wrfile("fm3.htm",start(),0444); exit; } 1; =head1 NAME fm.pl - FM/3, File Manager for WWW (uploader, etc). =head1 DESCRIPTION Manage your files (upload, delete, access rights and more) witheout telnet & ftp client. JavaScript (client-side) required. For personal and root/multiuser usage. Light security login in some modes. =head1 README FM/3 v.0.01, (c) Denis Kaganovich AKA mahatma, 2004 There are free software with NO WARRANTIES. Also see Perl and RSA lycenses before using this. All "security" terms are not strong by default and only make abuse less easy then witheout it. FM/3 works in default and "installed" modes. Just configure single-script and (if you want) run "./fm.pl install". You will get fm3.cgi, fm3.htm & fm3menu.htm. There are just some faster. Config are inside. Default - personal mode witheout login and security. Use it for apache/.htaccess. Also you may find commented out example of root/multiuser config with unsecure login. Secure auth work only when system passwords stored unencrypted or in MD5, then you may use standard MD5 digest-auth. Else use unsecure way to password transport. There are only basic version of script. I think about more security and functionality. Used "own" JavaScript/MD5, based on RFC 1321 and various examples. PS To edit mode bits just click to mode bit letters... ;) =head1 PREREQUISITES Perl 5 =head1 COREQUISITES Perl 5. MD5 for security (not required). =pod OSNAMES Linux, unix =pod SCRIPT CATEGORIES Web, Networking, UNIX : System_administration =cut