#!/usr/bin/perl -w ########################################################################## # Blosedit.cgi # # Allows editing of Blosxom files while maintaining posting file date. # # Using just one other file for user/password entry, one can root a # # user to a home directory. The use of user flags control visibility of # # subdirectories such as /.settings/ as used in Blosxom 3 and visibility # # of dot named files such as .htaccess. Old files can be edited while # # maintaining integrity of timestamps. New files can aslo be created. # # Edited files can be saved as draft and then later saved as completed # # Directories or files need to be writable by "www" (or "apache") to # # work. # # # # Blosedit is free software. you may redistribute it and/or modify it # # under the terms of the GNU General Public License as published by the # # Free Software Foundation. # # pdr 11/08/04, v1.1.6 # ########################################################################## use strict; use CGI; use Time::Local; use File::Find; # # <<<<<<<<<<<<<< Edit below for Your setup >>>>>>>>>>>>>>>>> # as they say, "Location, Location, Location!" #my $passfile = '/Library/WebServer/CGI-Executables/pssw.txt'; # absolute path to user/password file my $passfile = '/var/www/cgi-bin/pssw.txt'; # absolute path to user/password file my $salt ='gh'; # salt for crypt function my $usesecure = 0; # set if you want to use SSL secured cookie my $Xpiration = '+1d'; # cookie expiration date my $MAX_SIZE = 50; # if flag is set, user is not allowed to upload flles larger then this in kilobytes my $draft = '.tmp'; # draft file extension ig. mysummertrip.txt is saved as mysummertrip.txt.tmp # thus preventing blosxom from showing it until it has a valid 'flavour' name # also when re-loaded, comes up with non-draft name for normal save # for more personal style define your own header/footer # Note, user's header and footer files care not active until log in my $usr_headerfile =''; # path is relative to current user's root my $usr_footerfile =''; # path is relative to current user's root # If user's header/footer not defined or possibly doesn't exist then try default (if defined and exists) my $default_headerfile=''; # must be aboslute path to header my $default_footerfile=''; # must be aboslute path to footer my $Use_JavaScript=1; #set to zero if no Java script wanted # <<<<<<<<<<<<<< Edit above for Your setup >>>>>>>>>>>>>>>>> ########################################################################## # Password File Format: # a white-space delimited file with the following on each line # userid password flags rootdirectory # userid may be preded by white-space # if the path to user's root has a subdirectory with white space in the name then you must quote the # pathname. Single or double quotes ok # examples: # admin bratwurst 0 /var/www/html/data # betty boop 3 '/var/www/html/toons only' # otto bismark 12 "/var/www/html/prussia 1900" # # file flags are decimal summation of flag (bits) values that are set # if flag is clear then its value is not added (= zero) # File Invisibility value = 1 files with names starting with period are not shown # Directory Invisible value = 2 directories w/names starting w/period are not shown # Directory create value = 4 User cannot create new subdirectories in home folder # File upload value = 8 User limited to maximum set file size # example: betty can not see any dot name files or directories since flag = 3 =1 + 2 # and otto is not allowed to expand beyond his current boundaries by making new subdirectories # in his root folder named prussia and his upload size is limited 12 = 4 + 8 ########################################################################## #create a new CGI object my $cgi = new CGI; my $script=$cgi->script_name; my $submitted = ''; my $user =''; my $pass =''; my $root ='/bad'; #use non-existing directory here, if somegoes wrong then will get a open error my $mydir = ''; my $Cookie = 0; my $badlogin = 0; # flags must be set to self bit mask my $FileInvisible = 1; my $DirInvisible = 2; my $DirCreate = 4; my $LimitUpload = 8; # misc globals my ($HtmlPageTitle, $EditFileName, $story_title, $DateComment) = ('', '', '', ''); my ($LoginForm, $FileNavForm, $NewDirForm, $EditForm) = ('', '', '', ''); my ($CurrentDir, $DirList, $FileList, $UploadFile, $Logout) = ('', '', '', '',''); my ($CSS_Header, $JS_Code, $JS_Menu) = ('', '', ''); my ($FileForm, $CurrentPath, $FileName, $FileDate, $FileTime) = ('', '', '', '', ''); my ($StoryTitle, $StoryBody, $EditSubmits, $FileFormEnd) = ('', '', '', ''); #attempt to read the cookie from the clients cache my $userdata = $cgi->cookie("login"); if ($userdata) { $userdata =~ / pw=/; $user = $`; $pass = $'; $user =~ / /; $mydir = $'; $user = $`; if (ValidateUser()) { $Cookie =1; # user has a valid cookie. GetAction(); } } # check if the user has filled in the form. verify their ID/password and issue a cookie $submitted = $cgi->param('choose'); if (($submitted) && ($submitted eq 'Login')){ $user = $cgi->param('user'); $pass = $cgi->param('pass'); chomp($user); chomp($pass); $pass = Encrypt($pass) if ($pass); if (ValidateUser()) { $Cookie =1; HTML_ShowFiles(); } else { # Let them know that they didn't pass $badlogin =1; } } if (!$Cookie) { # user has no valid cookie. Allow them to log in HTML_Login(); } ############## HTML Pages and their Forms ############## sub HTML_Login { $LoginForm = qq(
Username:
Password:

); $LoginForm .= qq(

Username/Password invalid!

\n) if ($badlogin); $LoginForm .= qq(
); $HtmlPageTitle = 'Login'; HTML_Header ($LoginForm); HTML_Foot(); } sub HTML_MkDir { $NewDirForm = qq(
New Directory:
); $HtmlPageTitle = 'Enter New Directory Name'; HTML_Header ($NewDirForm); HTML_Foot(); } sub HTML_ShowFiles { my $dir = $root.'/'.$mydir; #directory location header $CurrentDir = qq(
Subdirectory = /root$mydir
); # Select Directory form $DirList = qq(



);; if (!$DirCreate) { $DirList .=qq(
) ; } $DirList .= qq(
\n
); # File List form chdir($dir); opendir(DIRHANDLE, $dir) || Error( "Can't open ", $dir); my @Flist = readdir(DIRHANDLE); closedir(DIRHANDLE); $FileList = qq(




); # Upload File form $UploadFile = qq(

Upload:
); $Logout = qq(
); $FileNavForm .= $CurrentDir.$DirList.$FileList.$UploadFile.$Logout; $HtmlPageTitle = 'Select File'; HTML_Header($FileNavForm); HTML_Foot(); } sub HTML_EditFile { chdir ($root.$mydir); my @fcontent; my ($Sec, $Min, $Hr, $Day, $Mon, $Yr) ; if($EditFileName) { open (FILEHANDLE, $root.$mydir.'/'.$EditFileName) || Error('Cant open: ', $EditFileName); @fcontent = ; my $mtime= (stat(FILEHANDLE))[9]; close(FILEHANDLE); $story_title = shift (@fcontent); $story_title =~ s/&/&/g; $story_title =~ s/\"/"/g; ($Sec, $Min, $Hr, $Day, $Mon, $Yr) = localtime($mtime); $Sec = "0$Sec" if($Sec <10); $Min = "0$Min" if($Min <10); $Hr = "0$Hr" if($Hr <10); $Day = "0$Day" if($Day <10); $Mon = "0$Mon" if(++$Mon <10); $Yr = $Yr + 1900; $EditFileName =~ s/\.$draft\z// ; } else { $EditFileName = 'untitled.txt'; my $i =0; while (( $i < 100) && ((-e $EditFileName) || (-e $EditFileName.'.'.$draft))) { $EditFileName = 'untitled'.++$i.'.txt' } } # EditForm Components Set_JSMenu($JS_Menu) if ($Use_JavaScript); $FileName = qq(File Name: ); $FileDate = qq(Date:  /   /  ); $DateComment = ' enter zero in MM or DD to use current DateTime
'; $FileTime = qq(

Time:  :   : 

); $CurrentPath = qq(
Subdirectory = /root$mydir
\n); $FileForm = qq(
); $StoryTitle .= qq(

Title

); $StoryBody= qq(

Body

\n); $EditSubmits .= qq( ); $FileFormEnd = '
'; # ediiting form $EditForm = qq($CurrentPath$FileForm

$FileName

$FileDate$DateComment

); $EditForm .= qq(

$FileTime

$StoryTitle$StoryBody); $EditForm .= $JS_Menu.$EditSubmits.$FileFormEnd; $HtmlPageTitle ='Edit File'; HTML_Header($EditForm); HTML_Foot(); } sub HTML_Header { if (($HtmlPageTitle ne 'Login') || ($Xpiration eq 'now')) { # you passed user/password, so here's a cookie for you! $userdata = "$user $mydir pw=$pass"; $Cookie = $cgi->cookie(-name=>'login', -value=>$userdata, -expires=>$Xpiration, -secure=>$usesecure); print $cgi->header(-cookie=>$Cookie); } else { # blehhhh... no cookie for you print $cgi->header; } SetCSSHead ($CSS_Header); Set_JSCode ( $JS_Code) if ($Use_JavaScript && ($HtmlPageTitle eq 'Edit File')); # get a name of an external header only if it exists my $headerfile; if ($usr_headerfile && (-e $root.'/'.$usr_headerfile)) { $headerfile = $root.'/'.$usr_headerfile; } elsif ($default_headerfile && (-e $default_headerfile)) { $headerfile = $default_headerfile; } if ($headerfile) { open (HTML_FILEHANDLE, $headerfile) || Error('Cant open header file: ', $headerfile); my @hcontent = ; close(HTML_FILEHANDLE); foreach (@hcontent) { s/(\$\w+(?:::)?\w*)/$1/gee; print ($_) ; } } else { print $CSS_Header; print $JS_Code; print "\n"; print qq(

) if (($HtmlPageTitle eq 'Select File') || ($HtmlPageTitle eq 'Edit File')); print $_[0]; print qq(
) if (($HtmlPageTitle eq 'Select File') || ($HtmlPageTitle eq 'Edit File')); } } sub HTML_Foot { # get a name of an external footer only if it exists my $footerfile = ''; if ($usr_footerfile && (-e $root.'/'.$usr_footerfile)) { $footerfile = $root.'/'.$usr_footerfile; } elsif ($default_footerfile && (-e $default_footerfile)) { $footerfile = $default_footerfile; } if ($footerfile ) { open (HTML_FILEHANDLE, $footerfile) || Error('Cant open footer file: ', $footerfile); my @hcontent = ; close(HTML_FILEHANDLE); foreach (@hcontent) { print() } } else { print qq(); } } ############## Utility Subs ############## sub ValidateUser { my $results = 0; if (($user) && ($pass)) { # Get User Info from password File and check against login or cookie open (FILEHANDLE, $passfile) || Error('open', $passfile); while () { # search for user name match at 1st word in line last if (/^\s*$user\s/); } close(FILEHANDLE); # clear any spaces before matched user name s/^\s*//; (my $ulogin, my $upass, my $uflags, $root) = split /\s+/; (my $temp, $root) = split /[\"\']/ if (/[\"\']/ ); $upass = Encrypt($upass); if (($ulogin eq $user) && ($upass eq $pass)) { $FileInvisible &= $uflags; $DirInvisible &= $uflags; $DirCreate &= $uflags; $LimitUpload &= $uflags; $results=1; } } return $results; } sub Encrypt { # since the built-in function crypt() will only encode upto 8 characters, double up on its use to support # passwords upto 16 characters in a way that generate unique pairs of crypt() output with the salt removed. # Side effect: this does change the value of the varible pass to it. my $secstr; if ( length($_[0]) > 8 ) { $_[0] = $_[0].'LooseLips'; $secstr = substr($_[0], 8,8); } else { $secstr = $_[0]. 'sinks'; $_[0] = $_[0]. 'Ships'; } return substr(crypt($_[0], $salt),2,11). substr(crypt($secstr, $salt),2,11); } sub GetAction { # Evaluate all form data and act upon it $submitted = $cgi->param('choose'); my $option = $cgi->param('sname'); $EditFileName = $cgi->param('new'); if ($submitted eq 'New Directory') { # Open small page to input new directory name HTML_MkDir (); } elsif ($submitted eq 'Create') { # Have a new directory name, create new directory if ($EditFileName) { $mydir = $mydir."/".$EditFileName; mkdir ($root.$mydir , 0777) || Error ('make directory','(onwership or permissions problem)'); HTML_ShowFiles (); } } elsif ($submitted eq 'Remove Directory') { # Delete selected directory if (($option ne 'Updir') || !$option) { if (rmdir("$root/$mydir/$option") eq 0){ Warn($option, 'not empty? or permissions problem') } else { HTML_ShowFiles (); } } } elsif ($submitted eq 'Select Directory') { # open selected directory if ($option eq "root") { $mydir = ''; } elsif ($option) { $mydir = $option; } HTML_ShowFiles (); } elsif ($submitted eq 'New File') { # Create new file for editing $EditFileName = ''; HTML_EditFile (); } elsif ($submitted eq 'Remove File') { # delete selected file if ($option) { unlink("$root/$mydir/$option"); } HTML_ShowFiles (); } elsif (($submitted eq 'Edit File') && $option) { # open selected file for editing $EditFileName = $option; HTML_EditFile (); } elsif ($submitted eq 'Upload') { # Upload file, 1st strip input path to filename only my $upload = $cgi->param('upload'); if ($upload) { $_ = $upload; s/\w:/\//g; #for Mac OS 9 replace colons with forward slash s/\w\\/\//g; #for Windows replace backslash with forward slash s/([^\/\\]+)$//; my $filename = $1; # 2nd add current path to filename and write to open(FILEHANDLE,'>',$root.$mydir.'/'.$filename) || Error('Error opening file '," $filename for writing!"); binmode FILEHANDLE; my $i; while (read($upload, my $buff,1024)) { if ((++$i > $MAX_SIZE) && ($LimitUpload)) { $i = -$MAX_SIZE; last; } else { print FILEHANDLE $buff; } } close(FILEHANDLE); if (($i <= 0) || ((stat ($root.$mydir.'/'.$filename))[7] <= 0)){ unlink ($root.$mydir.'/'.$filename); Error($filename, "exceeded ".-$i." kB max file size or write unsuccessful"); } } HTML_ShowFiles(); } elsif (($submitted eq 'Save') || ($submitted eq 'Draft')) { # fix filename if draft and evaluate date/time then save edited file $option =$option.'.'.$draft if ($submitted eq 'Draft'); my $filename = $root.$mydir.'/'.$option; my $Mon = $cgi->param('fmonth'); my $Day = $cgi->param('fday'); my $Yr = $cgi->param('fyear'); my $Hr = $cgi->param('fhour'); my $Min = $cgi->param('fmin'); my $Sec = $cgi->param('fsec'); my $mtime; if (($Mon > 0) && ($Day > 0)) { eval {$mtime = timelocal($Sec, $Min, $Hr, $Day, $Mon-1, $Yr) }; Error ('continue', "invalid Date or Time entry") if $@; } else { $mtime=time(); } # save edited file my $title = $cgi->param('title'); my @fcontent = $cgi->param('body'); open(FILEHANDLE,">$filename") || Error('Error opening file '," $option for writing!"); print FILEHANDLE "$title\n"; foreach (@fcontent) { print FILEHANDLE $_ } close(FILEHANDLE); # uopdate user's desired Date/time to file's Date/time utime $mtime, $mtime, $filename; HTML_ShowFiles(); } elsif ($submitted eq 'Logout') { # logout by forcing cookie expiration to now $Xpiration='now'; HTML_Login(); } else { # no cmd so just show file navigation page HTML_ShowFiles(); } } sub ListDirs { if (-d $_) { my $found = $File::Find::name; $found =~ s/\.//; $found = 'root' if ($found eq ''); if (!( $DirInvisible && ($found =~ /\/\./) )) { my $opt = '