# usermin-lib.pl # Common functions for configuring usermin # XXX share text with webmin! do '../web-lib.pl'; {'usermin_dir'}&init_config(); do '../ui-lib.pl'; %access = &get_module_acl(); $access{'upgrade'} = 0 if (&is_readonly_mode()); # too hard to fake &foreign_require("webmin", "webmin-lib.pl"); &foreign_require("acl", "acl-lib.pl"); $usermin_miniserv_config = "$config{'usermin_dir'}/miniserv.conf"; $usermin_config = "$config{'usermin_dir'}/config"; $update_host = "www.webmin.com"; $update_port = 80; $update_page = "/uupdates/uupdates.txt"; $standard_usermin_dir = "/etc/usermin"; $latest_rpm = "http://www.webmin.com/download/usermin-latest.noarch.rpm"; $latest_tgz = "http://www.webmin.com/download/usermin-latest.tar.gz"; $default_key_size = 512; $cron_cmd = "$module_config_directory/update.pl"; # get_usermin_miniserv_config(&array) sub get_usermin_miniserv_config { &read_file($usermin_miniserv_config, \%usermin_miniserv_config_cache) if (!%usermin_miniserv_config_cache); %{$_[0]} = %usermin_miniserv_config_cache; } # put_usermin_miniserv_config(&array) sub put_usermin_miniserv_config { %usermin_miniserv_config_cache = %{$_[0]}; &write_file($usermin_miniserv_config, \%usermin_miniserv_config_cache); } # get_usermin_version() sub get_usermin_version { local %miniserv; &get_usermin_miniserv_config(\%miniserv); open(VERSION, "$miniserv{'root'}/version"); local $version = ; close(VERSION); $version =~ s/\r|\n//g; return $version; } # restart_usermin_miniserv() # Send a HUP signal to miniserv sub restart_usermin_miniserv { return undef if (&is_readonly_mode()); local($pid, %miniserv, $addr, $i); &get_usermin_miniserv_config(\%miniserv) || return; $miniserv{'inetd'} && return; open(PID, $miniserv{'pidfile'}) || &error("Failed to open PID file"); chop($pid = ); close(PID); if (!$pid) { &error("Invalid PID file"); } return &kill_logged('HUP', $pid); } # reload_usermin_miniserv() # Sends a USR1 signal to the miniserv process sub reload_usermin_miniserv { return undef if (&is_readonly_mode()); local %miniserv; &get_usermin_miniserv_config(\%miniserv) || return; $miniserv{'inetd'} && return; local($pid, $addr, $i); open(PID, $miniserv{'pidfile'}) || &error("Failed to open PID file"); chop($pid = ); close(PID); if (!$pid) { &error("Invalid PID file"); } return &kill_logged('USR1', $pid); } # get_usermin_config(&array) sub get_usermin_config { &read_file($usermin_config, \%usermin_config_cache) if (!%usermin_config_cache); %{$_[0]} = %usermin_config_cache; } # put_usermin_config(&array) sub put_usermin_config { %usermin_config_cache = %{$_[0]}; &write_file($usermin_config, \%usermin_config_cache); } # list_themes() # Returns an array of all usermin themes sub list_themes { local @rv; local %miniserv; &get_usermin_miniserv_config(\%miniserv); opendir(DIR, $miniserv{'root'}); foreach $m (readdir(DIR)) { next if ($m =~ /^\./); local %tinfo = &get_usermin_theme_info($m); next if (!%tinfo); next if (!&check_usermin_os_support(\%tinfo)); push(@rv, \%tinfo); } closedir(DIR); return @rv; } # list_modules() # Returns a list of all usermin modules sub list_modules { local (@mlist, $m, %miniserv); &get_usermin_miniserv_config(\%miniserv); local %cats; &read_file_cached("$config{'usermin_dir'}/webmin.cats", \%cats); opendir(DIR, $miniserv{'root'}); foreach $m (readdir(DIR)) { local %minfo; if ((%minfo = &get_usermin_module_info($m)) && &check_usermin_os_support(\%minfo)) { $minfo{'realcategory'} = $minfo{'category'}; $minfo{'category'} = $cats{$m} if (defined($cats{$m})); push(@mlist, \%minfo); } } closedir(DIR); @mlist = sort { $a->{'desc'} cmp $b->{'desc'} } @mlist; return @mlist; } # get_usermin_module_info(module, [noclone]) # Returns a hash containg a module name, desc and os_support sub get_usermin_module_info { return () if ($_[0] =~ /^\./); local (%rv, $clone, %miniserv, $o); &get_usermin_miniserv_config(\%miniserv); &read_file("$miniserv{'root'}/$_[0]/module.info", \%rv) || return (); $clone = -l "$miniserv{'root'}/$_[0]"; foreach $o (@lang_order_list) { $rv{"desc"} = $rv{"desc_$o"} if ($rv{"desc_$o"}); } if ($clone && !$_[1] && $config_directory) { $rv{'clone'} = $rv{'desc'}; &read_file("$config{'usermin_dir'}/$_[0]/clone", \%rv); } $rv{'dir'} = $_[0]; $rv{'realcategory'} = $rv{'category'}; # Apply description overrides $rv{'realdesc'} = $rv{'desc'}; local %descs; &read_file_cached("$config{'usermin_dir'}/webmin.descs", \%descs); if ($descs{$_[0]." ".$current_lang}) { $rv{'desc'} = $descs{$_[0]." ".$current_lang}; } elsif ($descs{$_[0]}) { $rv{'desc'} = $descs{$_[0]}; } return %rv; } # get_usermin_theme_info(theme) sub get_usermin_theme_info { local (%tinfo, $o); local %miniserv; &get_usermin_miniserv_config(\%miniserv); &read_file("$miniserv{'root'}/$_[0]/theme.info", \%tinfo) || return (); foreach $o (@lang_order_list) { $tinfo{"desc"} = $rv{"desc_$o"} if ($tinfo{"desc_$o"}); } $tinfo{'dir'} = $_[0]; return %tinfo; } # check_usermin_os_support(&minfo) sub check_usermin_os_support { local $oss = $_[0]->{'os_support'}; return 1 if (!$oss || $oss eq '*'); local %uconfig; &get_usermin_config(\%uconfig); while(1) { local ($os, $ver, $codes); if ($oss =~ /^([^\/\s]+)\/([^\{\s]+)\{([^\}]*)\}\s*(.*)$/) { $os = $1; $ver = $2; $codes = $3; $oss = $4; } elsif ($oss =~ /^([^\/\s]+)\/([^\/\s]+)\s*(.*)$/) { $os = $1; $ver = $2; $oss = $3; } elsif ($oss =~ /^([^\{\s]+)\{([^\}]*)\}\s*(.*)$/) { $os = $1; $codes = $2; $oss = $3; } elsif ($oss =~ /^\{([^\}]*)\}\s*(.*)$/) { $codes = $1; $oss = $2; } elsif ($oss =~ /^(\S+)\s*(.*)$/) { $os = $1; $oss = $2; } else { last; } next if ($os && !($os eq $uconfig{'os_type'} || $uconfig{'os_type'} =~ /^(\S+)-(\S+)$/ && $os eq "*-$2")); next if ($ver && $ver ne $uconfig{'os_version'}); next if ($codes && !eval $codes); return 1; } return 0; } # read_usermin_acl(&array, &array) # Reads the acl file into the given associative arrays sub read_usermin_acl { local($user, $_, @mods); if (!defined(%usermin_acl_hash_cache)) { open(ACL, &usermin_acl_filename()); while() { if (/^(\S+):\s*(.*)/) { local(@mods); $user = $1; @mods = split(/\s+/, $2); foreach $m (@mods) { $usermin_acl_hash_cache{$user,$m}++; } $usermin_acl_array_cache{$user} = \@mods; } } close(ACL); } if ($_[0]) { %{$_[0]} = %usermin_acl_hash_cache; } if ($_[1]) { %{$_[1]} = %usermin_acl_array_cache; } } # usermin_acl_filename() # Returns the file containing the webmin ACL sub usermin_acl_filename { return "$config{'usermin_dir'}/webmin.acl"; } # save_usermin_acl(user, &modules) sub save_usermin_acl { &open_tempfile(ACL, ">".&usermin_acl_filename()); &print_tempfile(ACL, $_[0],": ",join(" ", @{$_[1]}),"\n"); &close_tempfile(ACL); } # install_usermin_module(file, unlink, nodeps) # Installs a usermin module or theme, and returns either an error message # or references to three arrays for descriptions, directories and sizes. # On success or failure, the file is deleted if the unlink parameter is set. sub install_usermin_module { local ($file, $need_unlink, $nodeps) = @_; local (@mdescs, @mdirs, @msizes); if (&is_readonly_mode()) { return "Module installs are not allowed in readonly mode"; } # Uncompress the module file if needed open(MFILE, $file); read(MFILE, $two, 2); close(MFILE); if ($two eq "\037\235") { if (!&has_command("uncompress")) { unlink($file) if ($need_unlink); return &text('install_ecomp', "uncompress"); } local $temp = $file =~ /\/([^\/]+)\.Z/i ? &transname("$1") : &transname(); local $out = `uncompress -c "$file" 2>&1 >$temp`; unlink($file) if ($need_unlink); if ($?) { unlink($temp); return &text('install_ecomp2', $out); } $file = $temp; $need_unlink = 1; } elsif ($two eq "\037\213") { if (!&has_command("gunzip")) { unlink($file) if ($need_unlink); return &text('install_egzip', "gunzip"); } local $temp = $file =~ /\/([^\/]+)\.gz/i ? &transname("$1") : &transname(); local $out = `gunzip -c "$file" 2>&1 >$temp`; unlink($file) if ($need_unlink); if ($?) { unlink($temp); return &text('install_egzip2', $out); } $file = $temp; $need_unlink = 1; } local %miniserv; &get_usermin_miniserv_config(\%miniserv); # Check if this is an RPM usermin module or theme local ($type, $redirect_to); open(TYPE, "../install-type"); chop($type = ); close(TYPE); if ($type eq 'rpm' && $file =~ /\.rpm$/i && ($out = `rpm -qp $file 2>/dev/null`)) { # Looks like an RPM of some kind, hopefully an RPM usermin module # or theme local ($out, %minfo, %tinfo); if ($out !~ /^(wbm|wbt)-([^\s\-]+)/) { unlink($file) if ($need_unlink); return $text{'install_erpm'}; } $redirect_to = $name = $2; $out = &backquote_logged("rpm -U \"$file\" 2>&1"); if ($?) { unlink($file) if ($need_unlink); return &text('install_eirpm', "$out"); } $mdirs[0] = "$miniserv{'root'}/$name"; if (%minfo = &get_usermin_module_info($name)) { # Get the new module info $mdescs[0] = $minfo{'desc'}; $msizes[0] = &disk_usage_kb($mdirs[0]); # Update the ACL for the usermin user local %acl; &read_usermin_acl(undef, \%acl); &open_tempfile(ACL, "> ".&usermin_acl_filename()); foreach $u (keys %acl) { local @mods = @{$acl{$u}}; if ($u eq 'user') { push(@mods, $name); @mods = &unique(@mods); } &print_tempfile(ACL, "$u: ",join(' ', @mods),"\n"); } &close_tempfile(ACL); &webmin_log("install", undef, $name, { 'desc' => $mdescs[0] }); } elsif (%tinfo = &get_usermin_theme_info($name)) { # Get the theme info $mdescs[0] = $tinfo{'desc'}; $msizes[0] = &disk_usage_kb($mdirs[0]); &webmin_log("tinstall", undef, $name, { 'desc' => $mdescs[0] }); } else { unlink($file) if ($need_unlink); return $text{'install_eneither'}; } } else { # Check if this is a valid module (a tar file of multiple module or # theme directories) local (%mods, %hasfile); local $tar = `tar tf "$file" 2>&1`; if ($?) { unlink($file) if ($need_unlink); return &text('install_etar', $tar); } foreach $f (split(/\n/, $tar)) { if ($f =~ /^\.\/([^\/]+)\/(.*)$/ || $f =~ /^([^\/]+)\/(.*)$/) { $redirect_to = $1 if (!$redirect_to); $mods{$1}++; $hasfile{$1,$2}++; } } foreach $m (keys %mods) { if (!$hasfile{$m,"module.info"} && !$hasfile{$m,"theme.info"}) { unlink($file) if ($need_unlink); return &text('install_einfo', "$m"); } } if (!%mods) { unlink($file) if ($need_unlink); return $text{'install_enone'}; } # Get the module.info files to check dependancies local $ver = &get_usermin_version(); local $tmpdir = &transname(); mkdir($tmpdir, 0700); local $err; local @realmods; foreach $m (keys %mods) { next if (!$hasfile{$m,"module.info"}); push(@realmods, $m); local %minfo; system("cd $tmpdir ; tar xf \"$file\" $m/module.info ./$m/module.info >/dev/null 2>&1"); if (!&read_file("$tmpdir/$m/module.info", \%minfo)) { $err = &text('install_einfo', "$m"); } elsif (!&check_usermin_os_support(\%minfo)) { $err = &text('install_eos', "$m", $gconfig{'real_os_type'}, $gconfig{'real_os_version'}); } elsif (!$minfo{'usermin'}) { $err = &text('install_eusermin', "$m"); } elsif (!$nodeps) { local $deps = $minfo{'usermin_depends'} || $minfo{'depends'}; foreach $dep (split(/\s+/, $minfo{'depends'})) { if ($dep =~ /^[0-9\.]+$/) { if ($dep > $ver) { $err = &text('install_ever', "$m", "$dep"); } } elsif (!-r "$miniserv{'root'}/$dep/module.info" && !$mods{$dep}) { $err = &text('install_edep', "$m", "$dep"); } } foreach $dep (split(/\s+/, $minfo{'perldepends'})) { eval "use $dep"; if ($@) { $err = &text('install_eperldep', "$m", "$dep", "/cpan/download.cgi?source=3&cpan=$dep"); } } } last if ($err); } system("rm -rf $tmpdir >/dev/null 2>&1"); if ($err) { unlink($file) if ($need_unlink); return $err; } # Delete modules or themes being replaced foreach $m (@realmods) { system("rm -rf '$miniserv{'root'}/$m' 2>&1 >/dev/null") if ($m ne 'webmin'); } # Extract all the modules and update perl path and ownership local $out = `cd $miniserv{'root'} ; tar xf "$file" 2>&1 >/dev/null`; if ($?) { unlink($file) if ($need_unlink); return &text('install_eextract', $out); } if ($need_unlink) { unlink($file); } local $perl; open(PERL, "$miniserv{'root'}/miniserv.pl"); =~ /^#!(\S+)/; $perl = $1; close(PERL); local @st = stat($0); foreach $moddir (keys %mods) { local $pwd = "$miniserv{'root'}/$moddir"; if ($hasfile{$moddir,"module.info"}) { local %minfo = &get_usermin_module_info($moddir); push(@mdescs, $minfo{'desc'}); push(@mdirs, $pwd); push(@msizes, &disk_usage_kb($pwd)); &webmin_log("install", undef, $moddir, { 'desc' => $minfo{'desc'} }); } else { local %tinfo = &get_usermin_theme_info($moddir); &read_file("theme.info", \%tinfo); push(@mdescs, $tinfo{'desc'}); push(@mdirs, $pwd); push(@msizes, &disk_usage_kb($pwd)); &webmin_log("tinstall", undef, $moddir, { 'desc' => $tinfo{'desc'} }); } system("(find $pwd -name '*.cgi' ; find $pwd -name '*.pl') 2>/dev/null | $perl $miniserv{'root'}/perlpath.pl $perl -"); system("chown -R $st[4]:$st[5] $pwd"); } # Copy appropriate config file from modules to /etc/webmin local %ugconfig; &get_usermin_config(\%ugconfig); system("$perl $miniserv{'root'}/copyconfig.pl '$ugconfig{'os_type'}/$ugconfig{'real_os_type'}' '$ugconfig{'os_version'}/$ugconfig{'real_os_version'}' $miniserv{'root'} $config{'usermin_dir'} ".join(' ', @realmods)); # Update ACL for this user so they can access the new modules local %acl; &read_usermin_acl(undef, \%acl); &open_tempfile(ACL, "> ".&usermin_acl_filename()); foreach $u (keys %acl) { local @mods = @{$acl{$u}}; if ($u eq 'user') { push(@mods, @realmods); @mods = &unique(@mods); } &print_tempfile(ACL, "$u: ",join(' ', @mods),"\n"); } &close_tempfile(ACL); } &flush_modules_cache(); return [ \@mdescs, \@mdirs, \@msizes ]; } # list_usermin_usermods() # Returns the list of additional module restrictions for usermin sub list_usermin_usermods { local @rv; open(USERMODS, "$config{'usermin_dir'}/usermin.mods"); while() { if (/^([^:]+):(\+|-|):(.*)/) { push(@rv, [ $1, $2, [ split(/\s+/, $3) ] ]); } } close(USERMODS); return @rv; } # save_usermin_usermods(&usermods) # Saves the list of additional module restrictions sub save_usermin_usermods { &open_tempfile(USERMODS, ">$config{'usermin_dir'}/usermin.mods"); foreach $u (@{$_[0]}) { &print_tempfile(USERMODS, join(":", $u->[0], $u->[1], join(" ", @{$u->[2]})),"\n"); } &close_tempfile(USERMODS); } # get_usermin_miniserv_users() sub get_usermin_miniserv_users { local %miniserv; &get_usermin_miniserv_config(\%miniserv); local @rv; open(USERS, $miniserv{'userfile'}); while() { s/\r|\n//g; local @u = split(/:/, $_); push(@rv, { 'name' => $u[0], 'pass' => $u[1], 'sync' => $u[2], 'cert' => $u[3], 'allow' => $u[4] }); } close(USERS); return @rv; } # save_usermin_miniserv_users(user, ...) sub save_usermin_miniserv_users { local %miniserv; &get_usermin_miniserv_config(\%miniserv); &open_tempfile(USERS, ">$miniserv{'userfile'}"); local $u; foreach $u (@_) { &print_tempfile(USERS, join(":", $u->{'name'}, $u->{'pass'}, $u->{'sync'}, $u->{'cert'}, $u->{'allow'}),"\n"); } &close_tempfile(USERS); } # can_use_module(module) sub can_use_module { return 1 if ($access{'mods'} eq '*'); local @mods = split(/\s+/, $access{'mods'}); return &indexof($_[0], @mods) >= 0; } # get_usermin_base_version() # Gets the usermin version, rounded to the nearest .01 sub get_usermin_base_version { return &base_version(&get_usermin_version()); } # base_version() # Rounds a version number to the nearest .01 sub base_version { return sprintf("%.2f0", $_[0]); } # find_cron_job(\@jobs) sub find_cron_job { local ($job) = grep { $_->{'user'} eq 'root' && $_->{'command'} eq $cron_cmd } @{$_[0]}; return $job; } # delete_usermin_module(module, [delete-acls]) # Deletes some usermin module, clone or theme, and return a description of # the thing deleted. sub delete_usermin_module { local $m = $_[0]; return undef if (!$m); local %minfo = &get_usermin_module_info($m); %minfo = &get_usermin_theme_info($m) if (!%minfo); return undef if (!%minfo); local ($mdesc, @aclrm); @aclrm = ( $m ) if ($_[1]); local %miniserv; &get_usermin_miniserv_config(\%miniserv); local %ugconfig; &get_usermin_config(\%uconfig); local $mdir = "$miniserv{'root'}/$m"; local $cdir = "$config{'usermin_dir'}/$m"; if ($minfo{'clone'}) { # Deleting a clone local %cinfo; &read_file("$config{'usermin_dir'}/$m/clone", \%cinfo); &unlink_logged($mdir); &system_logged("rm -rf ".quotemeta($cdir)); if ($ugconfig{'theme'}) { &unlink_logged("$miniserv{'root'}/$ugconfig{'theme'}/$m"); } $mdesc = &webmin::text('delete_desc1', $minfo{'desc'}, $minfo{'clone'}); } else { # Delete any clones of this module local @clones; local @mst = stat($mdir); opendir(DIR, $miniserv{'root'}); local $l; foreach $l (readdir(DIR)) { @lst = stat("$miniserv{'root'}/$l"); if (-l "$miniserv{'root'}/$l" && $lst[1] == $mst[1]) { &unlink_logged("$miniserv{'root'}/$l"); &system_logged("rm -rf $config{'usermin_dir'}/$l"); push(@clones, $l); } } closedir(DIR); open(TYPE, "$mdir/install-type"); chop($type = ); close(TYPE); # Deleting the real module local $size = &disk_usage_kb($mdir); $mdesc = &webmin::text('delete_desc2', "$minfo{'desc'}", "$mdir", $size); if ($type eq 'rpm') { # This module was installed from an RPM .. rpm -e it &system_logged("rpm -e ubm-$m"); } else { # Module was installed from a .wbm file .. just rm it &system_logged("rm -rf ".quotemeta($mdir)); } } &webmin_log("delete", undef, $m, { 'desc' => $minfo{'desc'} }); return $mdesc; } # flush_modules_cache() # Forces a rebuild of the Usermin module cache sub flush_modules_cache { &unlink_file("$config{'usermin_dir'}/module.infos.cache"); } # stop_usermin() # Kills the running Usermin server process, returning undef on sucess or an # error message on failure. sub stop_usermin { local %miniserv; &get_usermin_miniserv_config(\%miniserv); local $pid; if (open(PID, $miniserv{'pidfile'}) && ($pid = int())) { &kill_logged('TERM', $pid) || return &text('stop_ekill', $!); close(PID); } else { return $text{'stop_efile'}; } return undef; } # start_usermin() # Starts the Usermin server process sub start_usermin { &system_logged("$config{'usermin_dir'}/start >/dev/null 2>&1 ); close(MODE); } else { if ($miniserv{'root'} eq "/usr/libexec/usermin") { $mode = "rpm"; } elsif ($miniserv{'root'} eq "/usr/share/usermin") { $mode = "deb"; } else { $mode = undef; } } return $mode; } 1;