Files
webmin/file/file-lib.pl
2007-04-12 19:28:29 +00:00

453 lines
11 KiB
Perl

# file-lib.pl
# Common functions for file manager CGIs
do '../web-lib.pl';
&ReadParse(\%prein, 'GET');
if ($prein{'trust'}) {
&open_trust_db();
if ($trustdb{$prein{'trust'}}) {
$trust_unknown_referers = 1;
$trustdb{$prein{'trust'}} = time();
}
dbmclose(%trustdb);
}
&init_config();
do '../ui-lib.pl';
@file_buttons = ( "save", "preview", "edit", "info", "acl", "attr", "ext", "search",
"delete", "new", "upload", "mkdir", "makelink",
"rename", "sharing", "mount", "copy" );
if ($module_info{'usermin'}) {
# Usermin gets the allowed list from the module config
&switch_to_remote_user();
&create_user_config_dirs();
$hide_dot_files = $userconfig{'hide_dot_files'};
$follow = int($config{'follow'});
$real_home_dir = &simplify_path(&resolve_links($remote_user_info[7]));
$upload_max = $config{'max'};
if ($config{'home_only'} == 1) {
@allowed_roots = ( $real_home_dir,
split(/\s+/, $config{'root'}) );
}
elsif ($config{'home_only'} == 2) {
@allowed_roots = split(/\s+/, $config{'root'});
}
else {
@allowed_roots = ( "/" );
}
@denied_roots = split(/\s+/, $config{'noroot'});
if ($config{'archive'} eq 'y') {
$archive = 1;
}
elsif ($config{'archive'} eq 'n') {
$archive = 0;
}
else {
$archive = 2;
$archmax = $config{'archive'};
}
$unarchive = 1;
$dostounix = 1;
$chroot = "/";
@disallowed_buttons = ( );
foreach $k (keys %config) {
if ($k =~ /^button_(.*)/ && $config{$k} == 0) {
push(@disallowed_buttons, $1);
}
}
$canperms = 1;
$canusers = 1;
$contents = 1;
}
else {
# Webmin gets the list of allowed directories from the ACL
%access = &get_module_acl();
$hide_dot_files = $config{'hide_dot_files'};
$follow = int($access{'follow'});
$upload_max = $access{'max'};
@allowed_roots = split(/\s+/, $access{'root'});
if ($access{'home'}) {
local @u = getpwnam($remote_user);
if (@u) {
push(@allowed_roots,
&simplify_path(&resolve_links($u[7])));
}
}
@denied_roots = split(/\s+/, $access{'noroot'});
$archive = $access{'archive'};
$archmax = $access{'archmax'};
$unarchive = $access{'unarchive'};
$dostounix = $access{'dostounix'};
$chroot = $access{'chroot'};
$access{'button_search'} = 0 if (!&has_command("find"));
$access{'button_makelink'} = 0 if (!&supports_symlinks());
$access{'button_info'} = 0 if (!&supports_users());
@disallowed_buttons = grep { !$access{'button_'.$_} } @file_buttons;
if (&is_readonly_mode()) {
# Force read-only mode for file manager if global readonly
# is in effect.
$access{'ro'} = 1;
}
$canperms = $access{'noperms'} ? 0 : 1;
$canusers = $access{'nousers'} ? 0 : 1;
$contents = $access{'contents'};
}
%disallowed_buttons = map { $_, 1 } @disallowed_buttons;
$icon_map = ( "c", 1, "txt", 1,
"pl", 1, "cgi", 1,
"html", 1, "htm", 1,
"gif", 2, "jpg", 2,
"tar", 3
);
# file_info_line(path, [displaypath])
# Returns a line of text containing encoded details of some file
sub file_info_line
{
local @st;
local $islink = (-l $_[0]);
local $f = $islink && &must_follow($_[0]);
local @st = $f ? stat($_[0]) : lstat($_[0]);
local $ext = $_[0] =~ /\S+\.([^\.\/]+)$/ ? $1 : undef;
local $dp = $_[1] || $_[0];
$dp =~ s/\\/\\\\/g;
$dp =~ s/\t/\\t/g;
return undef if ($dp =~ /\r|\n/);
if (!@st) {
# Work around a broken stat function on large files on redhat 7.x
&has_command("stat") || return undef;
local $out = `stat -t '$_[0]'`;
return undef if ($?);
$out =~ /^(.*)\s+(\d+)\s+(\d+)\s+(\S+)\s+(\d+)\s+(\d+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)/;
local $type = defined($icon_map{$ext}) ? $icon_map{$ext} : 4;
local $user = defined(%uid_to_user) ? $uid_to_user{$5} : getpwuid($5);
$user = $5 if (!$user);
local $group = defined(%gid_to_group) ? $gid_to_group{$6} :getgrgid($6);
$group = $6 if (!$group);
local $size = $2;
local $mtime = $13;
local $mode = hex($4);
return sprintf ("%s\t%u\t%s\t%s\t%u\t%u\t%u\t%s",
$dp, $type, $user, $group, $size, $mode, $mtime, undef);
}
local $type = $islink && !$f ? 5 :
-d _ ? 0 :
-b _ ? 6 :
-c _ ? 6 :
-p _ ? 7 :
-S _ ? 7 : defined($icon_map{$ext}) ? $icon_map{$ext} : 4;
local $user = !&supports_users() ? "root" :
defined(%uid_to_user) ? $uid_to_user{$st[4]} : getpwuid($st[4]);
$user = $st[4] if (!$user);
local $group = !&supports_users() ? "root" :
defined(%gid_to_group) ? $gid_to_group{$st[5]} :getgrgid($st[5]);
$group = $st[5] if (!$group);
local $rl = readlink($_[0]);
return join("\t", $dp, $type,
$user, $group,
$st[7] < 0 ? 2**32+$st[7] : $st[7], $st[2],
$st[9], $f ? "" : $islink && !$rl ? "???" : $rl);
}
# switch_acl_uid()
sub switch_acl_uid
{
if (!$module_info{'usermin'} && $access{'uid'}) {
local @u = $access{'uid'} < 0 ? getpwnam($remote_user)
: getpwuid($access{'uid'});
@u || &error($text{'switch_euser'});
$( = $u[3]; $) = "$u[3] ".join(" ", $u[3], &other_groups($u[0]));
($>, $<) = ($u[2], $u[2]);
umask(oct($access{'umask'}));
}
}
# switch_acl_uid_and_chroot()
# Combines the switch_acl_uid and go_chroot functions
sub switch_acl_uid_and_chroot
{
if (!$module_info{'usermin'} && $access{'uid'}) {
local @u = $access{'uid'} < 0 ? getpwnam($remote_user)
: getpwuid($access{'uid'});
@u || &error($text{'switch_euser'});
local @other = &other_groups($u[0]);
&go_chroot();
$( = $u[3]; $) = "$u[3] ".join(" ", $u[3], @other);
($>, $<) = ($u[2], $u[2]);
umask(oct($access{'umask'}));
}
else {
&go_chroot();
}
}
# can_access(file)
# Returns 1 if some file can be edited/deleted
sub can_access
{
return &under_root_dir($_[0], \@allowed_roots) &&
($_[0] eq "/" || !&under_root_dir($_[0], \@denied_roots));
}
# under_root_dir(file, &roots)
# Returns 1 if some file is under one of the given roots
sub under_root_dir
{
local @f = grep { $_ ne '' } split(/\//, $_[0]);
local $r;
DIR: foreach $r (@{$_[1]}) {
return 1 if ($r eq '/' || $_[0] eq '/' || $_[0] eq $r);
local @a = grep { $_ ne '' } split(/\//, $r);
local $i;
for($i=0; $i<@a; $i++) {
next DIR if ($a[$i] ne $f[$i]);
}
return 1;
}
return 0;
}
# can_list(dir)
# Returns 1 if some directory can be listed. Parent directories of allowed
# directories are included as well.
sub can_list
{
return &under_root_dir_or_parent($_[0], \@allowed_roots) &&
($_[0] eq "/" || !&under_root_dir($_[0], \@denied_roots));
}
# under_root_dir_or_parent(file, &roots)
# Returns 1 if some file is under one of the given roots, or their parents
sub under_root_dir_or_parent
{
local @f = grep { $_ ne '' } split(/\//, $_[0]);
DIR: foreach $r (@allowed_roots) {
return 1 if ($r eq '/' || $_[0] eq '/' || $_[0] eq $r);
local @a = grep { $_ ne '' } split(/\//, $r);
local $i;
for($i=0; $i<@a && $i<@f; $i++) {
next DIR if ($a[$i] ne $f[$i]);
}
return 1;
}
return 0;
}
# accessible_subdir(dir)
# Returns the path to a dir under the given one that we can access
sub accessible_subdir
{
local ($r, @rv);
foreach $r (@allowed_roots) {
if ($r =~ /^(\Q$_[0]\E\/[^\/]+)/) {
push(@rv, $1);
}
}
return @rv;
}
sub open_trust_db
{
local $trust = "$ENV{'WEBMIN_CONFIG'}/file/trust";
eval "use SDBM_File";
dbmopen(%trustdb, $trust, 0700);
eval { $trustdb{'1111111111'} = 'foo bar' };
if ($@) {
dbmclose(%trustdb);
eval "use NDBM_File";
dbmopen(%trustdb, $trust, 0700);
}
}
# must_follow(path)
# For symlinks, returns 1 if a link should be follow, 0 if not
sub must_follow
{
if ($follow == 1) {
return 1;
}
elsif ($follow == 0) {
return 0;
}
else {
local @s = stat($_[0]);
local @l = lstat($_[0]);
@st = ($s[4] == $l[4] ? @s : @l);
return $s[4] == $l[4];
}
}
# extract_archive(path, delete)
# Called by upload to extract some zip or tar.gz file. Returns undef if something
# was actually done, an error message otherwise.
sub extract_archive
{
local $out;
$_[0] =~ /^(\S*\/)/ || return 0;
local $dir = $1;
local $qdir = quotemeta($dir);
local $qpath = quotemeta($_[0]);
if ($_[0] =~ /\.zip$/i) {
# Extract zip file
return &text('zip_ecmd', "unzip") if (!&has_command("unzip"));
$out = `(cd $qdir; unzip -o $qpath) 2>&1 </dev/null`;
if ($?) {
return &text('zip_eunzip', $out);
}
}
elsif ($_[0] =~ /\.tar$/i) {
# Extract un-compressed tar file
return &text('zip_ecmd', "tar") if (!&has_command("tar"));
$out = `(cd $qdir; tar xf $qpath) 2>&1 </dev/null`;
if ($?) {
return &text('zip_euntar', $out);
}
}
elsif ($_[0] =~ /\.(tar\.gz|tgz|tar\.bz|tbz|tar\.bz2|tbz2)$/i) {
# Extract gzip or bzip2-compressed tar file
local $zipper = $_[0] =~ /bz(2?)$/i ? "bunzip2"
: "gunzip";
return &text('zip_ecmd', "tar") if (!&has_command("tar"));
return &text('zip_ecmd', $zipper) if (!&has_command($zipper));
$out = `(cd $qdir; $zipper -c $qpath | tar xf -) 2>&1`;
if ($?) {
return &text('zip_euntar2', $out);
}
}
elsif ($_[0] =~ /\.gz$/i) {
# Uncompress gzipped file
return &text('zip_ecmd', "gunzip") if (!&has_command("gunzip"));
local $final = $_[0];
$final =~ s/\.gz$//;
local $qfinal = quotemeta($final);
$out = `(cd $qdir; gunzip -c $qpath >$qfinal) 2>&1`;
if ($?) {
return &text('zip_euntar2', $out);
}
}
else {
return $text{'zip_ename'};
}
if ($_[1]) {
unlink($_[0]);
}
return undef;
}
# post_upload(path, dir, unzip)
sub post_upload
{
local ($path, $dir, $zip) = @_;
if ($unarchive == 2) {
$zip = $path =~ /\.(zip|tgz|tar|tar\.gz)$/i ? 1 : 0;
}
elsif ($unarchive == 0) {
$zip = 0;
}
local $refresh = $path;
local $err;
if ($zip) {
$err = &extract_archive($path, $zip-1);
if (!$err) {
# Refresh whole dir
$refresh = $in{'dir'};
}
}
$info = &file_info_line(&unmake_chroot($refresh), $refresh);
print "<script>\n";
print "opener.document.FileManager.",
"upload_notify(\"$refresh\", \"$info\");\n";
if ($err) {
$err =~ s/\r//g;
$err =~ s/\n/\\n/g;
print "opener.document.FileManager.","upload_error(\"",&text('zip_err', $err),"\");\n";
}
print "close();\n";
print "</script>\n";
}
sub go_chroot
{
if ($chroot ne "/" && $chroot ne "") {
# First build hash of users and groups, which will not be accessible
# after a chroot
local (@u, @g);
setpwent();
while(@u = getpwent()) {
$uid_to_user{$u[2]} = $u[0] if (!defined($uid_to_user{$u[2]}));
$user_to_uid{$u[0]} = $u[2] if (!defined($user_to_uid{$u[0]}));
}
endpwent();
setgrent();
while(@g = getgrent()) {
$gid_to_group{$g[2]} = $g[0] if(!defined($gid_to_group{$g[2]}));
$group_to_gid{$g[0]} = $g[2] if(!defined($group_to_gid{$g[0]}));
}
endgrent();
chroot($chroot) || die("chroot to $chroot failed");
}
}
# make_chroot(dir)
# Converts some real directory to the chroot form
sub make_chroot
{
if ($chroot eq "/") {
return $_[0];
}
elsif ($_[0] eq $chroot) {
return "/";
}
else {
local $rv = $_[0];
if ($rv =~ /^$chroot\//) {
$rv =~ s/^$chroot//;
return $rv;
}
else {
return undef;
}
}
}
# unmake_chroot(dir)
# Converts some chroot'd directory to the real form
sub unmake_chroot
{
if ($chroot eq "/") {
return $_[0];
}
elsif ($_[0] eq "/") {
return $chroot;
}
else {
return $chroot.$_[0];
}
}
# print_content_type()
# Prints the content-type header, with a charset
sub print_content_type
{
if ($userconfig{'nocharset'} || $config{'nocharset'}) {
# Never try to use charset
print "Content-type: text/plain\n\n";
}
else {
$charset = &get_charset();
print "Content-type: text/plain; charset=$charset\n\n";
}
}
1;