diff --git a/web-lib-funcs.pl b/web-lib-funcs.pl index ef25fa6de..57f1006b8 100755 --- a/web-lib-funcs.pl +++ b/web-lib-funcs.pl @@ -538,9 +538,12 @@ else { my $mkdirerr; while($tries++ < 10) { my @st = lstat($tmp_dir); - last if ($st[4] == $< && (-d _) && - ($st[2] & 0777) == 0755); if (@st) { + my $mode = $st[2] & 07777; + # Accept Webmin-private dirs and standard shared + last if ($st[4] == $< && (-d _) && + ((($mode & 0777) == 0755) || + $mode == 01777)); unlink($tmp_dir) || rmdir($tmp_dir) || system("/bin/rm -rf ". quotemeta($tmp_dir)); @@ -559,14 +562,15 @@ else { $tmp_dir.$mkdirerr); } } - # If running as root, check parent dir (usually /tmp) to make sure it's - # world-writable and owned by root + # If running as root, check parent dir (usually /tmp) to make sure it + # is searchable by group and others. my $tmp_parent = $tmp_dir; $tmp_parent =~ s/\/[^\/]+$//; if ($tmp_parent eq "/tmp") { my @st = stat($tmp_parent); - if (($st[2] & 0555) != 0555) { - &error("Base temp directory $tmp_parent is not world readable and listable"); + if (($st[2] & 0011) != 0011) { + &error("Base temp directory $tmp_parent must be ". + "group and other executable"); } } } diff --git a/webmin/change_advanced.cgi b/webmin/change_advanced.cgi index e9bf765b4..1c8cf8877 100755 --- a/webmin/change_advanced.cgi +++ b/webmin/change_advanced.cgi @@ -11,9 +11,10 @@ if ($in{'tempdir_def'}) { delete($gconfig{'tempdir'}); } else { - -d $in{'tempdir'} || &error($text{'advanced_etemp'}); &allowed_temp_dir($in{'tempdir'}) || &error(&text('advanced_etempallowed', $in{'tempdir'})); + $in{'tempdir'} = &setup_advanced_temp_dir( + $in{'tempdir'}, $text{'advanced_etemp'}); $gconfig{'tempdir'} = $in{'tempdir'}; } @@ -33,9 +34,10 @@ for($i=0; defined($tmod = $in{'tmod_'.$i}); $i++) { next if (!$tmod); $tdir = $in{'tdir_'.$i}; %minfo = &get_module_info($tmod); - -d $tdir || &error(&text('advanced_etdir', $minfo{'desc'})); &allowed_temp_dir($tdir) || - &error(&text('advanced_etempallowed', $in{'tempdir'})); + &error(&text('advanced_etempallowed', $tdir)); + $tdir = &setup_advanced_temp_dir( + $tdir, &text('advanced_etdir', $minfo{'desc'})); push(@tdirs, [ $tmod, $tdir ]); } &save_tempdirs(\%gconfig, \@tdirs); @@ -141,3 +143,114 @@ sub allowed_temp_dir my ($t) = @_; return $t eq "/" || $t =~ /^\/[^\/]+\/?$/ ? 0 : 1; } + +# Create a configured Webmin temp directory if needed, and validate existing +# path components without changing permissions on directories the user made. +sub setup_advanced_temp_dir +{ +my ($dir, $missing_error) = @_; +$dir =~ /\S/ || &error($missing_error); +$dir =~ s/\/+$// if ($dir ne "/"); +$dir =~ /\S/ || &error($missing_error); +if (&advanced_temp_dir_is_windows($dir)) { + if (!-d $dir) { + &make_dir($dir, &advanced_temp_dir_perms(), 1) || + &error(&text('advanced_etempmkdir', $dir, "$!")); + -d $dir || + &error(&text('advanced_etempmkdir', $dir, "$!")); + } + return $dir; + } +if ($dir =~ /^\//) { + my $sdir = &simplify_path($dir); + defined($sdir) || &error($missing_error); + $dir = $sdir; + } +&allowed_temp_dir($dir) || + &error(&text('advanced_etempallowed', $dir)); + +# Walk the path so existing parents are checked, while missing parents and +# the final temp directory are created with Webmin's expected mode. +my $path = $dir =~ /^\// ? "/" : ""; +foreach my $part (split(/\/+/, $dir)) { + next if ($part eq ""); + $path = $path eq "/" ? "/$part" : + $path eq "" ? $part : "$path/$part"; + my $final = $path eq $dir; + if (-e $path || -l $path) { + -d $path || + &error(&text('advanced_etempparent', $path)); + if ($final) { + &advanced_temp_dir_perms_ok($path) || + &error(&text('advanced_etempperms', $path, + &advanced_temp_dir_allowed_perms_text())); + } + else { + &advanced_temp_parent_dir_perms_ok($path) || + &error(&text('advanced_etempparentperms', + $path)); + } + next; + } + &make_dir($path, &advanced_temp_dir_perms()) || + &error(&text('advanced_etempmkdir', $path, "$!")); + &advanced_temp_dir_perms_ok($path) || + &error(&text('advanced_etempchmod', $path, + &advanced_temp_dir_perms_text(), "$!")); + } +&advanced_temp_dir_perms_ok($dir) || + &error(&text('advanced_etempperms', $dir, + &advanced_temp_dir_allowed_perms_text())); +return $dir; +} + +# Permissions used for newly created Webmin temp directories. +sub advanced_temp_dir_perms +{ +return 0755; +} + +# Octal text form used in error messages. +sub advanced_temp_dir_perms_text +{ +return sprintf("%04o", &advanced_temp_dir_perms()); +} + +# Existing final temp dirs may be Webmin-private, or standard shared temp dirs. +sub advanced_temp_dir_allowed_perms_text +{ +return "".&advanced_temp_dir_perms_text()." or 1777"; +} + +# Check the final configured temp directory. Webmin creates 0755 directories, +# but also permits standard shared temp directories like /var/tmp. +sub advanced_temp_dir_perms_ok +{ +my ($dir) = @_; +my @st = lstat($dir); +return 0 if (!@st || !-d _); +return 0 if ($st[4] != $<); +my $mode = $st[2] & 07777; +return 1 if (($mode & 0777) == &advanced_temp_dir_perms()); +return 1 if ($mode == 01777); +return 0; +} + +# Existing parents only need to be searchable by group and others. The final +# temp directory itself is checked more strictly above. +sub advanced_temp_parent_dir_perms_ok +{ +my ($dir) = @_; +my @st = stat($dir); +return 0 if (!@st || !-d _); +my $mode = $st[2] & 07777; +return 0 if (($mode & 0011) != 0011); +return 1; +} + +# Windows temp directories are only checked for existence and created if needed. +sub advanced_temp_dir_is_windows +{ +my ($dir) = @_; +return $gconfig{'os_type'} eq 'windows' || $dir =~ /^[a-z]:/i; +} diff --git a/webmin/lang/en b/webmin/lang/en index 9f29358d8..3ed7c3cda 100644 --- a/webmin/lang/en +++ b/webmin/lang/en @@ -942,6 +942,11 @@ advanced_err=Failed to save advanced options advanced_etemp=Missing or non-existant temporary files directory advanced_etdir=Missing or non-existant temporary files directory for $1 advanced_etempallowed=Temporary files directory $1 is a system directory +advanced_etempmkdir=Failed to create temporary files directory $1 : $2 +advanced_etempparent=Cannot create temporary files directory because $1 exists and is not a directory +advanced_etempparentperms=Parent directory $1 must be group and other executable. +advanced_etempchmod=Failed to set permissions on temporary files directory $1 to $2 : $3 +advanced_etempperms=Temporary files directory $1 already exists with incorrect ownership or permissions, and must be owned by root with mode $2 before saving. advanced_pass_desc=Make password available to Usermin programs? advanced_pass_help=Does not work when session authentication is enabled advanced_tempmods=Per-module temporary directories