mirror of
https://github.com/webmin/webmin.git
synced 2025-07-29 11:50:54 +00:00
Batch group creation / deletion
This commit is contained in:
@ -53,3 +53,4 @@ Improved support for default password changing options on AIX.
|
||||
Added a non-editable list of users who have this group as their primary to the Edit Group page.
|
||||
Added a Module Config option to use a text box for entering secondary group members, rather than the left/right user chooser.
|
||||
Created a page for exporting groups to a batch file, for importing on other systems.
|
||||
Added support for creating, deleting and modifying groups from a batch file. This is similar to the long-standing batch user management functionality.
|
||||
|
418
useradmin/gbatch_exec.cgi
Executable file
418
useradmin/gbatch_exec.cgi
Executable file
@ -0,0 +1,418 @@
|
||||
#!/usr/local/bin/perl
|
||||
# Execute create/modify/delete group commands in a batch file
|
||||
|
||||
require './user-lib.pl';
|
||||
$access{'batch'} || &error($text{'gbatch_ecannot'});
|
||||
if ($ENV{'REQUEST_METHOD'} eq 'GET') {
|
||||
&ReadParse();
|
||||
}
|
||||
else {
|
||||
&ReadParseMime();
|
||||
}
|
||||
if ($in{'source'} == 0) {
|
||||
$data = $in{'file'};
|
||||
$data =~ /\S/ || &error($text{'batch_efile'});
|
||||
}
|
||||
elsif ($in{'source'} == 1) {
|
||||
open(LOCAL, $in{'local'}) || &error($text{'batch_elocal'});
|
||||
while(<LOCAL>) {
|
||||
$data .= $_;
|
||||
}
|
||||
close(LOCAL);
|
||||
}
|
||||
elsif ($in{'source'} == 2) {
|
||||
$data = $in{'text'};
|
||||
$data =~ /\S/ || &error($text{'batch_etext'});
|
||||
}
|
||||
|
||||
&ui_print_unbuffered_header(undef, $text{'gbatch_title'}, "");
|
||||
|
||||
# Force defaults for save options
|
||||
$in{'chgid'} = 1 if (!$access{'chgid'});
|
||||
|
||||
# Work out a good base GID for new groups
|
||||
&build_group_used(\%gused, \%gtaken);
|
||||
$newgid = int($config{'base_gid'} > $access{'lowgid'} ?
|
||||
$config{'base_gid'} : $access{'lowgid'});
|
||||
|
||||
# Process the file
|
||||
&batch_start() if ($in{'batch'});
|
||||
&lock_user_files();
|
||||
$lnum = $created = $modified = $deleted = 0;
|
||||
print "<pre>\n";
|
||||
$pft = &passfiles_type();
|
||||
foreach $line (split(/[\r\n]+/, $data)) {
|
||||
$lnum++;
|
||||
$line =~ s/^\s*#.*$//;
|
||||
next if ($line !~ /\S/);
|
||||
local @line = split(/:/, $line, -1);
|
||||
local %group;
|
||||
if ($line[0] eq 'create') {
|
||||
# Creating a new group
|
||||
|
||||
# Validate line
|
||||
if (!$line[1]) {
|
||||
print &text('batch_eline', $lnum),"\n";
|
||||
next;
|
||||
}
|
||||
if (@line != 5) {
|
||||
print &text('batch_elen', $lnum, 5),"\n";
|
||||
next;
|
||||
}
|
||||
if ($line[1] !~ /^[^:\t]+$/) {
|
||||
print &text('gbatch_egroupname', $lnum),"\n";
|
||||
next;
|
||||
}
|
||||
$group{'group'} = $line[1];
|
||||
|
||||
if ($gtaken{$group{'group'}}) {
|
||||
print &text('gbatch_egroup', $lnum,
|
||||
$group{'group'}),"\n";
|
||||
next;
|
||||
}
|
||||
if ($line[3] !~ /^\d+$/) {
|
||||
# make up a GID
|
||||
while($gused{$newgid}) {
|
||||
$newgid++;
|
||||
}
|
||||
$group{'gid'} = $newgid;
|
||||
}
|
||||
else {
|
||||
# use the given UID
|
||||
if ($gused{$line[3]} && !$access{'gmultiple'}) {
|
||||
print &text('gbatch_ecaccess', $lnum,
|
||||
$text{'gsave_egidused2'}),"\n";
|
||||
next;
|
||||
}
|
||||
$group{'gid'} = $line[3];
|
||||
}
|
||||
$gused{$group{'gid'}}++;
|
||||
$group{'members'} = $line[4];
|
||||
|
||||
# Check access control restrictions
|
||||
if (!$access{'gcreate'}) {
|
||||
print &text('gbatch_ecaccess', $lnum,
|
||||
$text{'gsave_ecreate'});
|
||||
next;
|
||||
}
|
||||
local $ch = &check_group(\%group);
|
||||
if ($ch) {
|
||||
print &text('gbatch_ecaccess', $lnum, $ch),"\n";
|
||||
next;
|
||||
}
|
||||
|
||||
if ($line[2] eq '') {
|
||||
# No password needed
|
||||
$group{'pass'} = '';
|
||||
$group{'passmode'} = 0;
|
||||
}
|
||||
else {
|
||||
# Normal password
|
||||
$group{'pass'} = &encrypt_password($line[2]);
|
||||
$group{'passmode'} = 3;
|
||||
$group{'plainpass'} = $line[2];
|
||||
}
|
||||
|
||||
# Run the before command
|
||||
&set_user_envs(\%group, 'CREATE_GROUP');
|
||||
$merr = &making_changes();
|
||||
&error(&text('gsave_emaking', "<tt>$merr</tt>"))
|
||||
if (defined($merr));
|
||||
|
||||
# Create the group!
|
||||
&create_group(\%group);
|
||||
|
||||
# All done
|
||||
&made_changes();
|
||||
|
||||
# Call other modules, ignoring any failures
|
||||
$error_must_die = 1;
|
||||
eval {
|
||||
&other_modules("useradmin_create_group", \%group)
|
||||
if ($access{'cothers'} == 1 && $in{'others'} ||
|
||||
$access{'cothers'} == 0);
|
||||
};
|
||||
$other_err = $@;
|
||||
$error_must_die = 0;
|
||||
|
||||
print "<b>",&text('gbatch_created', $group{'group'}),"</b>\n";
|
||||
print "<b><i>",&text('batch_eother', $other_err),"</i></b>\n"
|
||||
if ($other_err);
|
||||
$created++;
|
||||
}
|
||||
elsif ($line[0] eq 'delete') {
|
||||
# Deleting an existing group
|
||||
if (@line != 2) {
|
||||
print &text('batch_elen', $lnum, 2),"\n";
|
||||
next;
|
||||
}
|
||||
local @glist = &list_groups();
|
||||
local ($group) = grep { $_->{'group'} eq $line[1] } @glist;
|
||||
if (!$group) {
|
||||
print &text('gbatch_enogroup', $lnum, $line[1]),"\n";
|
||||
next;
|
||||
}
|
||||
|
||||
# Check if deletion is allowed
|
||||
if (!&can_edit_group(\%access, $group)) {
|
||||
print &text('gbatch_edaccess', $lnum,
|
||||
$text{'gdel_egroup'}),"\n";
|
||||
next;
|
||||
}
|
||||
if (!$config{'delete_root'} && $group->{'gid'} <= 10) {
|
||||
print &text('gbatch_edaccess', $lnum,
|
||||
$text{'gdel_egroup'}),"\n";
|
||||
next;
|
||||
}
|
||||
|
||||
# Check if has primary members
|
||||
local $prim;
|
||||
foreach $u (&list_users()) {
|
||||
if ($u->{'gid'} == $group->{'gid'}) {
|
||||
$prim = $u;
|
||||
last;
|
||||
}
|
||||
}
|
||||
if ($prim) {
|
||||
print &text('gbatch_eprimary', $lnum,
|
||||
$prim->{'user'}),"\n";
|
||||
next;
|
||||
}
|
||||
|
||||
# Run the before command
|
||||
&set_user_envs($group, 'DELETE_GROUP');
|
||||
$merr = &making_changes();
|
||||
&error(&text('usave_emaking', "<tt>$merr</tt>"))
|
||||
if (defined($merr));
|
||||
|
||||
# Delete from other modules, ignoring errors
|
||||
$error_must_die = 1;
|
||||
eval {
|
||||
&other_modules("useradmin_delete_group", $group)
|
||||
if ($access{'dothers'} == 1 && $in{'others'} ||
|
||||
$access{'dothers'} == 0);
|
||||
};
|
||||
$other_err = $@;
|
||||
$error_must_die = 0;
|
||||
|
||||
# Delete the user entry
|
||||
&delete_group($group);
|
||||
|
||||
&made_changes();
|
||||
|
||||
print "<b>",&text('gbatch_deleted',$group->{'group'}),"</b>\n";
|
||||
print "<b><i>",&text('batch_eother', $other_err),"</i></b>\n"
|
||||
if ($other_err);
|
||||
$deleted++;
|
||||
}
|
||||
elsif ($line[0] eq 'modify') {
|
||||
# Modifying an existing user
|
||||
local $wlen = $pft == 5 ? 11 :
|
||||
$pft == 4 ? 13 :
|
||||
$pft == 2 ? 14 :
|
||||
$pft == 1 || $pft == 6 ? 12 : 9;
|
||||
if (@line != $wlen) {
|
||||
print &text('batch_elen', $lnum, $wlen),"\n";
|
||||
next;
|
||||
}
|
||||
local @ulist = &list_users();
|
||||
local ($user) = grep { $_->{'user'} eq $line[1] } @ulist;
|
||||
if (!$user) {
|
||||
print &text('batch_enouser', $lnum, $line[1]),"\n";
|
||||
next;
|
||||
}
|
||||
%olduser = %user = %$user;
|
||||
$user{'olduser'} = $user->{'user'};
|
||||
if (!&can_edit_user(\%access, \%user)) {
|
||||
print &text('batch_emaccess', $lnum,
|
||||
$text{'usave_eedit'}),"\n";
|
||||
next;
|
||||
}
|
||||
|
||||
# Update supplied fields
|
||||
if ($line[2] ne '') {
|
||||
if (!$access{'urename'}) {
|
||||
print &text('batch_erename', $lnum, $line[1]),"\n";
|
||||
}
|
||||
$user{'user'} = $line[2];
|
||||
}
|
||||
if ($in{'crypt'} && $line[3] ne '') {
|
||||
# Changing to pre-encrypted password
|
||||
$user{'pass'} = $line[3];
|
||||
$user{'passmode'} = 2;
|
||||
}
|
||||
elsif ($line[3] eq 'x') {
|
||||
# No login allowed
|
||||
$user{'pass'} = $config{'lock_string'};
|
||||
$user{'passmode'} = 1;
|
||||
}
|
||||
elsif ($line[3] ne '') {
|
||||
# Normal password
|
||||
$user{'pass'} = &encrypt_password($line[3]);
|
||||
$user{'passmode'} = 3;
|
||||
$user{'plainpass'} = $line[3];
|
||||
}
|
||||
else {
|
||||
# No change
|
||||
$user{'passmode'} = 4;
|
||||
}
|
||||
$user{'uid'} = $line[4] if ($line[4] ne '');
|
||||
$user{'gid'} = $line[5] if ($line[5] ne '');
|
||||
$user{'real'} = $line[6] if ($line[6] ne '');
|
||||
$user{'home'} = $line[7] if ($line[7] ne '');
|
||||
$user{'shell'} = $line[8] if ($line[8] ne '');
|
||||
if ($access{'peopt'}) {
|
||||
if ($pft == 5) {
|
||||
# Openserver password and short shadow
|
||||
$user{'min'}=$line[9] if ($line[9] ne '');
|
||||
$user{'max'}=$line[10] if ($line[10] ne '');
|
||||
$user{'change'}=int(time() / (60*60*24))
|
||||
if ($line[3] ne '');
|
||||
}
|
||||
elsif ($pft == 4) {
|
||||
# AIX password and security information
|
||||
$user{'min'}=$line[9] if ($line[9] ne '');
|
||||
$user{'max'}=$line[10] if ($line[10] ne '');
|
||||
$user{'expire'}=$line[11] if ($line[11] ne '');
|
||||
if ($line[12] ne '') {
|
||||
delete($user{'admin'});
|
||||
delete($user{'admchg'});
|
||||
delete($user{'nocheck'});
|
||||
map { $user{$_}++ }
|
||||
split(/\s+/, $line[12]);
|
||||
}
|
||||
$user{'change'}=time() if ($line[3] ne '');
|
||||
}
|
||||
elsif ($pft == 2) {
|
||||
# SYSV-style passwd and shadow information
|
||||
$user{'min'}=$line[9] if ($line[9] ne '');
|
||||
$user{'max'}=$line[10] if ($line[10] ne '');
|
||||
$user{'warn'}=$line[11] if ($line[11] ne '');
|
||||
$user{'inactive'}=$line[12]
|
||||
if ($line[12] ne '');
|
||||
$user{'expire'}=$line[13] if ($line[13] ne '');
|
||||
$user{'change'}=int(time() / (60*60*24))
|
||||
if ($line[3] ne '');
|
||||
}
|
||||
elsif ($pft == 1 || $pft == 6) {
|
||||
# BSD master.passwd information
|
||||
$user{'class'}=$line[9] if ($line[9] ne '');
|
||||
$user{'change'}=$line[10] if ($line[10] ne '');
|
||||
$user{'expire'}=$line[11] if ($line[11] ne '');
|
||||
}
|
||||
}
|
||||
|
||||
# Check access control restrictions
|
||||
local $ch = &check_user(\%user, \%olduser);
|
||||
if ($ch) {
|
||||
print &text('batch_emaccess', $lnum, $ch),"\n";
|
||||
next;
|
||||
}
|
||||
|
||||
# Run the before command
|
||||
&set_user_envs(\%user, 'MODIFY_USER', $user{'plainpass'},
|
||||
[ &secondary_groups($user{'user'}) ]);
|
||||
$merr = &making_changes();
|
||||
&error(&text('usave_emaking', "<tt>$merr</tt>"))
|
||||
if (defined($merr));
|
||||
|
||||
# Move home directory if needed
|
||||
if ($olduser{'home'} ne $user{'home'} && $in{'movehome'} &&
|
||||
$user{'home'} ne '/' && $olduser{'home'} ne '/') {
|
||||
if (-d $olduser{'home'} && !-e $user{'home'}) {
|
||||
local $out = &backquote_logged(
|
||||
"mv \"$olduser{'home'}\" ".
|
||||
"\"$user{'home'}\" 2>&1");
|
||||
if ($?) { &error(&text('batch_emove',
|
||||
$lnum, $out)); }
|
||||
}
|
||||
}
|
||||
|
||||
# Change UIDs and GIDs
|
||||
if ($olduser{'gid'} != $user{'gid'} && $in{'chgid'}) {
|
||||
if ($in{'chgid'} == 1) {
|
||||
&recursive_change($user{'home'},$olduser{'uid'},
|
||||
$olduser{'gid'}, -1, $user{'gid'});
|
||||
}
|
||||
else {
|
||||
&recursive_change("/", $olduser{'uid'},
|
||||
$olduser{'gid'}, -1, $user{'gid'});
|
||||
}
|
||||
}
|
||||
if ($olduser{'uid'} != $user{'uid'} && $in{'chuid'}) {
|
||||
if ($in{'chuid'} == 1) {
|
||||
&recursive_change($user{'home'},$olduser{'uid'},
|
||||
-1, $user{'uid'}, -1);
|
||||
}
|
||||
else {
|
||||
&recursive_change("/", $olduser{'uid'},
|
||||
-1, $user{'uid'}, -1);
|
||||
}
|
||||
}
|
||||
|
||||
# Actually modify the user
|
||||
&modify_user(\%olduser, \%user);
|
||||
|
||||
# If the user has been renamed, update any secondary groups
|
||||
if ($olduser{'user'} ne $user{'user'}) {
|
||||
foreach $group (@glist) {
|
||||
local @mems = split(/,/, $group->{'members'});
|
||||
local $idx = &indexof($olduser{'user'}, @mems);
|
||||
if ($idx >= 0) {
|
||||
$mems[$idx] = $user{'user'};
|
||||
$group->{'members'} = join(",", @mems);
|
||||
&modify_group($group, $group);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&made_changes();
|
||||
|
||||
# Modify in other modules, ignoring errors
|
||||
$error_must_die = 1;
|
||||
eval {
|
||||
&other_modules("useradmin_modify_user",
|
||||
\%user, \%olduser)
|
||||
if ($access{'mothers'} == 1 && $in{'others'} ||
|
||||
$access{'mothers'} == 0);
|
||||
};
|
||||
$error_must_die = 0;
|
||||
$other_err = $@;
|
||||
|
||||
print "<b>",&text('batch_modified',$olduser{'user'}),"</b>\n";
|
||||
print "<b><i>",&text('batch_eother', $other_err),"</i></b>\n"
|
||||
if ($other_err);
|
||||
$modified++;
|
||||
}
|
||||
else {
|
||||
print &text('batch_eaction', $lnum, $line[0]),"\n";
|
||||
next;
|
||||
}
|
||||
}
|
||||
print "</pre>\n";
|
||||
&batch_end() if ($in{'batch'});
|
||||
&unlock_user_files();
|
||||
&webmin_log("gbatch", undef, $in{'source'} == 1 ? $in{'local'} : undef,
|
||||
{ 'created' => $created, 'modified' => $modified,
|
||||
'deleted' => $deleted, 'lnum' => $lnum } );
|
||||
|
||||
&ui_print_footer("gbatch_form.cgi", $text{'batch_return'},
|
||||
"index.cgi?mode=groups", $text{'index_return'});
|
||||
|
||||
# check_group(\%group, [\%oldgroup])
|
||||
# Check access control restrictions for a group
|
||||
sub check_group
|
||||
{
|
||||
# check if gid is within range
|
||||
if ($access{'lowgid'} && $_[0]->{'gid'} < $access{'lowgid'}) {
|
||||
return &text('usave_elowgid', $access{'lowuid'});
|
||||
}
|
||||
if ($access{'hiuid'} && $_[0]->{'uid'} > $access{'hiuid'}) {
|
||||
return &text('usave_ehiuid', $access{'hiuid'});
|
||||
}
|
||||
if ($_[1] && !$access{'ggid'} && $_[1]->{'gid'} != $_[0]->{'gid'}) {
|
||||
return $text{'gsave_eggid'};
|
||||
}
|
||||
return undef;
|
||||
}
|
||||
|
51
useradmin/gbatch_form.cgi
Executable file
51
useradmin/gbatch_form.cgi
Executable file
@ -0,0 +1,51 @@
|
||||
#!/usr/local/bin/perl
|
||||
# Display a form for doing batch group creation, updates or deletion from
|
||||
# a text file
|
||||
|
||||
require './user-lib.pl';
|
||||
$access{'batch'} || &error($text{'gbatch_ecannot'});
|
||||
&ui_print_header(undef, $text{'gbatch_title'}, "");
|
||||
|
||||
# Instructions
|
||||
print &ui_hidden_start($text{'batch_instr'}, "instr", 0, "batch_form.cgi");
|
||||
print "$text{'gbatch_desc'}<p>\n";
|
||||
print "$text{'gbatch_descafter'}<br>\n";
|
||||
print "$text{'gbatch_descafter2'}\n";
|
||||
print &ui_hidden_end("instr");
|
||||
|
||||
print &ui_form_start("gbatch_exec.cgi", "form-data");
|
||||
print &ui_table_start($text{'gbatch_header'}, undef, 2);
|
||||
|
||||
# Source file
|
||||
print &ui_table_row($text{'batch_source'},
|
||||
&ui_radio_table("source", 0,
|
||||
[ [ 0, $text{'batch_source0'}, &ui_upload("file") ],
|
||||
[ 1, $text{'batch_source1'}, &ui_textbox("local", undef, 40)." ".
|
||||
&file_chooser_button("local") ],
|
||||
[ 2, $text{'batch_source2'}, &ui_textarea("text", undef, 5, 60) ]
|
||||
]));
|
||||
|
||||
if ($access{'cothers'} == 1 || $access{'mothers'} == 1 ||
|
||||
$access{'dothers'} == 1) {
|
||||
# Do other modules?
|
||||
print &ui_table_row($text{'gbatch_others'},
|
||||
&ui_yesno_radio("others", int($config{'default_other'})));
|
||||
}
|
||||
|
||||
# Only run post-command at end?
|
||||
print &ui_table_row($text{'gbatch_batch'},
|
||||
&ui_yesno_radio("batch", 0));
|
||||
|
||||
if ($access{'chgid'}) {
|
||||
# Update GIDs on files
|
||||
print &ui_table_row($text{'gbatch_chgid'},
|
||||
&ui_radio("chgid", 1, [ [ 0, $text{'no'} ],
|
||||
[ 1, $text{'home'} ],
|
||||
[ 2, $text{'uedit_allfiles'} ] ]));
|
||||
}
|
||||
|
||||
print &ui_table_end();
|
||||
print &ui_form_end([ [ undef, $text{'batch_upload'} ] ]);
|
||||
|
||||
&ui_print_footer("", $text{'index_return'});
|
||||
|
@ -357,6 +357,10 @@ log_batch=Executed batch file $1
|
||||
log_batch_l=Executed batch file $1 ($2 created, $3 modified, $4 deleted)
|
||||
log_ubatch=Executed uploaded batch file
|
||||
log_ubatch_l=Executed uploaded batch file ($1 created, $2 modified, $3 deleted)
|
||||
log_gbatch=Executed group batch file $1
|
||||
log_gbatch_l=Executed group batch file $1 ($2 created, $3 modified, $4 deleted)
|
||||
log_ugbatch=Executed uploaded group batch file
|
||||
log_ugbatch_l=Executed uploaded group batch file ($1 created, $2 modified, $3 deleted)
|
||||
|
||||
batch_title=Execute Batch File
|
||||
batch_ecannot=You cannot use the batch file form
|
||||
@ -495,3 +499,23 @@ emass_pass=Unlocking password ..
|
||||
emass_doing=Enabling user $1 ..
|
||||
emass_already=.. already enabled!
|
||||
|
||||
gbatch_title=Execute Group Batch File
|
||||
gbatch_ecannot=You cannot use the group batch file form
|
||||
batch_desc=This form allows you to create, modify or delete many groups at once from an uploaded or local text file. Each line in the file specifies one action to take, depending on its first field. The line formats are :
|
||||
gbatch_desc=<b>create</b>:groupname:passwd:gid:member,member,...<p><b>modify</b>:oldgroupname:groupname:passwd:gid:member,member,...<p><b>delete</b>:groupname
|
||||
gbatch_descafter=In <b>create</b> lines, if the <tt>gid</tt> field is left empty, Webmin will assign a GID automatically.
|
||||
gbatch_descafter2=In <b>modify</b> lines, an empty field will be taken to mean that the corresponding group attribute is not to be modified.
|
||||
gbatch_header=Batch group creation, update and deletion options
|
||||
gbatch_others=Create, modify or delete groups in other modules?
|
||||
gbatch_batch=Only update groups file when batch is complete?
|
||||
gbatch_chgid=Change GID on files of modified groups?
|
||||
gbatch_ecaccess=You are not allowed to create the group at line $1 : $2
|
||||
gbatch_emaccess=You are not allowed to modify the group at line $1 : $2
|
||||
gbatch_edaccess=You are not allowed to delete the group at line $1 : $2
|
||||
gbatch_created=Created group $1
|
||||
gbatch_deleted=Deleted group $1
|
||||
gbatch_modified=Modified group $1
|
||||
gbatch_enogroup=Group does not exist at line $1 : $2
|
||||
gbatch_eprimary=Group at line $1 cannot be deleted, as it is the primary group of user $2.
|
||||
gbatch_egroup=Duplicate group name at line $1 : $2
|
||||
gbatch_egroupname=Invalid group name at line $1
|
||||
|
@ -40,14 +40,14 @@ elsif ($type eq 'group') {
|
||||
return &text('log_gdelete', "<tt>$object</tt>");
|
||||
}
|
||||
}
|
||||
elsif ($action eq 'batch') {
|
||||
elsif ($action eq 'batch' || $action eq 'gbatch') {
|
||||
if ($object =~ /^\//) {
|
||||
return &text($long ? 'log_batch_l' : 'log_batch',
|
||||
return &text($long ? 'log_'.$action.'_l' : 'log_'.$action,
|
||||
"<tt>$object</tt>", $p->{'created'},
|
||||
$p->{'modified'}, $p->{'deleted'});
|
||||
}
|
||||
else {
|
||||
return &text($long ? 'log_ubatch_l' : 'log_ubatch',
|
||||
return &text($long ? 'log_u'.$action.'_l' : 'log_u'.$action,
|
||||
$p->{'created'}, $p->{'modified'},$p->{'deleted'});
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user