3 # Saves or creates a new user. If the changes require moving of the user's
4 # home directory or changing file ownerships, do that as well
6 require './user-lib.pl';
7 require 'timelocal.pl';
8 &error_setup($text{'usave_err'});
11 # Check for buttons at end of user form, redirect if needed
13 &redirect("list_logins.cgi?username=".&urlize($in{'old'}));
16 elsif ($in{'mailboxes'}) {
17 &redirect("../mailboxes/list_mail.cgi?user=".&urlize($in{'old'}));
20 elsif ($in{'switch'}) {
21 &redirect("../usermin/switch.cgi?user=".&urlize($in{'old'}));
24 elsif ($in{'delete'}) {
25 &redirect("delete_user.cgi?user=".&urlize($in{'old'}));
29 # Build list of used UIDs and GIDs
30 &build_user_used(\%used);
31 &build_group_used(\%used) if ($config{'new_user_gid'});
32 &build_group_used(\%gused);
34 # Strip out \n characters in inputs
35 $in{'real'} =~ s/\r|\n//g;
36 $in{'user'} =~ s/\r|\n//g;
37 $in{'user'} =~ s/\0.*$//; # some people reports \0 in usernames!
38 $in{'pass'} =~ s/\r|\n//g;
39 $in{'encpass'} =~ s/\r|\n//g;
40 $in{'home'} =~ s/\r|\n//g;
41 $in{'gid'} =~ s/\r|\n//g;
42 $in{'uid'} =~ s/\r|\n//g;
43 $in{'uid'} = int($in{'uid'});
44 $in{'othersh'} =~ s/\r|\n//g;
47 $user{'user'} = $in{'user'};
48 $in{'user'} =~ /^[^:\t]+$/ ||
49 &error(&text('usave_ebadname', $in{'user'}));
50 $err = &check_username_restrictions($in{'user'});
51 &error($err) if ($err);
54 @ulist = &list_users();
55 @glist = &list_groups();
56 if ($in{'old'} ne "") {
58 ($ouser_hash) = grep { $_->{'user'} eq $in{'old'} } @ulist;
59 $ouser_hash || &error($text{'uedit_egone'});
60 %ouser = %$ouser_hash;
61 if (!$access{'urename'} && $ouser{'user'} ne $user{'user'}) {
62 &error($text{'usave_erename'});
64 $user{'olduser'} = $ouser{'user'};
65 if ($user{'user'} ne $ouser{'user'}) {
66 foreach $ou (@ulist) {
67 &error(&text('usave_einuse', $in{'user'}))
68 if ($ou->{'user'} eq $in{'user'});
70 $access{'uedit_mode'} == 2 && &error($text{'usave_erename'});
73 &can_edit_user(\%access, \%ouser) || &error($text{'usave_eedit'});
76 # check new user details
77 $access{'ucreate'} || &error($text{'usave_ecreate'});
78 foreach $ou (@ulist) {
79 &error(&text('usave_einuse', $in{'user'}))
80 if ($ou->{'user'} eq $in{'user'});
83 if (($in{'old'} eq '' || $user{'user'} ne $ouser{'user'}) &&
84 $config{'alias_check'} && &foreign_check("sendmail")) {
85 # Check if the new username conflicts with a sendmail alias
86 &foreign_require("sendmail", "sendmail-lib.pl");
87 &foreign_require("sendmail", "aliases-lib.pl");
88 local $conf = &foreign_call("sendmail", "get_sendmailcf");
89 local $afiles = &foreign_call("sendmail", "aliases_file", $conf);
90 foreach $a (&foreign_call("sendmail", "list_aliases", $afiles)) {
91 &error(&text('usave_einuse_a', $in{'user'}))
92 if ($a->{'name'} eq $in{'user'});
96 # Validate and store basic inputs
97 if (!$in{'uid_def'} || $in{'old'} ne '') {
98 # Only do UID checks if not automatic
99 $in{'uid'} =~ /^\-?[0-9]+$/ || &error(&text('usave_euid', $in{'uid'}));
100 if (!%ouser || $ouser{'uid'} != $in{'uid'}) {
101 !$access{'lowuid'} || $in{'uid'} >= $access{'lowuid'} ||
102 &error(&text('usave_elowuid', $access{'lowuid'}));
103 !$access{'hiuid'} || $in{'uid'} <= $access{'hiuid'} ||
104 &error(&text('usave_ehiuid', $access{'hiuid'}));
106 if (!$access{'uuid'} && %ouser && $ouser{'uid'} != $in{'uid'}) {
107 &error($text{'usave_euuid'});
109 if (!$access{'umultiple'}) {
110 foreach $ou (@ulist) {
111 if ($ou->{'uid'} == $in{'uid'} &&
112 $ou->{'user'} ne $ouser{'user'}) {
113 &error(&text('usave_euidused',
114 $ou->{'user'}, $in{'uid'}));
119 elsif ( $in{'uid_def'} eq '1' ) {
120 # Can assign UID here
121 $in{'uid'} = int($config{'base_uid'} > $access{'lowuid'} ?
122 $config{'base_uid'} : $access{'lowuid'});
123 while($used{$in{'uid'}}) {
126 if ($access{'hiuid'} && $in{'uid'} > $access{'hiuid'}) {
128 &error($text{'usave_ealluid'});
132 elsif ( $in{'uid_def'} eq '2' ) {
133 # Can calculate UID here
134 if ( $config{'uid_calc'} ) {
135 $in{'uid'} = &mkuid($in{'user'});
137 $in{'uid'} = &berkeley_cksum($in{'user'});
139 &error("Unable to calculate UID, invalid user name specified") if ( $in{'uid'} lt 0 );
140 while($used{$in{'uid'}}) {
143 if ($access{'hiuid'} && $in{'uid'} > $access{'hiuid'}) {
145 &error($text{'usave_ealluid'});
149 $in{'real'} =~ /^[^:]*$/ || &error(&text('usave_ereal', $in{'real'}));
150 if ($in{'shell'} eq "*") { $in{'shell'} = $in{'othersh'}; }
151 if ($access{'shells'} ne "*") {
152 if (&indexof($in{'shell'}, split(/\s+/, $access{'shells'})) < 0 &&
153 (!%ouser || $in{'shell'} ne $ouser{'shell'})) {
154 &error(&text('usave_eshell', $in{'shell'}));
157 $user{'uid'} = $in{'uid'};
158 if ($in{'old'} ne "" || !$in{'gidmode'}) {
159 # Selecting existing group
160 $user{'gid'} = &my_getgrnam($in{'gid'});
161 if ($user{'gid'} eq "") { &error(&text('usave_egid', $in{'gid'})); }
165 # Creating a new group
166 $access{'gcreate'} || &error($text{'usave_egcreate'});
167 if ($in{'gidmode'} == 2) {
168 # New group has same name as user
169 $in{'newgid'} = $in{'user'};
172 # New group has arbitrary name
173 $in{'newgid'} =~ /^[^: \t]+$/ ||
174 &error(&text('gsave_ebadname', $in{'newgid'}));
176 foreach $og (@glist) {
177 &error(&text('usave_einuseg', $in{'newgid'}))
178 if ($og->{'group'} eq $in{'newgid'});
180 $grp = $in{'newgid'};
182 if ($config{'extra_real'}) {
183 $in{'real'} =~ /^[^:,]*$/ || &error(&text('usave_ereal', $in{'real'}));
184 $in{'office'} =~ /^[^:,]*$/ || &error($text{'usave_eoffice'});
185 $in{'workph'} =~ /^[^:,]*$/ || &error($text{'usave_eworkph'});
186 $in{'homeph'} =~ /^[^:,]*$/ || &error($text{'usave_ehomeph'});
187 $user{'real'} = join(",", $in{'real'}, $in{'office'}, $in{'workph'},
189 $user{'real'} .= ",$in{'extra'}" if ($in{'extra'});
192 $user{'real'} = $in{'real'};
195 if ($config{'real_base'} &&
196 $user{'home'} eq &auto_home_dir($config{'home_base'},
197 $ouser{'user'}, $grp)) {
198 # Work out old real home
199 $old_real_home = &auto_home_dir($config{'real_base'},
200 $ouser{'user'}, $grp);
202 if ($access{'autohome'}) {
203 # Home directory is forced to automatic
204 if ($in{'new'} || $ouser{'user'} ne $user{'user'}) {
205 $user{'home'} = &auto_home_dir($access{'home'}, $in{'user'},
207 $real_home = &auto_home_dir($config{'real_base'}, $in{'user'},
208 $grp) if ($config{'real_base'});
211 $user{'home'} = $ouser{'home'};
214 elsif ($config{'home_base'} && $in{'home_base'}) {
215 # Automatic home directory option chosen
216 $user{'home'} = &auto_home_dir($config{'home_base'}, $in{'user'},
218 $real_home = &auto_home_dir($config{'real_base'}, $in{'user'},
219 $grp) if ($config{'real_base'});
222 # Manual home directory chosen
223 $user{'home'} = $in{'home'};
225 $real_home ||= $user{'home'};
226 if (!$access{'autohome'}) {
227 $user{'home'} =~ /^\// || &error(&text('usave_ehome', $in{'home'}));
228 if (!&is_under_directory($access{'home'}, $user{'home'})) {
229 &error(&text('usave_ehomepath', $user{'home'}));
232 $user{'shell'} = $in{'shell'};
233 @sgnames = $config{'secmode'} == 2 ? &split_quoted_string($in{'sgid'})
234 : split(/\r?\n/, $in{'sgid'});
235 foreach $gname (@sgnames) {
237 $gid = &my_getgrnam($gname);
238 defined($gid) || &error(&text('usave_esgname', $gname));
242 if ($access{'ugroups'} ne "*") {
243 if ($in{'old'} ne "") {
244 # existing users can only be added to or removed from
246 if ($ouser{'gid'} != $user{'gid'}) {
247 &can_use_group(\%access, $in{'gid'}) ||
248 &error(&text('usave_eprimary', $in{'gid'}));
249 local $og = &my_getgrgid($ouser{'gid'});
250 &can_use_group(\%access, $og) ||
251 &error(&text('usave_eprimaryr', $og));
253 foreach $g (@glist) {
254 local @mems = split(/,/ , $g->{'members'});
255 local $idx = &indexof($ouser{'user'}, @mems);
256 if ($ingroup{$g->{'group'}} && $idx<0 &&
257 !&can_use_group(\%access, $g->{'group'})) {
258 &error(&text('usave_esecondary',
261 elsif (!$ingroup{$g->{'group'}} && $idx>=0 &&
262 !&can_use_group(\%access, $g->{'group'})) {
263 &error(&text('usave_esecondaryr',
268 elsif (!$in{'gidmode'}) {
269 # new users can only be added to allowed groups
270 # This is skipped if we are creating a new group for
272 &can_use_group(\%access, $in{'gid'}) ||
273 &error(&text('usave_eprimary', $in{'gid'}));
274 foreach $gname (@sgnames) {
275 &can_use_group(\%access, $gname) ||
276 &error(&text('usave_esecondary', $group));
281 # Store password input
282 if ($in{'passmode'} == 0) {
284 if (!$config{'empty_mode'}) {
285 local $err = &check_password_restrictions("", $user{'user'});
286 &error($err) if ($err);
290 elsif ($in{'passmode'} == 1) {
292 $user{'pass'} = $config{'lock_string'};
294 elsif ($in{'passmode'} == 2) {
295 # Specific encrypted password entered, or possibly no change
296 $user{'pass'} = $in{'encpass'};
298 elsif ($in{'passmode'} == 3) {
299 # Normal password entered - check restrictions
300 local $err = &check_password_restrictions($in{'pass'}, $user{'user'});
301 &error($err) if ($err);
302 $user{'pass'} = &encrypt_password($in{'pass'});
304 if (&supports_temporary_disable() &&
305 $in{'disable'} && ($in{'passmode'} == 2 || $in{'passmode'} == 3)) {
306 $user{'pass'} = $disable_string.$user{'pass'};
309 $pft = &passfiles_type();
310 if ($pft == 2 || $pft == 5) {
311 if ($access{'peopt'}) {
312 # Validate shadow-password inputs
313 $in{'min'} =~ /^\-?[0-9]*$/ ||
314 &error(&text('usave_emin', $in{'min'}));
315 $in{'max'} =~ /^\-?[0-9]*$/ ||
316 &error(&text('usave_emax', $in{'max'}));
317 $user{'min'} = $in{'min'};
318 $user{'max'} = $in{'max'};
320 if ($in{'expired'} ne "" && $in{'expirem'} ne ""
321 && $in{'expirey'} ne "") {
322 eval { $expire = timelocal(0, 0, 12,
325 $in{'expirey'}-1900); };
326 if ($@) { &error($text{'usave_eexpire'}); }
327 $expire = int($expire / (60*60*24));
329 else { $expire = ""; }
330 $user{'expire'} = $expire;
331 $in{'warn'} =~ /^\-?[0-9]*$/ ||
332 &error(&text('usave_ewarn', $in{'warn'}));
333 $in{'inactive'} =~ /^\-?[0-9]*$/ ||
334 &error(&text('usave_einactive', $in{'inactive'}));
335 $user{'warn'} = $in{'warn'};
336 $user{'inactive'} = $in{'inactive'};
340 $user{'expire'} = $ouser{'expire'};
341 $user{'min'} = $ouser{'min'};
342 $user{'max'} = $ouser{'max'};
344 $user{'warn'} = $ouser{'warn'};
345 $user{'inactive'} = $ouser{'inactive'};
348 $daynow = int(time() / (60*60*24));
349 $user{'change'} = $in{'forcechange'} ? 0 :
350 $pft == 5 && $in{'ask'} ? 0 :
352 $in{'passmode'} == 3 ? $daynow :
353 $in{'passmode'} == 2 &&
354 $user{'pass'} ne $ouser{'pass'} ? $daynow :
357 elsif ($pft == 1 || $pft == 6) {
358 if ($access{'peopt'}) {
359 # Validate BSD-password inputs
360 if ($in{'expired'} ne "" && $in{'expirem'} ne ""
361 && $in{'expirey'} ne "") {
362 eval { $expire = timelocal(59, $in{'expiremi'},
366 $in{'expirey'}-1900); };
367 if ($@) { &error($text{'usave_eexpire'}); }
369 else { $expire = ""; }
370 if ($in{'changed'} ne "" && $in{'changem'} ne ""
371 && $in{'changey'} ne "") {
372 eval { $change = timelocal(59, $in{'changemi'},
376 $in{'changey'}-1900); };
377 if ($@) { &error($text{'usave_echange'}); }
379 else { $change = ""; }
380 $in{'class'} =~ /^([^: ]*)$/ ||
381 &error(&text('usave_eclass', $in{'class'}));
382 $user{'expire'} = $expire;
383 $user{'change'} = $change;
384 $user{'class'} = $in{'class'};
387 $user{'expire'} = $ouser{'expire'};
388 $user{'change'} = $ouser{'change'};
389 $user{'class'} = $ouser{'class'};
393 # Validate AIX-style password inputs
394 if ($in{'expire_def'} == 1) {
395 # System default expiry date
398 elsif ($in{'expire_def'} == 2) {
403 # Add a leading zero if only 1 digit long
404 $in{'expirem'} =~ /^\d+$/ && $in{'expired'} =~ /^\d+$/ &&
405 $in{'expireh'} =~ /^\d+$/ && $in{'expiremi'} =~ /^\d+$/ &&
406 $in{'expirey'} =~ /^\d+$/ || &error($text{'usave_eexpire'});
407 $in{'expirem'} =~ s/^(\d)$/0$1/;
408 $in{'expired'} =~ s/^(\d)$/0$1/;
409 $in{'expireh'} =~ s/^(\d)$/0$1/;
410 $in{'expiremi'} =~ s/^(\d)$/0$1/;
412 # Only use the last two digits of the year
413 $in{'expirey'} =~ s/^\d\d(\d\d)$/$1/;
415 # If the user didn't choose the hour and min make them 01
416 $in{'expireh'} = "01" if $in{'expireh'} eq "";
417 $in{'expiremi'} = "01" if $in{'expiremi'} eq "";
418 $expire="$in{'expirem'}$in{'expired'}$in{'expireh'}$in{'expiremi'}$in{'expirey'}";
420 if ($access{'peopt'}) {
421 $user{'admin'} = $in{'flags'} =~ /admin/;
422 $user{'admchg'} = $in{'flags'} =~ /admchg/;
423 $user{'nocheck'} = $in{'flags'} =~ /nocheck/;
424 $user{'expire'} = $expire;
425 $user{'min'} = $in{'min_def'} ? undef : $in{'min'};
426 $user{'max'} = $in{'max_def'} ? undef : $in{'max'};
427 $user{'warn'} = $in{'warn_def'} ? undef : $in{'warn'};
430 $user{'admin'} = $ouser{'admin'};
431 $user{'admchg'} = $ouser{'admchg'};
432 $user{'nocheck'} = $ouser{'nocheck'};
433 $user{'expire'} = $ouser{'expire'};
434 $user{'min'} = $ouser{'min'};
435 $user{'max'} = $ouser{'max'};
436 $user{'warn'} = $ouser{'warn'};
438 $user{'change'} = !%ouser ? time() :
439 $in{'passmode'} == 3 ? time() :
440 $user{'pass'} ne $ouser{'pass'} ? time() :
445 # We are changing an existing user
446 if ($ouser{'uid'} != $user{'uid'}) {
449 if ($ouser{'gid'} != $user{'gid'}) {
452 if ($ouser{'home'} ne $user{'home'}) {
453 $changing_homedir = 1;
455 $in{'old'} = $ouser{'user'};
457 # Force defaults for save options if necessary
458 $in{'movehome'} = !$access{'movehome'} if ($access{'movehome'} != 1);
459 $in{'chuid'} = !$access{'chuid'} if ($access{'chuid'} != 1);
460 $in{'chgid'} = !$access{'chgid'} if ($access{'chgid'} != 1);
461 $in{'others'} = !$access{'mothers'} if ($access{'mothers'} != 1);
463 # Run the pre-change command
464 &set_user_envs(\%user, 'MODIFY_USER',
465 $in{'passmode'} == 3 ? $in{'pass'} : "", \@sgids);
466 $merr = &making_changes();
467 &error(&text('usave_emaking', "<tt>$merr</tt>")) if (defined($merr));
469 # Move the home directory if needed
470 if ($changing_homedir && $in{'movehome'}) {
471 &error($text{'usave_efromroot'}) if ($ouser{'home'} eq "/");
472 &error($text{'usave_etoroot'}) if ($user{'home'} eq "/");
473 if (-d $ouser{'home'} && !-e $user{'home'}) {
474 # Move home directory if the old one exists and
475 # the new one does not.
476 if ($real_home && $old_real_home) {
478 $out = &backquote_logged(
479 "mv ".quotemeta($old_real_home)." ".
480 quotemeta($real_home)." 2>&1");
483 $out = &backquote_logged(
484 "mv ".quotemeta($ouser{'home'})." ".
485 quotemeta($user{'home'})." 2>&1");
487 if ($?) { &error(&text('usave_emove', $out)); }
491 # Change GID on files if needed
492 if ($changing_gid && $in{'chgid'}) {
493 if ($in{'chgid'} == 1) {
494 &recursive_change($user{'home'}, $ouser{'uid'},
495 $ouser{'gid'}, -1, $user{'gid'});
498 &recursive_change("/", $ouser{'uid'},
499 $ouser{'gid'}, -1, $user{'gid'});
503 # Change UID on files if needed
504 if ($changing_uid && $in{'chuid'}) {
505 if ($in{'chuid'} == 1) {
506 &recursive_change($user{'home'}, $ouser{'uid'},
507 -1, $user{'uid'}, -1);
510 &recursive_change("/", $ouser{'uid'},
511 -1, $user{'uid'}, -1);
515 # Update user details
516 $user{'passmode'} = $in{'passmode'};
517 if ($in{'passmode'} == 2 && $user{'pass'} eq $ouser{'pass'}) {
518 # not changing password
519 $user{'passmode'} = 4;
521 $user{'plainpass'} = $in{'pass'} if ($in{'passmode'} == 3);
522 &modify_user(\%ouser, \%user);
524 # Rename group if needed and if possible
525 if ($user{'user'} ne $ouser{'user'} &&
526 $user{'gid'} == $ouser{'gid'} &&
528 ($group) = grep { $_->{'gid'} == $user{'gid'} } &list_groups();
529 if ($group->{'group'} eq $ouser{'user'} &&
530 &can_edit_group(\%access, $group)) {
532 $ogroup = { %$group };
533 $group->{'group'} = $user{'user'};
534 &modify_group($ogroup, $group);
539 # Force defaults for save options if necessary
540 $in{'makehome'} = !$access{'makehome'} if ($access{'makehome'} != 1);
541 $in{'copy_files'} = !$access{'copy'} if ($access{'copy'} != 1 &&
542 $config{'user_files'} =~ /\S/);
543 $in{'others'} = !$access{'cothers'} if ($access{'cothers'} != 1);
545 # Run the pre-change command
546 &set_user_envs(\%user, 'CREATE_USER',
547 $in{'passmode'} == 3 ? $in{'pass'} : "", \@sgids);
548 $merr = &making_changes();
549 &error(&text('usave_emaking', "<tt>$merr</tt>")) if (defined($merr));
551 # Create the home directory
552 if ($in{'makehome'}) {
553 &create_home_directory(\%user, $real_home);
557 if ($in{'gidmode'}) {
558 # New group for the new user ..
559 if ($config{'new_user_gid'}) {
560 # gid is the same as the uid
561 $newgid = $user{'uid'};
564 # find the first free GID above the base
565 $newgid = int($config{'base_gid'} > $access{'lowgid'} ?
566 $config{'base_gid'} : $access{'lowgid'});
567 while($gused{$newgid}) {
572 # create a new group for this user
573 $created_group = $group{'group'} = $in{'newgid'};
574 $user{'gid'} = $group{'gid'} = $newgid;
575 &create_group(\%group);
576 $created_group = \%group;
580 &set_ownership_permissions($user{'uid'}, $user{'gid'},
581 undef, $real_home) ||
582 &error(&text('usave_echown', $!));
586 $user{'passmode'} = $in{'passmode'};
587 $user{'plainpass'} = $in{'pass'} if ($in{'passmode'} == 3);
588 &create_user(\%user);
590 # Copy files into user's directory
591 if ($in{'copy_files'} && $in{'makehome'}) {
592 local $uf = &get_skel_directory(\%user, $in{'gid'});
593 ©_skel_files($uf, $real_home, $user{'uid'}, $user{'gid'});
597 if ($config{'secmode'} != 1) {
598 # Update secondary groups
599 foreach $g (@glist) {
600 @mems = split(/,/ , $g->{'members'});
602 $idx = &indexof($ouser{'user'}, @mems);
603 if ($ingroup{$g->{'group'}} && $idx<0) {
604 # Need to add to the group
605 push(@mems, $user{'user'});
607 elsif (!$ingroup{$g->{'group'}} && $idx>=0) {
608 # Need to remove from the group
609 splice(@mems, $idx, 1);
612 # Need to rename in group
613 $mems[$idx] = $user{'user'};
618 $idx = &indexof($user{'user'}, @mems);
619 if ($ingroup{$g->{'group'}} && $idx<0) {
620 # Need to add to the group
621 push(@mems, $user{'user'});
623 elsif (!$ingroup{$g->{'group'}} && $idx>=0) {
624 # Need to remove from the group
625 splice(@mems, $idx, 1);
630 $newg{'members'} = join(',', @mems);
631 &modify_group($g, \%newg);
634 &unlock_user_files();
637 # Run other modules' scripts
642 &other_modules("useradmin_modify_user", \%user,\%ouser);
645 &other_modules("useradmin_create_user", \%user);
647 if ($created_group) {
648 &other_modules("useradmin_create_group",
657 delete($in{'encpass'});
658 &webmin_log(%ouser ? 'modify' : 'create', 'user', $in{'user'}, \%in);
659 &webmin_log('create', 'group', $created_group->{'group'}, \%in)
662 # Bounce back to the list, if everything worked
663 &error(&text('usave_eothers', $others_err)) if ($others_err);
664 &redirect("index.cgi?mode=users");