3 # Execute create/modify/delete commands in a batch file
5 require './user-lib.pl';
6 $access{'batch'} || &error($text{'batch_ecannot'});
7 if ($ENV{'REQUEST_METHOD'} eq 'GET') {
13 if ($in{'source'} == 0) {
15 $data =~ /\S/ || &error($text{'batch_efile'});
17 elsif ($in{'source'} == 1) {
18 open(LOCAL, $in{'local'}) || &error($text{'batch_elocal'});
24 elsif ($in{'source'} == 2) {
26 $data =~ /\S/ || &error($text{'batch_etext'});
29 &ui_print_unbuffered_header(undef, $text{'batch_title'}, "");
31 # Force defaults for save options
32 $in{'makehome'} = 1 if (!$access{'makehome'});
33 $in{'copy'} = 1 if (!$access{'copy'} && $config{'user_files'} =~ /\S/);
34 $in{'movehome'} = 1 if (!$access{'movehome'});
35 $in{'chuid'} = 1 if (!$access{'chuid'});
36 $in{'chgid'} = 1 if (!$access{'chgid'});
38 # Work out a good base UID for new users
39 &build_user_used(\%used, undef, \%taken);
40 $newuid = int($config{'base_uid'} > $access{'lowuid'} ?
41 $config{'base_uid'} : $access{'lowuid'});
43 # Work out a good base GID for new groups
44 &build_group_used(\%gused, \%gtaken);
45 if ($config{'new_user_gid'}) {
46 %used = ( %used, %gused );
48 $newgid = int($config{'base_gid'} > $access{'lowgid'} ?
49 $config{'base_gid'} : $access{'lowgid'});
50 @glist = &list_groups();
53 &batch_start() if ($in{'batch'});
55 $lnum = $created = $modified = $deleted = 0;
57 $pft = &passfiles_type();
58 foreach $line (split(/[\r\n]+/, $data)) {
60 $line =~ s/^\s*#.*$//;
61 next if ($line !~ /\S/);
62 local @line = split(/:/, $line, -1);
64 if ($line[0] eq 'create') {
67 # Openserver passwd and short shadow information
69 print &text('batch_elen', $lnum, 10),"\n";
72 $user{'min'} = $line[8];
73 $user{'max'} = $line[9];
76 # AIX passwd and security information
78 print &text('batch_elen', $lnum, 12),"\n";
81 $user{'min'} = $line[8];
82 $user{'max'} = $line[9];
83 $user{'expire'} = $line[10];
84 map { $user{$_}++ } split(/\s+/, $line[11]);
87 # SYSV-style passwd and shadow information
89 print &text('batch_elen', $lnum, 13),"\n";
92 $user{'min'} = $line[8];
93 $user{'max'} = $line[9];
94 $user{'warn'} = $line[10];
95 $user{'inactive'} = $line[11];
96 $user{'expire'} = $line[12];
97 $user{'change'} = $line[2] eq '' ? '' :
98 int(time() / (60*60*24));
100 elsif ($pft == 1 || $pft == 6) {
101 # BSD master.passwd information
103 print &text('batch_elen', $lnum, 11),"\n";
106 $user{'class'} = $line[8];
107 $user{'change'} = $line[9];
108 $user{'expire'} = $line[10];
111 # Classic passwd file information (type 0 and 3)
113 print &text('batch_elen', $lnum, 8),"\n";
118 # Make sure all min/max fields are numeric
119 $err = &validate_batch_minmax(\%user, $lnum);
125 # Parse common fields
127 print &text('batch_eline', $lnum),"\n";
130 $user{'user'} = $line[1];
131 $err = &check_username_restrictions($user{'user'});
133 print &text('batch_echeck', $lnum, $err),"\n";
136 if ($taken{$user{'user'}}) {
137 print &text('batch_euser', $lnum, $user{'user'}),"\n";
140 if ($line[3] !~ /^\d+$/) {
142 while($used{$newuid}) {
145 $user{'uid'} = $newuid;
149 if ($used{$line[3]} && !$access{'umultiple'}) {
150 print &text('batch_ecaccess', $lnum,
151 $text{'usave_euidused2'}),"\n";
154 $user{'uid'} = $line[3];
156 $used{$user{'uid'}}++;
157 if ($line[7] !~ /^\//) {
158 print &text('batch_eshell', $lnum, $line[7]),"\n";
161 $user{'shell'} = $line[7];
162 $user{'real'} = $line[5];
163 local @gids = split(/[ ,]+/, $line[4]);
164 $user{'gid'} = $gids[0];
165 local $grp = &my_getgrgid($gids[0]);
168 if ($access{'autohome'}) {
169 # Assign home dir automatically based on ACL
170 $user{'home'} = &auto_home_dir($access{'home'},
173 if ($config{'real_base'}) {
174 $real_home = &auto_home_dir(
175 $config{'real_base'}, $user{'user'}, $grp);
179 if ($line[6] eq '' && $config{'home_base'}) {
180 # Choose home dir automatically based on
182 $user{'home'} = &auto_home_dir(
183 $config{'home_base'}, $user{'user'},
185 if ($config{'real_base'}) {
186 $real_home = &auto_home_dir(
187 $config{'real_base'},
188 $user{'user'}, $grp);
191 elsif ($line[6] !~ /^\//) {
192 print &text('batch_ehome', $lnum,$line[6]),"\n";
197 $user{'home'} = $line[6];
200 $real_home ||= $user{'home'};
202 # Check access control restrictions
203 if (!$access{'ucreate'}) {
204 print &text('batch_ecaccess', $lnum,
205 $text{'usave_ecreate'});
208 local $ch = &check_user(\%user);
210 print &text('batch_ecaccess', $lnum, $ch),"\n";
214 # Work out secondary group membership
218 for($i=1; $i<@gids; $i++) {
220 grep { $_->{'gid'} eq $gids[$i] } @glist;
221 push(@secs, $group) if ($group);
225 # Work out the password
227 $user{'pass'} = $line[2];
228 $user{'passmode'} = 2;
230 elsif ($line[2] eq 'x') {
232 $user{'pass'} = $config{'lock_string'};
233 $user{'passmode'} = 1;
235 elsif ($line[2] eq '') {
238 $user{'passmode'} = 0;
242 $user{'pass'} = &encrypt_password($line[2]);
243 $user{'passmode'} = 3;
244 $user{'plainpass'} = $line[2];
247 # Run the before command
248 &set_user_envs(\%user, 'CREATE_USER', $user{'plainpass'},
249 [ map { $_->{'gid'} } @secs ]);
250 $merr = &making_changes();
251 &error(&text('usave_emaking', "<tt>$merr</tt>"))
254 if ($user{'gid'} !~ /^\d+$/) {
255 # Need to create a new group for the user
256 if (!$access{'gcreate'}) {
257 print &text('batch_ecaccess', $lnum,
258 $text{'usave_egcreate'}),"\n";
261 if ($gtaken{$user{'user'}}) {
262 print &text('batch_egtaken', $lnum,
267 if ($config{'new_user_gid'}) {
268 $newgid = $user{'uid'};
271 while($gused{$newgid}) {
276 $group{'group'} = $user{'user'};
277 $user{'gid'} = $group{'gid'} = $newgid;
278 &create_group(\%group);
279 $gused{$group{'gid'}}++;
283 if ($in{'makehome'} && !-d $user{'home'}) {
284 &create_home_directory(\%user, $real_home);
286 &create_user(\%user);
288 # Add user to some secondary groups
290 foreach $group (@secs) {
291 local @mems = split(/,/ , $group->{'members'});
292 push(@mems, $user{'user'});
293 $group->{'members'} = join(",", @mems);
294 &modify_group($group, $group);
300 # Call other modules, ignoring any failures
303 &other_modules("useradmin_create_user", \%user)
304 if ($access{'cothers'} == 1 && $in{'others'} ||
305 $access{'cothers'} == 0);
310 if ($in{'copy'} && $in{'makehome'}) {
311 # Copy files to user's home directory
312 local $groupname = &my_getgrgid($user{'gid'});
313 local $uf = &get_skel_directory(\%user, $groupname);
314 ©_skel_files($uf, $user{'home'},
315 $user{'uid'}, $user{'gid'});
318 print "<b>",&text('batch_created',$user{'user'}),"</b>\n";
319 print "<b><i>",&text('batch_eother', $other_err),"</i></b>\n"
323 elsif ($line[0] eq 'delete') {
324 # Deleting an existing user
326 print &text('batch_elen', $lnum, 2),"\n";
329 local @ulist = &list_users();
330 local ($user) = grep { $_->{'user'} eq $line[1] } @ulist;
332 print &text('batch_enouser', $lnum, $line[1]),"\n";
335 if (!&can_edit_user(\%access, $user)) {
336 print &text('batch_edaccess', $lnum,
337 $text{'udel_euser'}),"\n";
340 if (!$config{'delete_root'} && $user->{'uid'} <= 10) {
341 print &text('batch_edaccess', $lnum,
342 $text{'udel_eroot'}),"\n";
346 # Run the before command
347 &set_user_envs($user, 'DELETE_USER', undef,
348 [ &secondary_groups($user->{'user'}) ]);
349 $merr = &making_changes();
350 &error(&text('usave_emaking', "<tt>$merr</tt>"))
353 # Delete from other modules, ignoring errors
356 &other_modules("useradmin_delete_user", $user)
357 if ($access{'dothers'} == 1 && $in{'others'} ||
358 $access{'dothers'} == 0);
363 # Delete the user entry
366 # Delete the user from groups
367 foreach $g (&list_groups()) {
368 @mems = split(/,/, $g->{'members'});
369 $idx = &indexof($user->{'user'}, @mems);
371 splice(@mems, $idx, 1);
373 $newg{'members'} = join(',', @mems);
374 &modify_group($g, \%newg);
376 $mygroup = $g if ($g->{'group'} eq $user->{'user'});
379 # Delete the user's group
380 if ($mygroup && !$mygroup->{'members'}) {
382 foreach $ou (&list_users()) {
384 if ($ou->{'gid'} == $mygroup->{'gid'});
387 &delete_group($mygroup);
392 # Delete his home directory
393 if ($in{'delhome'} &&
395 $user->{'home'} !~ /^\/+$/) {
396 &delete_home_directory($user);
399 print "<b>",&text('batch_deleted',$user->{'user'}),"</b>\n";
400 print "<b><i>",&text('batch_eother', $other_err),"</i></b>\n"
404 elsif ($line[0] eq 'modify') {
405 # Modifying an existing user
406 local $wlen = $pft == 5 ? 11 :
409 $pft == 1 || $pft == 6 ? 12 : 9;
410 if (@line != $wlen) {
411 print &text('batch_elen', $lnum, $wlen),"\n";
414 local @ulist = &list_users();
415 local ($user) = grep { $_->{'user'} eq $line[1] } @ulist;
417 print &text('batch_enouser', $lnum, $line[1]),"\n";
420 %olduser = %user = %$user;
421 $user{'olduser'} = $user->{'user'};
422 if (!&can_edit_user(\%access, \%user)) {
423 print &text('batch_emaccess', $lnum,
424 $text{'usave_eedit'}),"\n";
428 # Update supplied fields
429 if ($line[2] ne '') {
430 if (!$access{'urename'}) {
431 print &text('batch_erename', $lnum, $line[1]),"\n";
433 $user{'user'} = $line[2];
435 if ($in{'crypt'} && $line[3] ne '') {
436 # Changing to pre-encrypted password
437 $user{'pass'} = $line[3];
438 $user{'passmode'} = 2;
440 elsif ($line[3] eq 'x') {
442 $user{'pass'} = $config{'lock_string'};
443 $user{'passmode'} = 1;
445 elsif ($line[3] ne '') {
447 $user{'pass'} = &encrypt_password($line[3]);
448 $user{'passmode'} = 3;
449 $user{'plainpass'} = $line[3];
453 $user{'passmode'} = 4;
455 $user{'uid'} = $line[4] if ($line[4] ne '');
456 $user{'gid'} = $line[5] if ($line[5] ne '');
457 $user{'real'} = $line[6] if ($line[6] ne '');
458 $user{'home'} = $line[7] if ($line[7] ne '');
459 $user{'shell'} = $line[8] if ($line[8] ne '');
460 if ($access{'peopt'}) {
462 # Openserver password and short shadow
463 $user{'min'}=$line[9] if ($line[9] ne '');
464 $user{'max'}=$line[10] if ($line[10] ne '');
465 $user{'change'}=int(time() / (60*60*24))
469 # AIX password and security information
470 $user{'min'}=$line[9] if ($line[9] ne '');
471 $user{'max'}=$line[10] if ($line[10] ne '');
472 $user{'expire'}=$line[11] if ($line[11] ne '');
473 if ($line[12] ne '') {
474 delete($user{'admin'});
475 delete($user{'admchg'});
476 delete($user{'nocheck'});
478 split(/\s+/, $line[12]);
480 $user{'change'}=time() if ($line[3] ne '');
483 # SYSV-style passwd and shadow information
484 $user{'min'}=$line[9] if ($line[9] ne '');
485 $user{'max'}=$line[10] if ($line[10] ne '');
486 $user{'warn'}=$line[11] if ($line[11] ne '');
487 $user{'inactive'}=$line[12]
488 if ($line[12] ne '');
489 $user{'expire'}=$line[13] if ($line[13] ne '');
490 $user{'change'}=int(time() / (60*60*24))
493 elsif ($pft == 1 || $pft == 6) {
494 # BSD master.passwd information
495 $user{'class'}=$line[9] if ($line[9] ne '');
496 $user{'change'}=$line[10] if ($line[10] ne '');
497 $user{'expire'}=$line[11] if ($line[11] ne '');
501 # Check access control restrictions
502 local $ch = &check_user(\%user, \%olduser);
504 print &text('batch_emaccess', $lnum, $ch),"\n";
508 # Run the before command
509 &set_user_envs(\%user, 'MODIFY_USER', $user{'plainpass'},
510 [ &secondary_groups($user{'user'}) ]);
511 $merr = &making_changes();
512 &error(&text('usave_emaking', "<tt>$merr</tt>"))
515 # Move home directory if needed
516 if ($olduser{'home'} ne $user{'home'} && $in{'movehome'} &&
517 $user{'home'} ne '/' && $olduser{'home'} ne '/') {
518 if (-d $olduser{'home'} && !-e $user{'home'}) {
519 local $out = &backquote_logged(
520 "mv \"$olduser{'home'}\" ".
521 "\"$user{'home'}\" 2>&1");
522 if ($?) { &error(&text('batch_emove',
527 # Change UIDs and GIDs
528 if ($olduser{'gid'} != $user{'gid'} && $in{'chgid'}) {
529 if ($in{'chgid'} == 1) {
530 &recursive_change($user{'home'},$olduser{'uid'},
531 $olduser{'gid'}, -1, $user{'gid'});
534 &recursive_change("/", $olduser{'uid'},
535 $olduser{'gid'}, -1, $user{'gid'});
538 if ($olduser{'uid'} != $user{'uid'} && $in{'chuid'}) {
539 if ($in{'chuid'} == 1) {
540 &recursive_change($user{'home'},$olduser{'uid'},
541 -1, $user{'uid'}, -1);
544 &recursive_change("/", $olduser{'uid'},
545 -1, $user{'uid'}, -1);
549 # Actually modify the user
550 &modify_user(\%olduser, \%user);
552 # If the user has been renamed, update any secondary groups
553 if ($olduser{'user'} ne $user{'user'}) {
554 foreach $group (@glist) {
555 local @mems = split(/,/, $group->{'members'});
556 local $idx = &indexof($olduser{'user'}, @mems);
558 $mems[$idx] = $user{'user'};
559 $group->{'members'} = join(",", @mems);
560 &modify_group($group, $group);
567 # Modify in other modules, ignoring errors
570 &other_modules("useradmin_modify_user",
572 if ($access{'mothers'} == 1 && $in{'others'} ||
573 $access{'mothers'} == 0);
578 print "<b>",&text('batch_modified',$olduser{'user'}),"</b>\n";
579 print "<b><i>",&text('batch_eother', $other_err),"</i></b>\n"
584 print &text('batch_eaction', $lnum, $line[0]),"\n";
589 &batch_end() if ($in{'batch'});
590 &unlock_user_files();
591 &webmin_log("batch", undef, $in{'source'} == 1 ? $in{'local'} : undef,
592 { 'created' => $created, 'modified' => $modified,
593 'deleted' => $deleted, 'lnum' => $lnum } );
595 &ui_print_footer("batch_form.cgi", $text{'batch_return'},
596 "", $text{'index_return'});
598 # check_user(\%user, [\%olduser])
599 # Check access control restrictions for a user
602 # check if uid is within range
603 if ($access{'lowuid'} && $_[0]->{'uid'} < $access{'lowuid'}) {
604 return &text('usave_elowuid', $access{'lowuid'});
606 if ($access{'hiuid'} && $_[0]->{'uid'} > $access{'hiuid'}) {
607 return &text('usave_ehiuid', $access{'hiuid'});
609 if ($_[1] && !$access{'uuid'} && $_[1]->{'uid'} != $_[0]->{'uid'}) {
610 return $text{'usave_euuid'};
613 # make sure home dir is under the allowed root
614 if (!$access{'autohome'}) {
615 $al = length($access{'home'});
616 if (length($_[0]->{'home'}) < $al ||
617 substr($_[0]->{'home'}, 0, $al) ne $access{'home'}) {
618 return &text('usave_ehomepath', $_[0]->{'home'});
622 # check for invalid shell
623 if ($access{'shells'} ne '*' &&
624 &indexof($_[0]->{'shell'}, split(/\s+/, $access{'shells'})) < 0) {
625 return &text('usave_eshell', $_[0]->{'shell'});
628 # check for invalid primary group (unless one is dynamically assigned)
629 if ($user{'gid'} ne '') {
630 local $ng = &my_getgrgid($_[0]->{'gid'});
631 local $ni = &can_use_group(\%access, $ng);
633 if ($_[1]->{'gid'} != $_[0]->{'gid'}) {
634 local $og = &my_getgrgid($_[1]->{'gid'});
635 local $oi = &can_use_group(\%access, $og);
636 if (!$ni) { return &text('usave_eprimary', $ng); }
637 if (!$oi) { return &text('usave_eprimaryr', $og); }
641 return &text('usave_eprimary', $ng) if (!$ni);
650 foreach $g (@glist) {
651 @mems = split(/,/, $g->{'members'});
652 if (&indexof($_[0], @mems) >= 0) {
653 push(@secs, $g->{'gid'});
659 sub validate_batch_minmax
661 local ($user, $lnum) = @_;
662 foreach my $f ('min', 'max', 'warn', 'inactive', 'expire', 'change') {
663 $user->{$f} =~ /^(\-|\+|)\d*$/ ||
664 return &text('batch_e'.$f, $lnum, $user->{$f});