#!/usr/bin/env perl
# This code is provided without warranty of any kind. If you break it, you
# get to keep the pieces.
#
# I ran it as /usr/local/www/apache24/cgi-bin/apachee24_ls.cgi, with the
# following lines somewhere in httpd.conf (without the "#", of course):
#
#
# AllowOverride None
# Options None
# Require all granted
#
#
# ScriptAlias /cgi-bin/ "/usr/local/www/apache24/cgi-bin/"
#
# DirectoryIndex index.cgi index.html /cgi-bin/apache24_ls.cgi
#
# [end of httpd.conf lines]
#
# This code has at least six limitations. (Thanks, Monty Python!)
#
# 1. It has been tested only with an apache configuration that's wide open. A
# more security-conscious configuration may break this script.
# 2. It has not been designed with security configurations in mind. You may
# wish to improve it to make it more secure.
# 3. Even laying aside security considerations, it was written with common
# configuration assumptions in mind. I don't even know exactly what those
# assumptions are, and you won't either until you actually run this script
# in an environment where those assumptions don't hold.
# 4. It doesn't take into account strange characters like percent sign and
# space.
# 5. You may wish to narrow the display by removing some of the displayed
# information.
# 6. It logs debugging info to /tmp/billylog. You may wish to make use of
# this while debugging your own modifications, and/or remove this feature
# entirely by searching for "billylog" in the script.
# 7. I'm sure it has bugs.
#
# Bill Evans
# wje@acm.org
use strict;
use warnings FATAL=>"all";
#-----------------------------------------------------------------------------
sub billylog
{
my $out_string=shift;
my $phyle;
open($phyle,">>","/tmp/billylog");
print($phyle sprintf("%05d",$$)."$out_string\n");
close($phyle);
} # billylog()
#-----------------------------------------------------------------------------
sub spit
{
my $spittle=shift;
print $spittle;
$spittle=~s/\r?\n?$//;
billylog("\"$spittle\"")
}
#-----------------------------------------------------------------------------
sub fix_spaces
{
my $in_string=shift;
$in_string=~s/ /\ \;/g;
return $in_string;
} # fix_spaces()
#-----------------------------------------------------------------------------
sub etc_passwd
{
my $identity=shift;
my $flags =shift;
my $in_line;
my $phyle;
my @array;
open($phyle,"<","/etc/passwd")
or
return "inaccessible";
while($in_line=<$phyle>)
{
$in_line=~s/\r?\n?$//;
if($in_line=~/^\#/)
{
next; # <---------
}
@array=split(/:/,$in_line);
if(0+$array[2] == $identity)
{
close($phyle);
return sanitize($array[0]);
}
}
close($phyle);
if(defined($flags))
{
return "no such user name";
}
else
{
return sanitize($identity);
}
} # etc_passwd()
#-----------------------------------------------------------------------------
sub etc_group
{
my $identity=shift;
my $flags =shift;
my $in_line;
my $phyle;
my @array;
open($phyle,"<","/etc/group")
or
return "inaccessible";
while($in_line=<$phyle>)
{
$in_line=~s/\r?\n?$//;
if($in_line=~/^\#/)
{
next; # <---------
}
@array=split(/:/,$in_line);
if(0+$array[2] == $identity)
{
close($phyle);
return sanitize($array[0]);
}
}
close($phyle);
if(defined($flags))
{
return "no such group name";
}
else
{
return sanitize($identity);
}
} # etc_group()
#-----------------------------------------------------------------------------
sub printable_time
{
my $the_time=shift;
my $sec;
my $min;
my $hour,
my $mday,
my $mon,
my $year;
my @mlist=("Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec"
);
($sec,
$min,
$hour,
$mday,
$mon,
$year
)=localtime($the_time);
return sprintf("%04d %s %2d %02d:%02d:%02d",
$year+1900,
$mlist[$mon],
$mday,
$hour,
$min,
$sec
);
} # printable_time()
#-----------------------------------------------------------------------------
sub sanitize
{
my $in_string=shift;
if(!defined($in_string))
{
$in_string="undefined";
}
else
{
$in_string=~s/\&/\&\;/g;
$in_string=~s/\\<\;/g;
$in_string=~s/\>/\>\;/g;
if($in_string eq "")
{
return " ";
}
}
return $in_string;
} # sanitize()
#-----------------------------------------------------------------------------
sub handler
{
my $home_directory;
my $in_line;
my $param_1dot;
my $param_2dots;
my $param_dir_1;
my $param_dir_2;
my $param_file_1;
my $param_file_2;
my $param_mode_switching;
my $passwd_phyle;
my $physical_filename;
my $single_key;
my $single_value;
my $target_filename;
my $test_mode;
my $user_name;
my @arg_array;
my @passwd_fields;
my %arg_hash;
billylog("=== \"".$ENV{"REQUEST_URI"}."\"");
$target_filename=$ENV{"REQUEST_URI"};
$target_filename=~s/^(.*?)\?.*$/$1/;
$target_filename=~s/\/+$//;
$physical_filename=$target_filename;
if($physical_filename=~/^\/\~/)
{
$user_name=$physical_filename;
$user_name=~s/^\/\~(.*?)\/.*$/$1/
or
$user_name=~s/^\/\~([^\/]*)$/$1/;
if(open($passwd_phyle,"<","/etc/passwd"))
{
while($in_line=<$passwd_phyle>)
{
$in_line=~s/\r?\n?$//;
if($in_line=~/^\#/)
{
next; # <---------
}
@passwd_fields=split(/\:/,$in_line);
if(scalar(@passwd_fields)!=7)
{
next; # <---------
}
if($passwd_fields[0] eq $user_name)
{
last; # <---------
}
}
close($passwd_phyle);
}
if(defined($in_line))
{
$physical_filename=~s/^\/\~(.*?)\//$passwd_fields[5]\/public_html\//;
$physical_filename=~s/^\/\~(.*?)$/$passwd_fields[5]\/public_html/;
}
}
if($ENV{"QUERY_STRING"})
{
@arg_array=split(/\&/,$ENV{'QUERY_STRING'});
}
while(scalar(@arg_array))
{
$single_key =$arg_array[0];
$single_value=$arg_array[0];
if($single_key=~/\=/)
{
$single_key =~s/^(.*?)\=(.*)$/$1/;
$single_value=~s/^(.*?)\=(.*)$/$2/;
}
else
{
$single_value="";
}
$arg_hash{$single_key}=$single_value;
shift(@arg_array);
}
if(defined($arg_hash{"test"}))
{
$test_mode=1;
}
else
{
$test_mode=0;
}
if($test_mode) { ###########################################################
spit("Content-Type: text/html\n");
spit("\n");
spit("
\n");
spit("\r\n");
spit("| variable name | value | \r\n");
foreach $single_key (sort keys %ENV)
{
spit("| $single_key | \"".sanitize($ENV{$single_key})."\" |
\r\n");
}
spit("
\r\n");
spit("\n");
return 0;
} # if(test_mode) ##########################################################
# The f parameter informs us, when we're generating a directory, whether the
# directory will end up in a single frame; the lefthand frame; the righthand
# frame; the righthand frame of a framework which we should provide, and put
# the directory's parent in the lefthand frame; or the lefthand frame of a
# frameset which we should provide, and put the specified document in the
# righthand frame.
#
# Parameters are handled as follows.
#
# -- If there are no parameters:
#
# -- If the directory contains index.cgi or index.html, pump it out.
# -- Otherwise, divide the document into two frames.
#
# -- The first frame should point to ..,f=l; NAME=l.
# -- The second frame should point to .,f=r ; NAME=r.
#
# -- f=d&d=something (d stands for document)
#
# -- Divide the document into two frames.
# -- The first frame should point to .,f=l; NAME=l.
# -- The second frame should point to something; NAME=r.
#
# -- f=s (s stands for single)
#
# -- Display the directory in its current frame.
#
# -- The mode switching link should go to plain ".".
# -- For .. and . and all other directories, the link should
# include f=s.
# -- For all other files, the link should be plain.
#
# -- f=l (l stands for lefthand)
#
# -- Display the directory in its current frame.
#
# -- The mode switching link should go to .?f=s; TARGET=_top.
# -- For .. and . the links should be plain, TARGET=_top.
# -- For other directories, the links should be f=r; TARGET=r.
# -- For other files, the links should specify TARGET=r.
#
# -- f=r (r stands for righthand)
#
# -- Display the directory in its current frame.
#
# -- The mode switching link should go to .?f=s; TARGET=_top.
# -- For .. the link should be plain, TARGET=_top
# -- For . the link should go to .,f=r.
# -- For other directories, the links should be plain, TARGET=_top.
# -- For other files, the links should be .,f=d,d=destination,
# TARGET=_top.
# Correct for any missing destination, just for bulletproofing.
if((defined($arg_hash{"f"})) &&
($arg_hash{"f"} eq "d")
)
{
if((!defined($arg_hash{"d"})) ||
($arg_hash{"d"} eq "")
)
{
delete($arg_hash{"d"});
delete($arg_hash{"f"});
}
}
if((!defined($arg_hash{"f"})) ||
($arg_hash{"f"} eq "")
)
{
spit("Content-Type: text/html\n");
spit("\n");
spit("\n");
return 0;
} # if there's no f parameter
elsif($arg_hash{"f"} eq "d")
{
billylog("f is d");
spit("Content-Type: text/html");
spit("\n");
spit("\n");
return 0;
}
elsif($arg_hash{"f"} eq "s")
{
billylog("f is s");
$param_mode_switching="";
$param_2dots="";
$param_1dot="";
$param_dir_1="";
$param_file_1="";
}
elsif($arg_hash{"f"} eq "l")
{
billylog("f is l");
$param_mode_switching="";
$param_2dots="";
$param_1dot="";
$param_dir_1="";
$param_file_1="";
}
elsif($arg_hash{"f"} eq "r")
{
billylog("f is r");
$param_mode_switching="";
$param_2dots="";
$param_1dot="";
$param_dir_1="";
$param_file_1="";
}
else
{
billylog("oops");
spit("Content-Type: text/plain\n");
spit("Status: 500 Internal Server Error\n");
spit("\n");
return 0;
}
{
# Actually list the directory.
my $buffer;
my $dear;
my $entry;
my $jndex;
my $max_group_width;
my $max_nlink_width;
my $max_size_width;
my $max_user_width;
my $st_dev;
my $st_ino;
my $st_mode;
my $st_nlink;
my $st_uid;
my $st_gid;
my $st_rdev;
my $st_size;
my $st_atime;
my $st_mtime;
my $st_ctime;
my $st_blksize;
my $st_blocks;
my %filenames;
if(!opendir($dear,$physical_filename))
{
my $status=$!;
if($status==13)
{
spit("Content-Type: text/plain\n");
spit("Status: 403 Forbidden\n");
spit("\n");
spit("403 Forbidden $target_filename\n");
return 0;
}
else
{
spit("Content-Type: text/plain\n");
spit("Status: 404 Not Found\n");
spit("\n");
spit("404 Not Found $target_filename\n");
return 0;
}
}
spit("Content-Type: text/html\n");
spit("\n");
spit("\n");
spit
("".$param_mode_switching."Click here to switch modes.\n");
spit("ls -l --kinda $target_filename
");
while($entry=readdir($dear))
{
if($entry=~/^\./)
{
next;
}
$filenames{$entry}="X";
}
closedir($dear);
# Get widths of columns. Grr.
$max_nlink_width=1;
$max_size_width =1;
$max_user_width =0;
$max_group_width=0;
foreach $entry (sort(keys(%filenames)))
{
($st_dev,
$st_ino,
$st_mode,
$st_nlink,
$st_uid,
$st_gid,
$st_rdev,
$st_size,
$st_atime,
$st_mtime,
$st_ctime,
$st_blksize,
$st_blocks
)=stat("$physical_filename/$entry");
if($max_nlink_width");
if((!-r "$physical_filename/$entry") &&
($entry!~/^\./)
)
{
spit("");
}
elsif($entry!~/^\./)
{
if(-d "$physical_filename/$entry")
{
spit($param_dir_1);
}
else
{
spit($param_file_1);
}
for($jndex=0;
$jndex");
}
else
{
spit("");
}
spit("\n");
}
spit("");
spit("\n");
}
return 0;
} # handler()
handler();
#