# usermin-lib.pl
# Common functions for configuring usermin
# XXX share text with webmin!

do '../web-lib.pl';
&init_config();
require '../ui-lib.pl';
%access = &get_module_acl();
&foreign_require("webmin", "webmin-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;

# 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 = <VERSION>;
close(VERSION);
$version =~ s/\r|\n//g;
return $version;
}

# restart_usermin_miniserv()
# Send a HUP signal to miniserv
sub restart_usermin_miniserv
{
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 = <PID>);
close(PID);
if (!$pid) { &error("Invalid pid file"); }
&kill_logged('HUP', $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'};
return %rv;
}

# get_usermin_theme_info(theme)
sub get_usermin_theme_info
{
local (%tinfo, $o);
&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(<ACL>) {
		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(ACL, ">".&usermin_acl_filename());
print ACL $_[0],": ",join(" ", @{$_[1]}),"\n";
close(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);

# 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', "<tt>uncompress</tt>");
		}
	local $temp = $file =~ /\/([^\/]+)\.Z/i ? &tempname("$1")
						: &tempname();
	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', "<tt>gunzip</tt>");
		}
	local $temp = $file =~ /\/([^\/]+)\.gz/i ? &tempname("$1")
						 : &tempname();
	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 = <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', "<tt>$out</tt>");
		}

	$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(ACL, "> ".&usermin_acl_filename());
		foreach $u (keys %acl) {
			local @mods = @{$acl{$u}};
			if ($u eq 'user') {
				push(@mods, $name);
				@mods = &unique(@mods);
				}
			print ACL "$u: ",join(' ', @mods),"\n";
			}
		close(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', "<tt>$m</tt>");
			}
		}
	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 = &tempname();
	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', "<tt>$m</tt>");
			}
		elsif (!&check_usermin_os_support(\%minfo)) {
			$err = &text('install_eos', "<tt>$m</tt>",
				     $gconfig{'real_os_type'},
				     $gconfig{'real_os_version'});
			}
		elsif (!$minfo{'usermin'}) {
			$err = &text('install_eusermin', "<tt>$m</tt>");
			}
		elsif (!$nodeps) {
			foreach $dep (split(/\s+/, $minfo{'depends'})) {
				if ($dep =~ /^[0-9\.]+$/) {
					if ($dep > $ver) {
						$err = &text('install_ever',
							"<tt>$m</tt>",
							"<tt>$dep</tt>");
						}
					}
				elsif (!-r "$miniserv{'root'}/$dep/module.info"
				       && !$mods{$dep}) {
					$err = &text('install_edep',
					        "<tt>$m</tt>", "<tt>$dep</tt>");
					}
				}
			foreach $dep (split(/\s+/, $minfo{'perldepends'})) {
				eval "use $dep";
				if ($@) {
					$err = &text('install_eperldep',
					     "<tt>$m</tt>", "<tt>$dep</tt>",
					     "/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");
	<PERL> =~ /^#!(\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{'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(ACL, "> ".&usermin_acl_filename());
	foreach $u (keys %acl) {
		local @mods = @{$acl{$u}};
		if ($u eq 'user') {
			push(@mods, @realmods);
			@mods = &unique(@mods);
			}
		print ACL "$u: ",join(' ', @mods),"\n";
		}
	close(ACL);
	}
unlink("$config{'usermin_dir'}/module.infos.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(<USERMODS>) {
	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(USERMODS, ">$config{'usermin_dir'}/usermin.mods");
foreach $u (@{$_[0]}) {
	printf USERMODS "%s:%s:%s\n", $u->[0], $u->[1], join(" ", @{$u->[2]});
	}
close(USERMODS);
}

# get_usermin_miniserv_users()
sub get_usermin_miniserv_users
{
local %miniserv;
&get_usermin_miniserv_config(\%miniserv);
local @rv;
open(USERS, $miniserv{'userfile'});
while(<USERS>) {
	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(USERS, ">$miniserv{'userfile'}");
local $u;
foreach $u (@_) {
	print USERS join(":", $u->{'name'}, $u->{'pass'}, $u->{'sync'},
			      $u->{'cert'}, $u->{'allow'}),"\n";
	}
close(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]);
}

1;

