# acl-lib.pl
# Library for editing webmin users, passwords and access rights

do '../web-lib.pl';
&init_config();
%access = &get_module_acl();

# list_users()
# Returns a list of hashes containing user details
sub list_users
{
local(%miniserv, $_, @rv, %acl);
&read_acl(undef, \%acl);
&get_miniserv_config(\%miniserv);
open(PWFILE, $miniserv{'userfile'});
while(<PWFILE>) {
	s/\r|\n//g;
	local @user = split(/:/, $_);
	if (@user) {
		local(%user);
		$user{'name'} = $user[0];
		$user{'pass'} = $user[1];
		$user{'sync'} = $user[2];
		$user{'cert'} = $user[3];
		if ($user[4] =~ /^(allow|deny)\s+(.*)/) {
			$user{$1} = $2;
			}
		$user{'modules'} = $acl{$user[0]};
		$user{'lang'} = $gconfig{"lang_$user[0]"};
		$user{'notabs'} = $gconfig{"notabs_$user[0]"};
		$user{'skill'} = $gconfig{"skill_$user[0]"};
		$user{'risk'} = $gconfig{"risk_$user[0]"};
		$user{'theme'} = $gconfig{"theme_$user[0]"};
		$user{'ownmods'} = [ split(/\s+/,
					   $gconfig{"ownmods_$user[0]"}) ];
		push(@rv, \%user);
		}
	}
close(PWFILE);
return @rv;
}

# list_groups()
# Returns a list of hashes, one per group.
# Group membership is stored in /etc/webmin/webmin.groups, and other attributes
# in the config file.
sub list_groups
{
local @rv;
open(GROUPS, "$config_directory/webmin.groups");
while(<GROUPS>) {
	s/\r|\n//g;
	local @g = split(/:/, $_);
	local $group = { 'name' => $g[0],
			 'members' => [ split(/\s+/, $g[1]) ],
			 'modules' => [ split(/\s+/, $g[2]) ],
			 'desc' => $g[3],
			 'ownmods' => [ split(/\s+/, $g[4]) ] };
	push(@rv, $group);
	}
close(GROUPS);
return @rv;
}

# list_modules()
# Returns a list of the dirs of all modules available on this system
sub list_modules
{
return map { $_->{'dir'} } &list_module_infos();
}

# list_module_infos()
# Returns a list of the details of all  modules available on this system
sub list_module_infos
{
local @mods = grep { &check_os_support($_) } &get_all_module_infos();
return sort { $a->{'desc'} cmp $b->{'desc'} } @mods;
}

# create_user(&details, clone)
sub create_user
{
local(%user, %miniserv, @mods);
%user = %{$_[0]};

&lock_file($ENV{'MINISERV_CONFIG'});
&get_miniserv_config(\%miniserv);
if ($user{'theme'}) {
	$miniserv{"preroot_".$user{'name'}} = $user{'theme'};
	}
elsif (defined($user{'theme'})) {
	$miniserv{"preroot_".$user{'name'}} = "";
	}
&put_miniserv_config(\%miniserv);
&unlock_file($ENV{'MINISERV_CONFIG'});

&lock_file($miniserv{'userfile'});
open(PWFILE, ">> $miniserv{'userfile'}");
print PWFILE "$user{'name'}:$user{'pass'}:$user{'sync'}:$user{'cert'}:",
	$user{'allow'} ? "allow $user{'allow'}" :
	$user{'deny'} ? "deny $user{'deny'}" : "","\n";
close(PWFILE);
&unlock_file($miniserv{'userfile'});

&lock_file(&acl_filename());
@mods = &list_modules();
open(ACL, ">> ".&acl_filename());
print ACL &acl_line(\%user, \@mods);
close(ACL);
&unlock_file(&acl_filename());

delete($gconfig{"lang_".$user{'name'}});
$gconfig{"lang_".$user{'name'}} = $user{'lang'} if ($user{'lang'});
delete($gconfig{"notabs_".$user{'name'}});
$gconfig{"notabs_".$user{'name'}} = $user{'notabs'} if ($user{'notabs'});
delete($gconfig{"skill_".$user{'name'}});
$gconfig{"skill_".$user{'name'}} = $user{'skill'} if ($user{'skill'});
delete($gconfig{"risk_".$user{'name'}});
$gconfig{"risk_".$user{'name'}} = $user{'risk'} if ($user{'risk'});
delete($gconfig{"ownmods_".$user{'name'}});
$gconfig{"ownmods_".$user{'name'}} = join(" ", @{$user{'ownmods'}})
	if (@{$user{'ownmods'}});
delete($gconfig{"theme_".$user{'name'}});
$gconfig{"theme_".$user{'name'}} = $user{'theme'} if (defined($user{'theme'}));
&write_file("$config_directory/config", \%gconfig);

if ($_[1]) {
	foreach $m ("", @mods) {
		local $file = "$config_directory/$m/$_[1].acl";
		local $dest = "$config_directory/$m/$user{'name'}.acl";
		if (-r $file) {
			local %macl;
			&read_file($file, \%macl);
			&write_file($dest, \%macl);
			}
		}
	}
}

# modify_user(name, &details)
sub modify_user
{
local(%user, %miniserv, @pwfile, @acl, @mods, $_, $m);
%user = %{$_[1]};

&lock_file($ENV{'MINISERV_CONFIG'});
&get_miniserv_config(\%miniserv);
if ($user{'theme'}) {
	$miniserv{"preroot_".$user{'name'}} = $user{'theme'};
	}
elsif (defined($user{'theme'})) {
	$miniserv{"preroot_".$user{'name'}} = "";
	}
else {
	delete($miniserv{"preroot_".$_[0]});
	}
&put_miniserv_config(\%miniserv);
&unlock_file($ENV{'MINISERV_CONFIG'});

&lock_file($miniserv{'userfile'});
open(PWFILE, $miniserv{'userfile'});
@pwfile = <PWFILE>;
close(PWFILE);
open(PWFILE, "> $miniserv{'userfile'}");
foreach (@pwfile) {
	if (/^([^:]+):/ && $1 eq $_[0]) {
		print PWFILE "$user{'name'}:$user{'pass'}:",
			     "$user{'sync'}:$user{'cert'}:",
			$user{'allow'} ? "allow $user{'allow'}" :
			$user{'deny'} ? "deny $user{'deny'}" : "","\n";
		}
	else { print PWFILE $_; }
	}
close(PWFILE);
&unlock_file($miniserv{'userfile'});

&lock_file(&acl_filename());
@mods = &list_modules();
open(ACL, &acl_filename());
@acl = <ACL>;
close(ACL);
open(ACL, "> ".&acl_filename());
foreach (@acl) {
	if (/^(\S+):/ && $1 eq $_[0]) {
		print ACL &acl_line($_[1], \@mods);
		}
	else { print ACL $_; }
	}
close(ACL);
&unlock_file(&acl_filename());

delete($gconfig{"lang_".$_[0]});
$gconfig{"lang_".$user{'name'}} = $user{'lang'} if ($user{'lang'});
delete($gconfig{"notabs_".$_[0]});
$gconfig{"notabs_".$user{'name'}} = $user{'notabs'} if ($user{'notabs'});
delete($gconfig{"skill_".$_[0]});
$gconfig{"skill_".$user{'name'}} = $user{'skill'} if ($user{'skill'});
delete($gconfig{"risk_".$_[0]});
$gconfig{"risk_".$user{'name'}} = $user{'risk'} if ($user{'risk'});
delete($gconfig{"ownmods_".$_[0]});
$gconfig{"ownmods_".$user{'name'}} = join(" ", @{$user{'ownmods'}})
	if (@{$user{'ownmods'}});
delete($gconfig{"theme_".$_[0]});
$gconfig{"theme_".$user{'name'}} = $user{'theme'} if (defined($user{'theme'}));
&write_file("$config_directory/config", \%gconfig);

foreach $m (@mods, "") {
	local $file = "$config_directory/$m/$_[0].acl";
	if (-r $file) {
		rename($file, "$config_directory/$m/$user{'name'}.acl");
		}
	}

if ($miniserv{'session'} && $_[0] ne $user{'name'}) {
	# Modify all sessions for the renamed user
	&open_session_db(\%miniserv);
	foreach $s (keys %sessiondb) {
		local ($u,$t) = split(/\s+/, $sessiondb{$s});
		if ($u eq $_[0]) {
			$sessiondb{$s} = "$user{'name'} $t";
			}
		}
	dbmclose(%sessiondb);
	}
}

# delete_user(name)
# Delete some user from the ACL and password files
sub delete_user
{
local($_, @pwfile, @acl, %miniserv);

&lock_file($ENV{'MINISERV_CONFIG'});
&get_miniserv_config(\%miniserv);
delete($miniserv{"preroot_".$_[0]});
&put_miniserv_config(\%miniserv);
&unlock_file($ENV{'MINISERV_CONFIG'});

&lock_file($miniserv{'userfile'});
open(PWFILE, $miniserv{'userfile'});
@pwfile = <PWFILE>;
close(PWFILE);
open(PWFILE, "> $miniserv{'userfile'}");
foreach (@pwfile) {
	if (!/^([^:]+):/ || $1 ne $_[0]) { print PWFILE $_; }
	}
close(PWFILE);
&unlock_file($miniserv{'userfile'});

&lock_file(&acl_filename());
open(ACL, &acl_filename());
@acl = <ACL>;
close(ACL);
open(ACL, "> ".&acl_filename());
foreach (@acl) {
	if (!/^([^:]+):/ || $1 ne $_[0]) { print ACL $_; }
	}
close(ACL);
&unlock_file(&acl_filename());

delete($gconfig{"lang_".$_[0]});
delete($gconfig{"notabs_".$_[0]});
delete($gconfig{"skill_".$_[0]});
delete($gconfig{"risk_".$_[0]});
delete($gconfig{"ownmods_".$_[0]});
delete($gconfig{"theme_".$_[0]});
&write_file("$config_directory/config", \%gconfig);

unlink(map { "$config_directory/$_/$_[0].acl" } &list_modules());

if ($miniserv{'session'}) {
	# Delete all sessions for the deleted user
	&open_session_db(\%miniserv);
	foreach $s (keys %sessiondb) {
		local ($u,$t) = split(/\s+/, $sessiondb{$s});
		if ($u eq $_[0]) {
			delete($sessiondb{$s});
			}
		}
	dbmclose(%sessiondb);
	}
}

# create_group(&group, [clone])
# Add a new webmin group
sub create_group
{
&lock_file("$config_directory/webmin.groups");
open(GROUP, ">>$config_directory/webmin.groups");
print GROUP &group_line($_[0]),"\n";
close(GROUP);
&unlock_file("$config_directory/webmin.groups");

if ($_[1]) {
	foreach $m ("", &list_modules()) {
		local $file = "$config_directory/$m/$_[1].acl";
		local $dest = "$config_directory/$m/$_[0]->{'name'}.acl";
		if (-r $file) {
			local %macl;
			&read_file($file, \%macl);
			&write_file($dest, \%macl);
			}
		}
	}
}

# modify_group(name, &group)
# Update a webmin group
sub modify_group
{
&lock_file("$config_directory/webmin.groups");
local $lref = &read_file_lines("$config_directory/webmin.groups");
foreach $l (@$lref) {
	if ($l =~ /^([^:]+):/ && $1 eq $_[0]) {
		$l = &group_line($_[1]);
		}
	}
&flush_file_lines();
&unlock_file("$config_directory/webmin.groups");

foreach $m (@{$_[1]->{'modules'}}, "") {
	local $file = "$config_directory/$m/$_[0].acl";
	if (-r $file) {
		rename($file, "$config_directory/$m/$_[1]->{'name'}.acl");
		}
	}
}

# delete_group(name)
# Delete a webmin group
sub delete_group
{
&lock_file("$config_directory/webmin.groups");
local $lref = &read_file_lines("$config_directory/webmin.groups");
@$lref = grep { !/^([^:]+):/ || $1 ne $_[0] } @$lref;
&flush_file_lines();
&unlock_file("$config_directory/webmin.groups");
unlink(map { "$config_directory/$_/$_[0].acl" } &list_modules());
}

# group_line(&group)
sub group_line
{
return join(":", $_[0]->{'name'},
		 join(" ", @{$_[0]->{'members'}}),
		 join(" ", @{$_[0]->{'modules'}}),
		 $_[0]->{'desc'},
		 join(" ", @{$_[0]->{'ownmods'}}) );
}

# acl_line(&user, &allmodules)
# Internal function to generate an ACL file line
sub acl_line
{
local(%user);
%user = %{$_[0]};
return "$user{'name'}: ".join(' ', @{$user{'modules'}})."\n";
}

# can_edit_user(user, [&groups])
sub can_edit_user
{
return 1 if ($access{'users'} eq '*');
if ($access{'users'} eq '~') {
	return $base_remote_user eq $_[0];
	}
local $u;
local $glist = $_[1] ? $_[1] : [ &list_groups() ];
foreach $u (split(/\s+/, $access{'users'})) {
	if ($u =~ /^_(\S+)$/) {
		foreach $g (@$glist) {
			return 1 if ($g->{'name'} eq $1 &&
				     &indexof($_[0], @{$g->{'members'}}) >= 0);
			}
		}
	else {
		return 1 if ($u eq $_[0]);
		}
	}
return 0;
}

# open_session_db(\%miniserv)
sub open_session_db
{
local $sfile = $_[0]->{'sessiondb'} ? $_[0]->{'sessiondb'} :
	       $_[0]->{'pidfile'} =~ /^(.*)\/[^\/]+$/ ? "$1/sessiondb"
						      : return;
eval "use SDBM_File";
dbmopen(%sessiondb, $sfile, 0700);
eval { $sessiondb{'1111111111'} = 'foo bar' };
if ($@) {
	dbmclose(%sessiondb);
	eval "use NDBM_File";
	dbmopen(%sessiondb, $sfile, 0700);
	}
}

# update_members(&allusers, &allgroups, &modules, &members)
# Update the members users and groups of some group
sub update_members
{
foreach $m (@{$_[3]}) {
	local ($u) = grep { $_->{'name'} eq $m } @{$_[0]};
	if ($u) {
		$u->{'modules'} = [ @{$_[2]}, @{$u->{'ownmods'}} ];
		&modify_user($u->{'name'}, $u);
		}
	local ($g) = grep { $_->{'name'} eq $m } @{$_[1]};
	if ($g) {
		$g->{'modules'} = [ @{$_[2]}, @{$g->{'ownmods'}} ];
		&modify_group($g->{'name'}, $g);
		&update_members($_[0], $_[1], $_[2], $g->{'members'});
		}
	}
}

# copy_acl_files(from, to, &modules)
# Copy all .acl files from some user or group to another in a list of modules
sub copy_acl_files
{
local $m;
foreach $m (@{$_[2]}) {
	unlink("$config_directory/$m/$_[1].acl");
	local %acl;
	if (&read_file("$config_directory/$m/$_[0].acl", \%acl)) {
		&write_file("$config_directory/$m/$_[1].acl", \%acl);
		}
	}
}

# set_acl_files(&allusers, &allgroups, module, &members, &access)
sub set_acl_files
{
foreach $m (@{$_[3]}) {
	local ($u) = grep { $_->{'name'} eq $m } @{$_[0]};
	local ($g) = grep { $_->{'name'} eq $m } @{$_[1]};
	if ($u || $g) {
		local $n = $u ? $u->{'name'} : $g->{'name'};
		&lock_file("$config_directory/$_[2]/$n.acl");
		&write_file("$config_directory/$_[2]/$n.acl", $_[4]);
		chmod(0640, "$config_directory/$_[2]/$n.acl");
		&unlock_file("$config_directory/$_[2]/$n.acl");
		}
	if ($g) {
		&set_acl_files($_[0], $_[1], $_[2], $g->{'members'}, $_[4]);
		}
	}
}

1;

