Batch group creation / deletion

This commit is contained in:
Jamie Cameron
2008-12-14 03:54:39 +00:00
parent 2878c2150d
commit 572ccca8b7
5 changed files with 497 additions and 3 deletions

View File

@ -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
View 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
View 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'});

View File

@ -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

View File

@ -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'});
}
}