!c99Shell v. 1.0 pre-release build #16!

Software: Apache/2.2.3 (CentOS). PHP/5.1.6 

uname -a: Linux mx-ll-110-164-51-230.static.3bb.co.th 2.6.18-194.el5PAE #1 SMP Fri Apr 2 15:37:44
EDT 2010 i686
 

uid=48(apache) gid=48(apache) groups=48(apache) 

Safe-mode: OFF (not secure)

/usr/libexec/webmin/acl/   drwxr-xr-x
Free 50.93 GB of 127.8 GB (39.85%)
Home    Back    Forward    UPDIR    Refresh    Search    Buffer    Encoder    Tools    Proc.    FTP brute    Sec.    SQL    PHP-code    Update    Feedback    Self remove    Logout    


Viewing file:     acl-lib.pl (56.76 KB)      -rwxr-xr-x
Select action/file-type:
(+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
=head1 acl-lib.pl

Library for editing webmin users, passwords and access rights.

 foreign_require("acl", "acl-lib.pl");
 @users = acl::list_users();
 $newguy = { 'name' => 'newguy',
             'pass' => acl::encrypt_password('smeg'),
             'modules' => [ 'useradmin' ] };
 acl::create_user($newguy);

=cut

BEGIN { push(@INC, ".."); };
use WebminCore;
&init_config();
do 'md5-lib.pl';
%access = &get_module_acl();
$access{'switch'} = 0 if (&is_readonly_mode());

=head2 list_users([&only-users])

Returns a list of hashes containing Webmin user details. Useful keys include :

=item name - Login name

=item pass - Encrypted password

=item modules - Array references of modules

=item theme - Custom theme, if any

If the only-users parameter is given, limit the list to just users with
those usernames.

=cut
sub list_users
{
my ($only) = @_;
my (%miniserv, @rv, %acl, %logout);
local %_;
&read_acl(undef, \%acl);
&get_miniserv_config(\%miniserv);
foreach my $a (split(/\s+/, $miniserv{'logouttimes'})) {
    if ($a =~ /^([^=]+)=(\S+)$/) {
        $logout{$1} = $2;
        }
    }
open(PWFILE, $miniserv{'userfile'});
while(<PWFILE>) {
    s/\r|\n//g;
    local @user = split(/:/, $_);
    if (@user) {
        my %user;
        next if ($only && &indexof($user[0], @$only) < 0);
        $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{$1} =~ s/;/:/g;
            }
        if ($user[5] =~ /days\s+(\S+)/) {
            $user{'days'} = $1;
            }
        if ($user[5] =~ /hours\s+(\d+\.\d+)-(\d+\.\d+)/) {
            $user{'hoursfrom'} = $1;
            $user{'hoursto'} = $2;
            }
        $user{'lastchange'} = $user[6];
        $user{'olds'} = [ split(/\s+/, $user[7]) ];
        $user{'minsize'} = $user[8];
        $user{'nochange'} = int($user[9]);
        $user{'temppass'} = int($user[10]);
        $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{'rbacdeny'} = $gconfig{"rbacdeny_$user[0]"};
        if ($gconfig{"theme_$user[0]"}) {
            ($user{'theme'}, $user{'overlay'}) =
                split(/\s+/, $gconfig{"theme_$user[0]"});
            }
        elsif (defined($gconfig{"theme_$user[0]"})) {
            $user{'theme'} = "";
            }
        $user{'readonly'} = $gconfig{"readonly_$user[0]"};
        $user{'ownmods'} = [ split(/\s+/,
                       $gconfig{"ownmods_$user[0]"}) ];
        $user{'logouttime'} = $logout{$user[0]};
        $user{'real'} = $gconfig{"realname_$user[0]"};
        push(@rv, \%user);
        }
    }
close(PWFILE);

# If a user DB is enabled, get users from it too
if ($miniserv{'userdb'}) {
    my ($dbh, $proto, $prefix, $args) =&connect_userdb($miniserv{'userdb'});
    &error("Failed to connect to user database : $dbh") if (!ref($dbh));
    if ($proto eq "mysql" || $proto eq "postgresql") {
        # Fetch users with SQL
        my %userid;
        my $cmd = $dbh->prepare(
            "select id,name,pass from webmin_user".
            ($only ? " where name in (".
                 join(",", map { "'$_'" } @$only).")" : ""));
        $cmd && $cmd->execute() ||
            &error("Failed to query users : ".$dbh->errstr);
        while(my ($id, $name, $pass) = $cmd->fetchrow()) {
            my $u = { 'name' => $name,
                  'pass' => $pass,
                  'proto' => $proto,
                  'id' => $id };
            push(@rv, $u);
            $userid{$id} = $u;
            }
        $cmd->finish();

        # Add user attributes
        my $cmd = $dbh->prepare(
            "select id,attr,value from webmin_user_attr ".
            ($only && %userid ?
             " where id in (".join(",", keys %userid).")" : ""));
        $cmd && $cmd->execute() ||
            &error("Failed to query user attrs : ".$dbh->errstr);
        while(my ($id, $attr, $value) = $cmd->fetchrow()) {
            if ($attr eq "olds" || $attr eq "modules" ||
                $attr eq "ownmods") {
                $value = [ split(/\s+/, $value) ];
                }
            $userid{$id}->{$attr} = $value;
            }
        $cmd->finish();
        }
    elsif ($proto eq "ldap") {
        # Find users with LDAP query
        my $filter = '(objectClass='.$args->{'userclass'}.')';
        if ($only) {
            my $ufilter =
                "(|".join("", map { "(cn=$_)" } @$only).")";
            $filter = "(&".$filter.$ufilter.")";
            }
        my $rv = $dbh->search(
            base => $prefix,
            filter => $filter,
            scope => 'sub');
        if (!$rv || $rv->code) {
            &error("Failed to search users : ".
                ($rv ? $rv->error : "Unknown error"));
            }
        foreach my $l ($rv->all_entries) {
            my $u = { 'name' => $l->get_value('cn'),
                  'pass' => $l->get_value('webminPass'),
                  'proto' => $proto,
                  'id' => $l->dn() };
            foreach my $la ($l->get_value('webminAttr')) {
                my ($attr, $value) = split(/=/, $la, 2);
                if ($attr eq "olds" || $attr eq "ownmods") {
                    $value = [ split(/\s+/, $value) ];
                    }
                $u->{$attr} = $value;
                }
            $u->{'modules'} = [ $l->get_value('webminModule') ];
            push(@rv, $u);
            }
        }
    &disconnect_userdb($miniserv{'userdb'}, $dbh);
    }

return @rv;
}

=head2 get_user(username)

Returns a hash ref of details of the user with some name, in the same format
as list_users. Returns undef if not found

=cut
sub get_user
{
my ($username) = @_;
my @rv = &list_users([ $username ]);
my ($user) = grep { $_->{'name'} eq $username } @rv;
return $user;
}

=head2 list_groups([&only-groups])

Returns a list of hashes, one per Webmin group. Group membership is stored in
/etc/webmin/webmin.groups, and other attributes in the config file. Useful
keys include :

=item name - Group name

=item members - Array reference of member users

=item modules - Modules to grant to members

If the only-groups parameter is given, limit the list to just groups with
those group names.

=cut
sub list_groups
{
my ($only) = @_;
my @rv;
my %miniserv;
&get_miniserv_config(\%miniserv);

# Add groups from local files
open(GROUPS, "$config_directory/webmin.groups");
while(<GROUPS>) {
    s/\r|\n//g;
    local @g = split(/:/, $_);
    if (@g) {
        next if ($only && &indexof($g[0], @$only) < 0);
        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);

# If a user DB is enabled, get groups from it too
if ($miniserv{'userdb'}) {
    my ($dbh, $proto, $prefix, $args) =&connect_userdb($miniserv{'userdb'});
    &error("Failed to connect to group database : $dbh") if (!ref($dbh));
    if ($proto eq "mysql" || $proto eq "postgresql") {
        # Fetch groups with SQL
        my %groupid;
        my $cmd = $dbh->prepare(
            "select id,name,description from webmin_group ".
            ($only ? " where name in (".
                 join(",", map { "'$_'" } @$only).")" : ""));
        $cmd && $cmd->execute() ||
            &error("Failed to query groups : ".$dbh->errstr);
        while(my ($id, $name, $desc) = $cmd->fetchrow()) {
            my $g = { 'name' => $name,
                  'desc' => $desc,
                  'proto' => $proto,
                  'id' => $id };
            push(@rv, $g);
            $groupid{$id} = $g;
            }
        $cmd->finish();

        # Add group attributes
        my $cmd = $dbh->prepare(
            "select id,attr,value from webmin_group_attr ".
            ($only && %userid ?
                         " where id in (".join(",", keys %userid).")" : ""));
        $cmd && $cmd->execute() ||
            &error("Failed to query group attrs : ".$dbh->errstr);
        while(my ($id, $attr, $value) = $cmd->fetchrow()) {
            if ($attr eq "members" || $attr eq "modules" ||
                $attr eq "ownmods") {
                $value = [ split(/\s+/, $value) ];
                }
            $groupid{$id}->{$attr} = $value;
            }
        $cmd->finish();
        }
    elsif ($proto eq "ldap") {
        # Find groups with LDAP query
        my $filter = '(objectClass='.$args->{'groupclass'}.')';
        if ($only) {
            my $gfilter =
                "(|".join("", map { "(cn=$_)" } @$only).")";
            $filter = "(&".$filter.$gfilter.")";
            }
        my $rv = $dbh->search(
            base => $prefix,
            filter => $filter,
            scope => 'sub');
        if (!$rv || $rv->code) {
            &error("Failed to search groups : ".
                ($rv ? $rv->error : "Unknown error"));
            }
        foreach my $l ($rv->all_entries) {
            my $g = { 'name' => $l->get_value('cn'),
                  'desc' => $l->get_value('webminDesc'),
                  'proto' => $proto,
                  'id' => $l->dn() };
            foreach my $la ($l->get_value('webminAttr')) {
                my ($attr, $value) = split(/=/, $la, 2);
                if ($attr eq "members" || $attr eq "ownmods") {
                    $value = [ split(/\s+/, $value) ];
                    }
                $g->{$attr} = $value;
                }
            $g->{'modules'} = [ $l->get_value('webminModule') ];
            push(@rv, $g);
            }
        }
    &disconnect_userdb($miniserv{'userdb'}, $dbh);
    }

return @rv;
}

=head2 get_group(groupname)

Returns a hash ref of details of the group with some name, in the same format
as list_groups. Returns undef if not found

=cut
sub get_group
{
my ($groupname) = @_;
my @rv = &list_groups([ $groupname ]);
my ($group) = grep { $_->{'name'} eq $groupname } @rv;
return $group;
}

=head2 list_modules

Returns a list of the dirs of all modules available on this system.

=cut
sub list_modules
{
return map { $_->{'dir'} } &list_module_infos();
}

=head2 list_module_infos

Returns a list of the details of all modules that can be used on this system,
each of which is a hash reference in the same format as their module.info files.

=cut
sub list_module_infos
{
my @mods = grep { &check_os_support($_) } &get_all_module_infos();
return sort { $a->{'desc'} cmp $b->{'desc'} } @mods;
}

=head2 create_user(&details, [clone])

Creates a new Webmin user, based on the hash reference in the details parameter.
This must be in the same format as those returned by list_users. If the clone
parameter is given, it must be a username to copy detailed access control
settings from for this new user.

=cut
sub create_user
{
my %user = %{$_[0]};
my $clone = $_[1];
my %miniserv;
my @mods = &list_modules();

&get_miniserv_config(\%miniserv);

if ($miniserv{'userdb'} && !$miniserv{'userdb_addto'}) {
    # Adding to user database
    my ($dbh, $proto, $prefix, $args) =&connect_userdb($miniserv{'userdb'});
        &error("Failed to connect to user database : $dbh") if (!ref($dbh));
    if ($proto eq "mysql" || $proto eq "postgresql") {
        # Add user with SQL
        my $cmd = $dbh->prepare("insert into webmin_user (name,pass) values (?, ?)");
        $cmd && $cmd->execute($user{'name'}, $user{'pass'}) ||
            &error("Failed to add user : ".$dbh->errstr);
        $cmd->finish();
        my $cmd = $dbh->prepare("select max(id) from webmin_user");
        $cmd->execute();
        my ($id) = $cmd->fetchrow();
        $cmd->finish();

        # Add other attributes
        my $cmd = $dbh->prepare("insert into webmin_user_attr (id,attr,value) values (?, ?, ?)");
        foreach my $attr (keys %user) {
            next if ($attr eq "name" || $attr eq "pass");
            my $value = $user{$attr};
            if ($attr eq "olds" || $attr eq "modules" ||
                $attr eq "ownmods") {
                $value = join(" ", @$value);
                }
            $cmd->execute($id, $attr, $value) ||
                &error("Failed to add user attribute : ".
                    $dbh->errstr);
            $cmd->finish();
            }
        $_[0]->{'id'} = $id;
        $_[0]->{'proto'} = $proto;
        }
    elsif ($proto eq "ldap") {
        # Add user to LDAP
        my $dn = "cn=".$user{'name'}.",".$prefix;
        my @attrs = ( "objectClass", $args->{'userclass'},
                  "cn", $user{'name'},
                  "webminPass", $user{'pass'} );
        my @webminattrs;
        foreach my $attr (keys %user) {
            next if ($attr eq "name" || $attr eq "pass" ||
                 $attr eq "modules");
            my $value = $user{$attr};
            if ($attr eq "olds" || $attr eq "ownmods") {
                $value = join(" ", @$value);
                }
            push(@webminattrs,
                 defined($value) ? $attr."=".$value : $attr);
            }
        if (@webminattrs) {
            push(@attrs, "webminAttr", \@webminattrs);
            }
        if (@{$user{'modules'}}) {
            push(@attrs, "webminModule", $user{'modules'});
            }
        my $rv = $dbh->add($dn, attr => \@attrs);
        if (!$rv || $rv->code) {
            &error("Failed to add user to LDAP : ".
                   ($rv ? $rv->error : "Unknown error"));
            }
        $_[0]->{'id'} = $dn;
        $_[0]->{'proto'} = 'ldap';
        }
    &disconnect_userdb($miniserv{'userdb'}, $dbh);
    $user{'proto'} = $proto;
    }
else {
    # Adding to local files
    &lock_file($ENV{'MINISERV_CONFIG'});
    if ($user{'theme'}) {
        $miniserv{"preroot_".$user{'name'}} =
            $user{'theme'}.($user{'overlay'} ? " ".$user{'overlay'} : "");
        }
    elsif (defined($user{'theme'})) {
        $miniserv{"preroot_".$user{'name'}} = "";
        }
    if (defined($user{'logouttime'})) {
        my @logout = split(/\s+/, $miniserv{'logouttimes'});
        push(@logout, "$user{'name'}=$user{'logouttime'}");
        $miniserv{'logouttimes'} = join(" ", @logout);
        }
    &put_miniserv_config(\%miniserv);
    &unlock_file($ENV{'MINISERV_CONFIG'});

    my @times;
    push(@times, "days", $user{'days'}) if ($user{'days'} ne '');
    push(@times, "hours", $user{'hoursfrom'}."-".$user{'hoursto'})
        if ($user{'hoursfrom'});
    &lock_file($miniserv{'userfile'});
    &open_tempfile(PWFILE, ">>$miniserv{'userfile'}");
    my $allow = $user{'allow'};
    $allow =~ s/:/;/g;
    my $deny = $user{'deny'};
    $deny =~ s/:/;/g;
    &print_tempfile(PWFILE,
        "$user{'name'}:$user{'pass'}:$user{'sync'}:$user{'cert'}:",
        ($allow ? "allow $allow" :
         $deny ? "deny $deny" : ""),":",
        join(" ", @times),":",
        $user{'lastchange'},":",
        join(" ", @{$user{'olds'}}),":",
        $user{'minsize'},":",
        $user{'nochange'},":",
        $user{'temppass'},
        "\n");
    &close_tempfile(PWFILE);
    &unlock_file($miniserv{'userfile'});

    &lock_file(&acl_filename());
    &open_tempfile(ACL, ">>".&acl_filename());
    &print_tempfile(ACL, &acl_line(\%user, \@mods));
    &close_tempfile(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{"rbacdeny_".$user{'name'}});
    $gconfig{"rbacdeny_".$user{'name'}} = $user{'rbacdeny'} if ($user{'rbacdeny'});
    delete($gconfig{"ownmods_".$user{'name'}});
    $gconfig{"ownmods_".$user{'name'}} = join(" ", @{$user{'ownmods'}})
        if (@{$user{'ownmods'}});
    delete($gconfig{"theme_".$user{'name'}});
    if ($user{'theme'}) {
        $gconfig{"theme_".$user{'name'}} =
            $user{'theme'}.($user{'overlay'} ? " ".$user{'overlay'} : "");
        }
    elsif (defined($user{'theme'})) {
        $gconfig{"theme_".$user{'name'}} = '';
        }
    $gconfig{"readonly_".$user{'name'}} = $user{'readonly'}
        if (defined($user{'readonly'}));
    $gconfig{"realname_".$user{'name'}} = $user{'real'}
        if (defined($user{'real'}));
    &write_file("$config_directory/config", \%gconfig);
    }

# Copy ACLs from user being cloned
if ($clone) {
    &copy_acl_files($clone, $user{'name'}, [ "", @mods ]);
    }
}

=head2 modify_user(old-name, &details)

Updates an existing Webmin user, identified by the old-name paramter. The
details hash must be in the same format as returned by list_users or passed
to create_user.

=cut
sub modify_user
{
my $username = $_[0];
my %user = %{$_[1]};
my (%miniserv, @pwfile, @acl, @mods, $m);
local $_;
&get_miniserv_config(\%miniserv);

if ($user{'proto'}) {
    # In users and groups DB
    my ($dbh, $proto) = &connect_userdb($miniserv{'userdb'});
    &error("Failed to connect to user database : $dbh") if (!ref($dbh));
    if ($proto eq "mysql" || $proto eq "postgresql") {
        # Get old password, for change detection
        my $cmd = $dbh->prepare(
            "select pass from webmin_user where id = ?");
        $cmd && $cmd->execute($user{'id'}) ||
            &error("Failed to get old password : ".$dbh->errstr);
        my ($oldpass) = $cmd->fetchrow();
        $cmd->finish();
        &add_old_password(\%user, $oldpass, \%miniserv);

        # Update primary details
        my $cmd = $dbh->prepare("update webmin_user set name = ?, ".
                        "pass = ? where id = ?");
        $cmd && $cmd->execute($user{'name'}, $user{'pass'},
                      $user{'id'}) ||
            &error("Failed to update user : ".$dbh->errstr);
        $cmd->finish();

        # Re-save attributes
        my $cmd = $dbh->prepare("delete from webmin_user_attr ".
                    "where id = ?");
        $cmd && $cmd->execute($user{'id'}) ||
            &error("Failed to delete attrs : ".$dbh->errstr);
        my $cmd = $dbh->prepare("insert into webmin_user_attr ".
                    "(id,attr,value) values (?, ?, ?)");
        foreach my $attr (keys %user) {
            next if ($attr eq "name" || $attr eq "pass");
            my $value = $user{$attr};
            if ($attr eq "olds" || $attr eq "modules" ||
                $attr eq "ownmods") {
                $value = join(" ", @$value);
                }
            $cmd->execute($user{'id'}, $attr, $value) ||
                &error("Failed to add user attribute : ".
                    $dbh->errstr);
            $cmd->finish();
            }
        }
    elsif ($proto eq "ldap") {
        # Rename in LDAP if needed
        if ($user{'name'} ne $username) {
            my $newdn = $user{'id'};
            $newdn =~ s/^cn=\Q$username\E,/cn=$user{'name'},/;
            my $rv = $dbh->moddn($user{'id'},
                         newrdn => "cn=$user{'name'}");
            if (!$rv || $rv->code) {
                &error("Failed to rename user : ".
                       ($rv ? $rv->error : "Unknown error"));
                }
            $user{'id'} = $newdn;
            }

        # Re-save all the attributes
        my @attrs = ( "cn", $user{'name'},
                  "webminPass", $user{'pass'} );
        my @webminattrs;
        foreach my $attr (keys %user) {
            next if ($attr eq "name" || $attr eq "desc" ||
                 $attr eq "modules");
            my $value = $user{$attr};
            if ($attr eq "olds" || $attr eq "ownmods") {
                $value = join(" ", @$value);
                }
            push(@webminattrs,
                 defined($value) ? $attr."=".$value : $attr);
            }
        push(@attrs, "webminAttr", \@webminattrs);
        push(@attrs, "webminModule", $user{'modules'});
        my $rv = $dbh->modify($user{'id'}, replace => { @attrs });
        if (!$rv || $rv->code) {
            &error("Failed to modify user : ".
                   ($rv ? $rv->error : "Unknown error"));
            }
        }
    &disconnect_userdb($miniserv{'userdb'}, $dbh);
    }
else {
    # In local files
    &lock_file($ENV{'MINISERV_CONFIG'});
    delete($miniserv{"preroot_".$username});
    if ($user{'theme'}) {
        $miniserv{"preroot_".$user{'name'}} =
          $user{'theme'}.($user{'overlay'} ? " ".$user{'overlay'} : "");
        }
    elsif (defined($user{'theme'})) {
        $miniserv{"preroot_".$user{'name'}} = "";
        }
    local @logout = split(/\s+/, $miniserv{'logouttimes'});
    @logout = grep { $_ !~ /^$username=/ } @logout;
    if (defined($user{'logouttime'})) {
        push(@logout, "$user{'name'}=$user{'logouttime'}");
        }
    $miniserv{'logouttimes'} = join(" ", @logout);
    &put_miniserv_config(\%miniserv);
    &unlock_file($ENV{'MINISERV_CONFIG'});

    local @times;
    push(@times, "days", $user{'days'}) if ($user{'days'} ne '');
    push(@times, "hours", $user{'hoursfrom'}."-".$user{'hoursto'})
        if ($user{'hoursfrom'});
    &lock_file($miniserv{'userfile'});
    open(PWFILE, $miniserv{'userfile'});
    @pwfile = <PWFILE>;
    close(PWFILE);
    &open_tempfile(PWFILE, ">$miniserv{'userfile'}");
    my $allow = $user{'allow'};
    $allow =~ s/:/;/g;
    my $deny = $user{'deny'};
    $deny =~ s/:/;/g;
    foreach (@pwfile) {
        if (/^([^:]+):([^:]*)/ && $1 eq $username) {
            &add_old_password(\%user, "$2", \%miniserv);
            &print_tempfile(PWFILE,
                "$user{'name'}:$user{'pass'}:",
                "$user{'sync'}:$user{'cert'}:",
                ($allow ? "allow $allow" :
                 $deny ? "deny $deny" : ""),":",
                join(" ", @times),":",
                $user{'lastchange'},":",
                join(" ", @{$user{'olds'}}),":",
                $user{'minsize'},":",
                $user{'nochange'},":",
                $user{'temppass'},
                "\n");
            }
        else {
            &print_tempfile(PWFILE, $_);
            }
        }
    &close_tempfile(PWFILE);
    &unlock_file($miniserv{'userfile'});

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

    delete($gconfig{"lang_".$username});
    $gconfig{"lang_".$user{'name'}} = $user{'lang'} if ($user{'lang'});
    delete($gconfig{"notabs_".$username});
    $gconfig{"notabs_".$user{'name'}} = $user{'notabs'}
        if ($user{'notabs'});
    delete($gconfig{"skill_".$username});
    $gconfig{"skill_".$user{'name'}} = $user{'skill'} if ($user{'skill'});
    delete($gconfig{"risk_".$username});
    $gconfig{"risk_".$user{'name'}} = $user{'risk'} if ($user{'risk'});
    delete($gconfig{"rbacdeny_".$username});
    $gconfig{"rbacdeny_".$user{'name'}} = $user{'rbacdeny'}
        if ($user{'rbacdeny'});
    delete($gconfig{"ownmods_".$username});
    $gconfig{"ownmods_".$user{'name'}} = join(" ", @{$user{'ownmods'}})
        if (@{$user{'ownmods'}});
    delete($gconfig{"theme_".$username});
    if ($user{'theme'}) {
        $gconfig{"theme_".$user{'name'}} =
          $user{'theme'}.($user{'overlay'} ? " ".$user{'overlay'} : "");
        }
    elsif (defined($user{'theme'})) {
        $gconfig{"theme_".$user{'name'}} = '';
        }
    delete($gconfig{"readonly_".$username});
    $gconfig{"readonly_".$user{'name'}} = $user{'readonly'}
        if (defined($user{'readonly'}));
    delete($gconfig{"realname_".$username});
    $gconfig{"realname_".$user{'name'}} = $user{'real'}
        if (defined($user{'real'}));
    &write_file("$config_directory/config", \%gconfig);
    }

if ($username ne $user{'name'} && !$user{'proto'}) {
    # Rename all .acl files if user renamed
    foreach $m (@mods, "") {
        local $file = "$config_directory/$m/$username.acl";
        if (-r $file) {
            &rename_file($file,
                "$config_directory/$m/$user{'name'}.acl");
            }
        }
    local $file = "$config_directory/$username.acl";
    if (-r $file) {
        &rename_file($file, "$config_directory/$user{'name'}.acl");
        }
    }

if ($miniserv{'session'} && $username ne $user{'name'}) {
    # Modify all sessions for the renamed user
    &rename_session_user(\&miniserv, $username, $user{'name'});
    }
}

=head2 add_old_password(&user, oldpass, &miniserv)

Internal function to update the olds list of old passwords for a user

=cut
sub add_old_password
{
my ($user, $oldpass, $miniserv) = @_;
if ($oldpass ne $user->{'pass'} &&
    "!".$oldpass ne $user->{'pass'} &&
    $oldpass ne "!".$user->{'pass'} &&
    $user->{'pass'} ne 'x' &&
    $user->{'pass'} ne 'e' &&
    $user->{'pass'} ne '*LK*') {
    # Password change detected .. update change time
    # and save the old one
    local $nolock = $oldpass;
    $nolock =~ s/^\!//;
    unshift(@{$user->{'olds'}}, $nolock);
    if ($miniserv->{'pass_oldblock'}) {
        while(scalar(@{$user->{'olds'}}) >
              $miniserv->{'pass_oldblock'}) {
            pop(@{$user->{'olds'}});
            }
        }
    $user->{'lastchange'} = time();
    }
}

=head2 delete_user(name)

Deletes the named user, including all .acl files for detailed module access
control settings.

=cut
sub delete_user
{
my ($username) = @_;
my (@pwfile, @acl, %miniserv);
local $_;

&lock_file($ENV{'MINISERV_CONFIG'});
&get_miniserv_config(\%miniserv);
delete($miniserv{"preroot_".$username});
my @logout = split(/\s+/, $miniserv{'logouttimes'});
@logout = grep { $_ !~ /^$username=/ } @logout;
$miniserv{'logouttimes'} = join(" ", @logout);
&put_miniserv_config(\%miniserv);
&unlock_file($ENV{'MINISERV_CONFIG'});

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

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

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

# Delete all module .acl files
&unlink_file(map { "$config_directory/$_/$username.acl" } &list_modules());
&unlink_file("$config_directory/$username.acl");

if ($miniserv{'session'}) {
    # Delete all sessions for the deleted user
    &delete_session_user(\%miniserv, $username);
    }

if ($miniserv{'userdb'}) {
    # Also delete from user database
    my ($dbh, $proto, $prefix, $args) =&connect_userdb($miniserv{'userdb'});
    &error("Failed to connect to user database : $dbh") if (!ref($dbh));
    if ($proto eq "mysql" || $proto eq "postgresql") {
        # Find the user with SQL query
        my $cmd = $dbh->prepare(
            "select id from webmin_user where name = ?");
        $cmd && $cmd->execute($username) ||
            &error("Failed to find user : ".$dbh->errstr);
        my ($id) = $cmd->fetchrow();
        $cmd->finish();

        if (defined($id)) {
            # Delete the user
            my $cmd = $dbh->prepare(
                "delete from webmin_user where id = ?");
            $cmd && $cmd->execute($id) ||
                &error("Failed to delete user : ".$dbh->errstr);
            $cmd->finish();

            # Delete attributes
            my $cmd = $dbh->prepare(
                "delete from webmin_user_attr where id = ?");
            $cmd && $cmd->execute($id) ||
                &error("Failed to delete user attrs : ".
                       $dbh->errstr);
            $cmd->finish();

            # Delete ACLs
            my $cmd = $dbh->prepare(
                "delete from webmin_user_acl where id = ?");
            $cmd && $cmd->execute($id) ||
                &error("Failed to delete user acls : ".
                       $dbh->errstr);
            $cmd->finish();
            }
        }
    elsif ($proto eq "ldap") {
        # Find user with LDAP query
        my $rv = $dbh->search(
            base => $prefix,
            filter => '(&(cn='.$username.')(objectClass='.
                  $args->{'userclass'}.'))',
            scope => 'sub');
        if (!$rv || $rv->code) {
            &error("Failed to find user : ".
                   ($rv ? $rv->error : "Unknown error"));
            }
        my ($user) = $rv->all_entries;

        if ($user) {
            # Delete sub-objects
            my $rv = $dbh->search(
                base => $user->dn(),
                filter => '(objectClass=*)',
                scope => 'sub');
            if (!$rv || $rv->code) {
                &error("Failed to delete LDAP user : ".
                       ($rv ? $rv->error : "Unknown error"));
                }
            foreach my $so ($rv->all_entries) {
                next if ($so->dn() eq $user->dn());
                my $drv = $dbh->delete($so->dn());
                if ($drv->code) {
                    &error("Failed to delete LDAP ".
                           "sub-object : ".$drv->error);
                    }
                }

            # Delete the user from LDAP
            my $rv = $dbh->delete($user->dn());
            if (!$rv || $rv->code) {
                &error("Failed to delete LDAP user : ".
                       ($rv ? $rv->error : "Unknown error"));
                }
            }
        }
    &disconnect_userdb($miniserv{'userdb'}, $dbh);
    }
}

=head2 create_group(&group, [clone])

Add a new webmin group, based on the details in the group hash. The required
keys are :

=item name - Unique name of the group

=item modules - An array reference of module names

=item members - An array reference of group member names. Sub-groups must have their names prefixed with an @.

=cut
sub create_group
{
my %group = %{$_[0]};
my $clone = $_[1];
my %miniserv;
&get_miniserv_config(\%miniserv);

if ($miniserv{'userdb'} && !$miniserv{'userdb_addto'}) {
    # Adding to group database
    my ($dbh, $proto, $prefix, $args) =&connect_userdb($miniserv{'userdb'});
        &error("Failed to connect to group database : $dbh") if (!ref($dbh));
    if ($proto eq "mysql" || $proto eq "postgresql") {
        # Add group with SQL
        my $cmd = $dbh->prepare("insert into webmin_group (name,description) values (?, ?)");
        $cmd && $cmd->execute($group{'name'}, $group{'desc'}) ||
            &error("Failed to add group : ".$dbh->errstr);
        $cmd->finish();
        my $cmd = $dbh->prepare("select max(id) from webmin_group");
        $cmd->execute();
        my ($id) = $cmd->fetchrow();
        $cmd->finish();

        # Add other attributes
        my $cmd = $dbh->prepare("insert into webmin_group_attr (id,attr,value) values (?, ?, ?)");
        foreach my $attr (keys %group) {
            next if ($attr eq "name" || $attr eq "desc");
            my $value = $group{$attr};
            if ($attr eq "members" || $attr eq "modules" ||
                $attr eq "ownmods") {
                $value = join(" ", @$value);
                }
            $cmd->execute($id, $attr, $value) ||
                &error("Failed to add group attribute : ".
                    $dbh->errstr);
            $cmd->finish();
            }
        $_[0]->{'id'} = $id;
        $_[0]->{'proto'} = $proto;
        }
    elsif ($proto eq "ldap") {
        # Add group to LDAP
        my $dn = "cn=".$group{'name'}.",".$prefix;
        my @attrs = ( "objectClass", $args->{'groupclass'},
                  "cn", $group{'name'},
                  "webminDesc", $group{'desc'} );
        my @webminattrs;
        foreach my $attr (keys %group) {
            next if ($attr eq "name" || $attr eq "desc" ||
                 $attr eq "modules");
            my $value = $group{$attr};
            if ($attr eq "members" || $attr eq "ownmods") {
                $value = join(" ", @$value);
                }
            push(@webminattrs, $attr."=".$value);
            }
        if (@webminattrs) {
            push(@attrs, "webminAttr", \@webminattrs);
            }
        if (@{$group{'modules'}}) {
            push(@attrs, "webminModule", $group{'modules'});
            }
        my $rv = $dbh->add($dn, attr => \@attrs);
        if (!$rv || $rv->code) {
            &error("Failed to add group to LDAP : ".
                   ($rv ? $rv->error : "Unknown error"));
            }
        $_[0]->{'id'} = $dn;
        $_[0]->{'proto'} = 'ldap';
        }
    &disconnect_userdb($miniserv{'userdb'}, $dbh);
    $group{'proto'} = $proto;
    }
else {
    # Adding to local files
    &lock_file("$config_directory/webmin.groups");
    open(GROUP, ">>$config_directory/webmin.groups");
    print GROUP &group_line(\%group),"\n";
    close(GROUP);
    &unlock_file("$config_directory/webmin.groups");
    }

if ($clone) {
    # Clone ACLs from original group
    &copy_acl_files($clone, $group{'name'}, [ "", &list_modules() ],
            "group", "group");
    }
}

=head2 modify_group(old-name, &group)

Update a webmin group, identified by the name parameter. The group's new
details are in the group hash ref, which must be in the same format as
returned by list_groups.

=cut
sub modify_group
{
my $groupname = $_[0];
my %group = %{$_[1]};
my %miniserv;
&get_miniserv_config(\%miniserv);

if ($group{'proto'}) {
    # In users and groups DB
    my ($dbh, $proto) = &connect_userdb($miniserv{'userdb'});
    &error("Failed to connect to group database : $dbh") if (!ref($dbh));
    if ($proto eq "mysql" || $proto eq "postgresql") {
        # Update primary details
        my $cmd = $dbh->prepare("update webmin_group set name = ?, ".
                        "description = ? where id = ?");
        $cmd && $cmd->execute($group{'name'}, $group{'desc'},
                      $group{'id'}) ||
            &error("Failed to update group : ".$dbh->errstr);
        $cmd->finish();

        # Re-save attributes
        my $cmd = $dbh->prepare("delete from webmin_group_attr ".
                    "where id = ?");
        $cmd && $cmd->execute($group{'id'}) ||
            &error("Failed to delete attrs : ".$dbh->errstr);
        my $cmd = $dbh->prepare("insert into webmin_group_attr ".
                    "(id,attr,value) values (?, ?, ?)");
        foreach my $attr (keys %group) {
            next if ($attr eq "name" || $attr eq "desc");
            my $value = $group{$attr};
            if ($attr eq "members" || $attr eq "modules" ||
                $attr eq "ownmods") {
                $value = join(" ", @$value);
                }
            $cmd->execute($group{'id'}, $attr, $value) ||
                &error("Failed to add group attribute : ".
                    $dbh->errstr);
            $cmd->finish();
            }
        }
    elsif ($proto eq "ldap") {
        # Rename in LDAP if needed
        if ($group{'name'} ne $groupname) {
            my $newdn = $group{'id'};
            $newdn =~ s/^cn=\Q$groupname\E,/cn=$group{'name'},/;
            my $rv = $dbh->moddn($group{'id'},
                         newrdn => "cn=$group{'name'}");
            if (!$rv || $rv->code) {
                &error("Failed to rename group : ".
                       ($rv ? $rv->error : "Unknown error"));
                }
            $group{'id'} = $newdn;
            }

        # Re-save all the attributes
        my @attrs = ( "cn", $group{'name'},
                  "webminDesc", $group{'desc'} );
        my @webminattrs;
        foreach my $attr (keys %group) {
            next if ($attr eq "name" || $attr eq "desc" ||
                 $attr eq "modules");
            my $value = $group{$attr};
            if ($attr eq "members" || $attr eq "ownmods") {
                $value = join(" ", @$value);
                }
            push(@webminattrs, $attr."=".$value);
            }
        push(@attrs, "webminAttr", \@webminattrs);
        push(@attrs, "webminModule", $group{'modules'});
        my $rv = $dbh->modify($group{'id'}, replace => { @attrs });
        if (!$rv || $rv->code) {
            &error("Failed to modify group : ".
                   ($rv ? $rv->error : "Unknown error"));
            }
        }
    &disconnect_userdb($miniserv{'userdb'}, $dbh);
    }
else {
    # Update local file
    &lock_file("$config_directory/webmin.groups");
    local $lref = &read_file_lines("$config_directory/webmin.groups");
    foreach $l (@$lref) {
        if ($l =~ /^([^:]+):/ && $1 eq $groupname) {
            $l = &group_line(\%group);
            }
        }
    &flush_file_lines("$config_directory/webmin.groups");
    &unlock_file("$config_directory/webmin.groups");
    }

if ($groupname ne $group{'name'} && !$group{'proto'}) {
    # Rename all .gacl files if group renamed
    foreach my $m (@{$group{'modules'}}, "") {
        local $file = "$config_directory/$m/$groupname.gacl";
        if (-r $file) {
            &rename_file($file,
                 "$config_directory/$m/$group{'name'}.gacl");
            }
        }
    }
}

=head2 delete_group(name)

Delete a webmin group, identified by the name parameter.

=cut
sub delete_group
{
my ($groupname) = @_;
my %miniserv;
&get_miniserv_config(\%miniserv);

# Delete from local files
&lock_file("$config_directory/webmin.groups");
my $lref = &read_file_lines("$config_directory/webmin.groups");
@$lref = grep { !/^([^:]+):/ || $1 ne $groupname } @$lref;
&flush_file_lines();
&unlock_file("$config_directory/webmin.groups");
&unlink_file(map { "$config_directory/$_/$groupname.gacl" } &list_modules());

if ($miniserv{'userdb'}) {
    # Also delete from group database
    my ($dbh, $proto, $prefix, $args) =&connect_userdb($miniserv{'userdb'});
    &error("Failed to connect to group database : $dbh") if (!ref($dbh));
    if ($proto eq "mysql" || $proto eq "postgresql") {
        # Find the group with SQL query
        my $cmd = $dbh->prepare(
            "select id from webmin_group where name = ?");
        $cmd && $cmd->execute($groupname) ||
            &error("Failed to find group : ".$dbh->errstr);
        my ($id) = $cmd->fetchrow();
        $cmd->finish();

        if (defined($id)) {
            # Delete the group
            my $cmd = $dbh->prepare(
                "delete from webmin_group where id = ?");
            $cmd && $cmd->execute($id) ||
                &error("Failed to delete group : ".$dbh->errstr);
            $cmd->finish();

            # Delete attributes
            my $cmd = $dbh->prepare(
                "delete from webmin_group_attr where id = ?");
            $cmd && $cmd->execute($id) ||
                &error("Failed to delete group attrs : ".
                       $dbh->errstr);
            $cmd->finish();

            # Delete ACLs
            my $cmd = $dbh->prepare(
                "delete from webmin_group_acl where id = ?");
            $cmd && $cmd->execute($id) ||
                &error("Failed to delete group acls : ".
                       $dbh->errstr);
            $cmd->finish();
            }
        }
    elsif ($proto eq "ldap") {
        # Find group with LDAP query
        my $rv = $dbh->search(
            base => $prefix,
            filter => '(&(cn='.$groupname.')(objectClass='.
                                  $args->{'groupclass'}.'))',
            scope => 'sub');
        if (!$rv || $rv->code) {
            &error("Failed to find group : ".
                   ($rv ? $rv->error : "Unknown error"));
            }
        my ($group) = $rv->all_entries;

        if ($group) {
            # Delete sub-objects
            my $rv = $dbh->search(
                base => $group->dn(),
                filter => '(objectClass=*)',
                scope => 'sub');
            if (!$rv || $rv->code) {
                &error("Failed to delete LDAP group : ".
                       ($rv ? $rv->error : "Unknown error"));
                }
            foreach my $so ($rv->all_entries) {
                next if ($so->dn() eq $group->dn());
                my $drv = $dbh->delete($so->dn());
                if ($drv->code) {
                    &error("Failed to delete LDAP ".
                           "sub-object : ".$drv->error);
                    }
                }

            # Delete the group from LDAP
            my $rv = $dbh->delete($group->dn());
            if (!$rv || $rv->code) {
                &error("Failed to delete LDAP group : ".
                       ($rv ? $rv->error : "Unknown error"));
                }
            }
        }
    &disconnect_userdb($miniserv{'userdb'}, $dbh);
    }

}

=head2 group_line(&group)

Internal function to generate a group file line

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

=head2 acl_line(&user, &allmodules)

Internal function to generate an ACL file line.

=cut
sub acl_line
{
my %user = %{$_[0]};
return "$user{'name'}: ".join(' ', @{$user{'modules'}})."\n";
}

=head2 can_edit_user(user, [&groups])

Returns 1 if the current Webmin user can edit some other user.

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

=head2 open_session_db(\%miniserv)

Opens the session database, and ties it to the sessiondb hash. Parameters are :

=item miniserv - The Webmin miniserv.conf file as a hash ref, as supplied by get_miniserv_config

=cut
sub open_session_db
{
my $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);
    }
else {
    delete($sessiondb{'1111111111'});
    }
}

=head2 delete_session_id(\%miniserv, id)

Deletes one session from the database. Parameters are :

=item miniserv - The Webmin miniserv.conf file as a hash ref, as supplied by get_miniserv_config.

=item user - ID of the session to remove.

=cut
sub delete_session_id
{
return 1 if (&is_readonly_mode());
&open_session_db($_[0]);
my $ex = exists($sessiondb{$_[1]});
delete($sessiondb{$_[1]});
dbmclose(%sessiondb);
return $ex;
}

=head2 delete_session_user(\%miniserv, user)

Deletes all sessions for some user. Parameters are :

=item miniserv - The Webmin miniserv.conf file as a hash ref, as supplied by get_miniserv_config.

=item user - Name of the user whose sessions get removed.

=cut
sub delete_session_user
{
return 1 if (&is_readonly_mode());
&open_session_db($_[0]);
foreach my $s (keys %sessiondb) {
    local ($u,$t) = split(/\s+/, $sessiondb{$s});
    if ($u eq $_[1]) {
        delete($sessiondb{$s});
        }
    }
dbmclose(%sessiondb);
}

=head2 rename_session_user(\%miniserv, olduser, newuser)

Changes the username in all sessions for some user. Parameters are :

=item miniserv - The Webmin miniserv.conf file as a hash ref, as supplied by get_miniserv_config.

=item olduser - The original username.

=item newuser - The new username.

=cut
sub rename_session_user
{
return 1 if (&is_readonly_mode());
&open_session_db(\%miniserv);
foreach my $s (keys %sessiondb) {
    local ($u,$t) = split(/\s+/, $sessiondb{$s});
    if ($u eq $_[1]) {
        $sessiondb{$s} = "$_[2] $t";
        }
    }
dbmclose(%sessiondb);
}

=head2 update_members(&allusers, &allgroups, &modules, &members)

Update the modules for members users and groups of some group. The parameters
are :

=item allusers - An array ref of all Webmin users, as returned by list_users.

=item allgroups - An array ref of all Webmin groups.

=item modules - Modules to assign to members.

=item members - An array ref of member user and group names.

=cut
sub update_members
{
foreach my $m (@{$_[3]}) {
    if ($m !~ /^\@(.*)$/) {
        # Member is a user
        my ($u) = grep { $_->{'name'} eq $m } @{$_[0]};
        if ($u) {
            $u->{'modules'} = [ @{$_[2]}, @{$u->{'ownmods'}} ];
            &modify_user($u->{'name'}, $u);
            }
        }
    else {
        # Member is a group
        my $gname = substr($m, 1);
        my ($g) = grep { $_->{'name'} eq $gname } @{$_[1]};
        if ($g) {
            $g->{'modules'} = [ @{$_[2]}, @{$g->{'ownmods'}} ];
            &modify_group($g->{'name'}, $g);
            &update_members($_[0], $_[1], $g->{'modules'},
                    $g->{'members'});
            }
        }
    }
}

=head2 copy_acl_files(from, to, &modules, [from-type], [to-type])

Copy all .acl files from some user to another user in a list of modules.
The parameters are :

=item from - Source user or group name.

=item to - Destination user or group name.

=item modules - Array ref of module names.

=item from-type - Either "user" or "group", defaults to "user"

=item to-type - Either "user" or "group", defaults to "user"

=cut
sub copy_acl_files
{
my ($from, $to, $mods, $fromtype, $totype) = @_;
$fromtype ||= "user";
$totype ||= "user";
my ($dbh, $proto, $fromid, $toid);

# Check if the source user/group is in a DB
my $userdb = &get_userdb_string();
if ($userdb) {
    ($dbh, $proto, $prefix, $args) = &connect_userdb($userdb);
    &error($dbh) if (!ref($dbh));
    if ($proto eq "mysql" || $proto eq "postgresql") {
        # Search in SQL DB
        my $cmd = $dbh->prepare(
            "select id from webmin_${fromtype} where name = ?");
        $cmd && $cmd->execute($from) || &error($dbh->errstr);
        ($fromid) = $cmd->fetchrow();
        $cmd->finish();
        my $cmd = $dbh->prepare(
            "select id from webmin_${totype} where name = ?");
        $cmd && $cmd->execute($to) || &error($dbh->errstr);
        ($toid) = $cmd->fetchrow();
        $cmd->finish();
        }
    elsif ($proto eq "ldap") {
        # Search in LDAP
        my $fromclass = $fromtype eq "user" ? "userclass"
                            : "groupclass";
        my $rv = $dbh->search(
            base => $prefix,
            filter => '(&(cn='.$from.')(objectClass='.
                  $fromclass.'))',
            scope => 'sub');
        $rv->code && &error($rv->error);
        my ($fromobj) = $rv->all_entries;
        $fromid = $fromobj ? $fromobj->dn() : undef;
        my $toclass = $totype eq "user" ? "userclass"
                        : "groupclass";
        my $rv = $dbh->search(
            base => $prefix,
            filter => '(&(cn='.$to.')(objectClass='.
                  $toclass.'))',
            scope => 'sub');
        $rv->code && &error($rv->error);
        my ($toobj) = $rv->all_entries;
        $toid = $toobj ? $toobj->dn() : undef;
        }
    }

if (defined($fromid) && defined($toid) &&
    ($proto eq "mysql" || $proto eq "postgresql")) {
    # Copy from database to database
    my $delcmd = $dbh->prepare("delete from webmin_${totype}_acl where id = ? and module = ?");
    my $cmd = $dbh->prepare("insert into webmin_${totype}_acl select ?,module,attr,value from webmin_${fromtype}_acl where id = ? and module = ?");
    foreach my $m (@$mods) {
        $delcmd && $delcmd->execute($toid, $m) ||
            &error("Failed to clear ACLs : ".$dbh->errstr);
        $delcmd->finish();
        $cmd && $cmd->execute($toid, $fromid, $m) ||
            &error("Failed to copy ACLs : ".$dbh->errstr);
        $cmd->finish();
        }
    }
elsif (!defined($fromid) && !defined($toid)) {
    # Copy files
    my $fromsuffix = $fromtype eq "user" ? "acl" : "gacl";
    my $tosuffix = $totype eq "user" ? "acl" : "gacl";
    foreach my $m (@$mods) {
        &unlink_file("$config_directory/$m/$to.$tosuffix");
        my %acl;
        if (&read_file("$config_directory/$m/$from.$fromsuffix",
                   \%acl)) {
            &write_file("$config_directory/$m/$to.$tosuffix",
                    \%acl);
            }
        }
    }
else {
    # Source and dest use different storage types
    foreach my $m (@$mods) {
        my %caccess;
        if ($fromtype eq "user") {
            %caccess = &get_module_acl($from, $m, 1, 1);
            }
        else {
            %caccess = &get_group_module_acl($from, $m, 1);
            }
        if (%caccess) {
            if ($totype eq "user") {
                &save_module_acl(\%caccess, $to, $m, 1);
                }
            else {
                &save_group_module_acl(\%caccess, $to, $m, 1);
                }
            }
        }
    }
if ($dbh) {
    &disconnect_userdb($userdb, $dbh);
    }
}

=head2 copy_group_acl_files(from, to, &modules)

Copy all .gacl files from some group to another in a list of modules. Parameters
are :

=item from - Source group name.

=item to - Destination group name.

=item modules - Array ref of module names.

=cut
sub copy_group_acl_files
{
my ($from, $to, $mods) = @_;
&copy_acl_files($from, $to, $mods, "group", "group");
}

=head2 copy_group_user_acl_files(from, to, &modules)

Copy all .acl files from some group to a user in a list of modules. Parameters
are :

=item from - Source group name.

=item to - Destination user name.

=item modules - Array ref of module names.

=cut
sub copy_group_user_acl_files
{
my ($from, $to, $mods) = @_;
&copy_acl_files($from, $to, $mods, "group", "user");
}

=head2 set_acl_files(&allusers, &allgroups, module, &members, &access)

Recursively update the ACL for all sub-users and groups of a group, by copying
detailed access control settings from the group down to users. Parameters are :

=item allusers - An array ref of Webmin users, as returned by list_users.

=item allgroups - An array ref of Webmin groups.

=item module - Name of the module to update ACL for.

=item members - Names of group members.

=item access - The module ACL hash ref to copy to users.

=cut
sub set_acl_files
{
my ($allusers, $allgroups, $mod, $members, $access) = @_;
foreach my $m (@$members) {
    if ($m !~ /^\@(.*)$/) {
        # Member is a user
        local ($u) = grep { $_->{'name'} eq $m } @$allusers;
        if ($u) {
            local $aclfile =
                "$config_directory/$mod/$u->{'name'}.acl";
            &lock_file($aclfile);
            &save_module_acl($access, $u->{'name'}, $mod, 1);
            chmod(0640, $aclfile) if (-r $aclfile);
            &unlock_file($aclfile);
            }
        }
    else {
        # Member is a group
        local $gname = substr($m, 1);
        local ($g) = grep { $_->{'name'} eq $gname } @$allgroups;
        if ($g) {
            local $aclfile =
                "$config_directory/$mod/$g->{'name'}.gacl";
            &lock_file($aclfile);
            &save_group_module_acl($access, $g->{'name'}, $mod, 1);
            chmod(0640, $aclfile) if (-r $aclfile);
            &unlock_file($aclfile);
            &set_acl_files($allusers, $allgroups, $mod,
                       $g->{'members'}, $access);
            }
        }
    }
}

=head2 get_ssleay

Returns the path to the openssl command (or equivalent) on this system.

=cut
sub get_ssleay
{
if (&has_command($config{'ssleay'})) {
    return &has_command($config{'ssleay'});
    }
elsif (&has_command("openssl")) {
    return &has_command("openssl");
    }
elsif (&has_command("ssleay")) {
    return &has_command("ssleay");
    }
else {
    return undef;
    }
}

=head2 encrypt_password(password, [salt])

Encrypts and returns a Webmin user password. If the optional salt parameter
is not given, a salt will be selected randomly.

=cut
sub encrypt_password
{
my ($pass, $salt) = @_;
if ($gconfig{'md5pass'}) {
    # Use MD5 encryption
    $salt ||= '$1$'.substr(time(), -8).'$xxxxxxxxxxxxxxxxxxxxxx';
    return &encrypt_md5($pass, $salt);
    }
else {
    # Use Unix DES
    &seed_random();
    $salt ||= chr(int(rand(26))+65).chr(int(rand(26))+65);
    return &unix_crypt($pass, $salt);
    }
}

=head2 get_unixauth(\%miniserv)

Returns a list of Unix users/groups/all and the Webmin user that they
authenticate as, as array references.

=cut
sub get_unixauth
{
my @rv;
my @ua = split(/\s+/, $_[0]->{'unixauth'});
foreach my $ua (@ua) {
    if ($ua =~ /^(\S+)=(\S+)$/) {
        push(@rv, [ $1, $2 ]);
        }
    else {
        push(@rv, [ "*", $ua ]);
        }
    }
return @rv;
}

=head2 save_unixauth(\%miniserv, &authlist)

Updates %miniserv with the given Unix auth list, which must be in the format
returned by get_unixauth.

=cut
sub save_unixauth
{
my @ua;
foreach my $ua (@{$_[1]}) {
    if ($ua->[0] ne "*") {
        push(@ua, "$ua->[0]=$ua->[1]");
        }
    else {
        push(@ua, $ua->[1]);
        }
    }
$_[0]->{'unixauth'} = join(" ", @ua);
}

=head2 delete_from_groups(user|@group)

Removes the specified user from all groups.

=cut
sub delete_from_groups
{
my ($user) = @_;
foreach my $g (&list_groups()) {
    my @mems = @{$g->{'members'}};
    my $i = &indexof($user, @mems);
    if ($i >= 0) {
        splice(@mems, $i, 1);
        $g->{'members'} = \@mems;
        &modify_group($g->{'name'}, $g);
        }
    }
}

=head2 check_password_restrictions(username, password)

Checks if some new password is valid for a user, and if not returns
an error message.

=cut
sub check_password_restrictions
{
my ($name, $pass) = @_;
my %miniserv;
&get_miniserv_config(\%miniserv);
my ($user) = grep { $_->{'name'} eq $name } &list_users();
my $minsize = $user ? $user->{'minsize'} : undef;
$minsize ||= $miniserv{'pass_minsize'};
if (length($pass) < $minsize) {
    return &text('cpass_minsize', $minsize);
    }
foreach my $re (split(/\t+/, $miniserv{'pass_regexps'})) {
    if ($re =~ /^\!(.*)$/) {
        $re = $1;
        $pass !~ /$re/ || return ($miniserv{'pass_regdesc'} ||
                      $text{'cpass_notre'});
        }
    else {
        $pass =~ /$re/ || return ($miniserv{'pass_regdesc'} ||
                      $text{'cpass_re'});
        }
    }
if ($miniserv{'pass_nouser'}) {
    $pass =~ /\Q$name\E/i && return $text{'cpass_name'};
    }
if ($miniserv{'pass_nodict'}) {
    local $temp = &transname();
    &open_tempfile(TEMP, ">$temp", 0, 1);
    &print_tempfile(TEMP, $pass,"\n");
    &close_tempfile(TEMP);
    local $unknown;
    if (&has_command("ispell")) {
        open(SPELL, "ispell -a <$temp |");
        while(<SPELL>) {
            if (/^(#|\&|\?)/) {
                $unknown++;
                }
            }
        close(SPELL);
        }
    elsif (&has_command("spell")) {
        open(SPELL, "spell <$temp |");
        local $line = <SPELL>;
        $unknown++ if ($line);
        close(SPELL);
        }
    else {
        return &text('cpass_spellcmd', "<tt>ispell</tt>",
                           "<tt>spell</tt>");
        }
    $unknown || return $text{'cpass_dict'};
    }
if ($miniserv{'pass_oldblock'} && $user) {
    local $c = 0;
    foreach my $o (@{$user->{'olds'}}) {
        local $enc = &encrypt_password($pass, $o);
        $enc eq $o && return $text{'cpass_old'};
        last if ($c++ > $miniserv{'pass_oldblock'});
        }
    }
return undef;
}

=head2 hash_session_id(sid)

Returns an MD5 or Unix-crypted session ID.

=cut
sub hash_session_id
{
my ($sid) = @_;
my $use_md5 = &md5_perl_module();
if (!$hash_session_id_cache{$sid}) {
        if ($use_md5) {
                # Take MD5 hash
                $hash_session_id_cache{$sid} = &hash_md5_session($sid);
                }
        else {
                # Unix crypt
                $hash_session_id_cache{$sid} = &unix_crypt($sid, "XX");
                }
        }
return $hash_session_id_cache{$sid};
}

=head2 hash_md5_session(string)

Returns a string encrypted in MD5 format.

=cut
sub hash_md5_session
{
my $passwd = $_[0];
my $use_md5 = &md5_perl_module();

# Add the password
my $ctx = eval "new $use_md5";
$ctx->add($passwd);

# Add some more stuff from the hash of the password and salt
my $ctx1 = eval "new $use_md5";
$ctx1->add($passwd);
$ctx1->add($passwd);
my $final = $ctx1->digest();
for(my $pl=length($passwd); $pl>0; $pl-=16) {
    $ctx->add($pl > 16 ? $final : substr($final, 0, $pl));
    }

# This piece of code seems rather pointless, but it's in the C code that
# does MD5 in PAM so it has to go in!
my $j = 0;
my ($i, $l);
for(my $i=length($passwd); $i; $i >>= 1) {
    if ($i & 1) {
        $ctx->add("\0");
        }
    else {
        $ctx->add(substr($passwd, $j, 1));
        }
    }
$final = $ctx->digest();

# Convert the 16-byte final string into a readable form
my $rv;
my @final = map { ord($_) } split(//, $final);
$l = ($final[ 0]<<16) + ($final[ 6]<<8) + $final[12];
$rv .= &to64($l, 4);
$l = ($final[ 1]<<16) + ($final[ 7]<<8) + $final[13];
$rv .= &to64($l, 4);
$l = ($final[ 2]<<16) + ($final[ 8]<<8) + $final[14];
$rv .= &to64($l, 4);
$l = ($final[ 3]<<16) + ($final[ 9]<<8) + $final[15];
$rv .= &to64($l, 4);
$l = ($final[ 4]<<16) + ($final[10]<<8) + $final[ 5];
$rv .= &to64($l, 4);
$l = $final[11];
$rv .= &to64($l, 2);

return $rv;
}

=head2 md5_perl_module

Returns a Perl module for MD5 hashing, or undef if none.

=cut
sub md5_perl_module
{
eval "use MD5";
if (!$@) {
        $use_md5 = "MD5";
        }
else {
        eval "use Digest::MD5";
        if (!$@) {
                $use_md5 = "Digest::MD5";
                }
        }
}

=head2 session_db_key(sid)

Returns the session DB key for some session ID. Assumes that open_session_db
has already been called.

=cut
sub session_db_key
{
my ($sid) = @_;
my $hash = &hash_session_id($sid);
return $sessiondb{$hash} ? $hash : $sid;
}

=head2 setup_anonymous_access(path, module)

Grants anonymous access to some path. By default, the user for other anonymous
access will be used, or if there is none, a user named 'anonymous' will be
created and granted access to the module.

=cut
sub setup_anonymous_access
{
my ($path, $mod) = @_;

# Find out what users and paths we grant access to currently
my %miniserv;
&get_miniserv_config(\%miniserv);
my @anon = split(/\s+/, $miniserv{'anonymous'});
my $found = 0;
my $user;
foreach my $a (@anon) {
        my ($p, $u) = split(/=/, $a);
    $found++ if ($p eq $path);
    $user = $u;
    }
return 1 if ($found);        # Already setup

if (!$user) {
    # Create a user if need be
    $user = "anonymous";
    local $uinfo = { 'name' => $user,
             'pass' => '*LK*',
             'modules' => [ $mod ],
               };
    &create_user($uinfo);
    }
else {
    # Make sure the user has the module
    local ($uinfo) = grep { $_->{'name'} eq $user } &list_users();
    if ($uinfo && &indexof($mod, @{$uinfo->{'modules'}}) < 0) {
        push(@{$uinfo->{'modules'}}, $mod);
        &modify_user($uinfo->{'name'}, $uinfo);
        }
    else {
        print STDERR "Anonymous access is granted to user $user, but he doesn't exist!\n";
        }
    }

# Grant access to the user and path
push(@anon, "$path=$user");
$miniserv{'anonymous'} = join(" ", @anon);
&put_miniserv_config(\%miniserv);
&reload_miniserv();
}

=head2 join_userdb_string(proto, user, pass, host, prefix, &args)

Creates a string in the format accepted by split_userdb_string

=cut
sub join_userdb_string
{
my ($proto, $user, $pass, $host, $prefix, $args) = @_;
return "" if (!$proto);
my $argstr;
if (keys %$args) {
    $argstr = "?".join("&", map { $_."=".$args->{$_} } (keys %$args));
    }
return $proto."://".$user.":".$pass."\@".$host."/".$prefix.$argstr;
}

=head2 validate_userdb(string, [no-table-check])

Checks if some user database is usable, and if not returns an error message

=cut
sub validate_userdb
{
my ($str, $notablecheck) = @_;
my ($proto, $user, $pass, $host, $prefix, $args) = &split_userdb_string($str);
if ($proto eq "mysql" || $proto eq "postgresql") {
    # Load DBI driver
    eval 'use DBI;';
    return &text('sql_emod', 'DBI') if ($@);
    if ($proto eq "mysql") {
        eval 'use DBD::mysql;';
        return &text('sql_emod', 'DBD::mysql') if ($@);
        my $drh = DBI->install_driver("mysql");
        return $text{'sql_emysqldriver'} if (!$drh);
        }
    else {
        eval 'use DBD::Pg;';
        return &text('sql_emod', 'DBD::Pg') if ($@);
        my $drh = DBI->install_driver("Pg");
        return $text{'sql_epostgresqldriver'} if (!$drh);
        }

    # Connect to the database
    my $dbh = &connect_userdb($str);
    ref($dbh) || return $dbh;

    # Validate critical tables
    if (!$notablecheck) {
        my %tables =
          ( "webmin_user" => [ "id", "name", "pass" ],
            "webmin_group" => [ "id", "name", "description" ],
            "webmin_user_attr" => [ "id", "attr", "value" ],
            "webmin_group_attr" => [ "id", "attr", "value" ],
            "webmin_user_acl" => [ "id", "module", "attr", "value" ],
            "webmin_group_acl" => [ "id", "module", "attr", "value"],
          );
        foreach my $t (keys %tables) {
            my @cols = @{$tables{$t}};
            my $sql = "select ".join(",", @cols)." from $t limit 1";
            my $cmd = $dbh->prepare($sql);
            if (!$cmd || !$cmd->execute()) {
                return &text('sql_etable', $t,
                         &html_escape($dbh->errstr));
                }
            $cmd->finish();
            }
        }
    &disconnect_userdb($str, $dbh);
    return undef;
    }
elsif ($proto eq "ldap") {
    # Load LDAP module
    eval 'use Net::LDAP;';
    return &text('sql_emod', 'Net::LDAP') if ($@);

    # Try to connect
    my $dbh = &connect_userdb($str);
    ref($dbh) || return $dbh;

    # Check for Webmin object classes
    my $schema = $dbh->schema();
    my @allocs = map { $_->{'name'} }
            $schema->all_objectclasses();
    &indexof($args->{'userclass'}, @allocs) >= 0 ||
        return &text('sql_eclass', $args->{'userclass'});
    &indexof($args->{'groupclass'}, @allocs) >= 0 ||
        return &text('sql_eclass', $args->{'groupclass'});

    # Check that base DN exists
    if (!$notablecheck) {
        my $superprefix = $prefix;
        $superprefix =~ s/^[^,]+,//;    # Make parent DN
        my $rv = $dbh->search(base => $superprefix,
                      filter => '(objectClass=*)',
                      scope => 'one');
        my $niceprefix = lc($prefix);
        $niceprefix =~ s/\s//g;
        my $found = 0;
        foreach my $d ($rv->all_entries) {
            my $niced = lc($d->dn());
            $niced =~ s/\s//g;
            $found++ if ($niced eq $niceprefix);
            }
        $found || return &text('sql_eldapdn', $prefix);
        }
    &disconnect_userdb($str, $dbh);
    return undef;
    }
else {
    return "Unknown user database type $proto";
    }
}

=head2 userdb_table_sql(string)

Returns SQL statements needed to create all required tables. Mainly for
internal use.

=cut
sub userdb_table_sql
{
my ($str) = @_;
my ($key, $auto, $idattrkey);
if ($str =~ /^mysql:/) {
    return ( "create table webmin_user ".
           "(id int(20) not null primary key auto_increment, ".
           "name varchar(255) not null, pass varchar(255))",
         "create table webmin_group ".
           "(id int(20) not null primary key auto_increment, ".
           "name varchar(255) not null, ".
           "description varchar(255))",
         "create table webmin_user_attr ".
           "(id int(20) not null, ".
           "attr varchar(255) not null, ".
           "value varchar(4096), ".
           "primary key(id, attr))",
         "create table webmin_group_attr ".
           "(id int(20) not null, ".
           "attr varchar(255) not null, ".
           "value varchar(4096), ".
           "primary key(id, attr))",
         "create table webmin_user_acl ".
           "(id int(20) not null, ".
           "module varchar(32) not null, ".
           "attr varchar(32) not null, ".
           "value varchar(255), ".
           "primary key(id, module, attr))",
         "create table webmin_group_acl ".
           "(id int(20) not null, ".
           "module varchar(32) not null, ".
           "attr varchar(32) not null, ".
           "value varchar(255), ".
           "primary key(id, module, attr))",
        );
    }
elsif ($str =~ /^postgresql:/) {
    return ( "create table webmin_user ".
           "(id serial not null primary key, ".
           "name varchar(255), ".
           "pass varchar(255))",
         "create table webmin_group ".
           "(id serial not null primary key, ".
           "name varchar(255), ".
           "description varchar(255))",
         "create table webmin_user_attr ".
           "(id int8 not null, ".
           "attr varchar(255) not null, ".
           "value varchar(4096), ".
           "primary key(id, attr))",
         "create table webmin_group_attr ".
           "(id int8 not null, ".
           "attr varchar(255) not null, ".
           "value varchar(4096), ".
           "primary key(id, attr))",
         "create table webmin_user_acl ".
           "(id int8 not null, ".
           "module varchar(255) not null, ".
           "attr varchar(255) not null, ".
           "value varchar(255), ".
           "primary key(id, module, attr))",
         "create table webmin_group_acl ".
           "(id int8 not null, ".
           "module varchar(255) not null, ".
           "attr varchar(255) not null, ".
           "value varchar(255), ".
           "primary key(id, module, attr))",
           );
    }
}

1;


:: Command execute ::

Enter:
 
Select:
 

:: Shadow's tricks :D ::

Useful Commands
 
Warning. Kernel may be alerted using higher levels
Kernel Info:

:: Preddy's tricks :D ::

Php Safe-Mode Bypass (Read Files)

File:

eg: /etc/passwd

Php Safe-Mode Bypass (List Directories):

Dir:

eg: /etc/

:: Search ::
  - regexp 

:: Upload ::
 
[ Read-Only ]

:: Make Dir ::
 
[ Read-Only ]
:: Make File ::
 
[ Read-Only ]

:: Go Dir ::
 
:: Go File ::
 

--[ c999shell v. 1.0 pre-release build #16 Modded by Shadow & Preddy | RootShell Security Group | r57 c99 shell | Generation time: 0.0194 ]--