3 # Create, update or delete an LDAP user
5 require './ldap-useradmin-lib.pl';
8 $ldap = &ldap_connect();
9 $schema = $ldap->schema();
13 $rv = $ldap->search(base => $in{'dn'},
15 filter => &user_filter());
16 ($uinfo) = $rv->all_entries;
17 $uinfo || &error($text{'usave_egone'});
18 %ouser = &dn_to_hash($uinfo);
19 &can_edit_user(\%ouser) || &error($text{'usave_eedit'});
22 $access{'ucreate'} || &error($text{'usave_ecreate'});
25 if ($in{'mailboxes'}) {
26 # Just re-direct to mailboxes page
27 &redirect("../mailboxes/list_mail.cgi?user=$ouser{'user'}");
30 elsif ($in{'switch'}) {
31 # Just re-direct to Usermin switch user program
32 &redirect("../usermin/switch.cgi?user=$ouser{'user'}");
35 elsif ($in{'delete'}) {
36 # Delete the user .. but ask first!
37 &ui_print_header(undef, $text{'udel_title'}, "");
38 $home = $uinfo->get_value("homeDirectory");
39 $user = $uinfo->get_value("uid");
41 # Run the before command
42 %uhash = &dn_to_hash($uinfo);
43 &set_user_envs(\%uhash, 'DELETE_USER', undef, undef);
44 $merr = &making_changes();
45 &error(&text('usave_emaking', "<tt>$merr</tt>"))
48 # Work out old classes
49 @classes = $uinfo->get_value("objectClass");
50 @cyrus_class_2 = split(' ',$cyrus_class);
51 $wascyrus = &indexof($cyrus_class_2[0], @classes) >= 0;
53 # Delete from other modules
54 %user = &dn_to_hash($uinfo);
56 print "$text{'udel_other'}<br>\n";
57 &useradmin::other_modules("useradmin_delete_user",
59 print "$text{'udel_done'}<p>\n";
62 # Delete from any groups
63 print "$text{'udel_groups'}<br>\n";
64 $base = &get_group_base();
65 $rv = $ldap->search(base => $base,
66 filter => &group_filter());
67 foreach $g ($rv->all_entries) {
68 local @mems = $g->get_value("memberUid");
69 local $idx = &indexof($user, @mems);
71 # Take out of this group
72 splice(@mems, $idx, 1);
73 $rv = $ldap->modify($g->dn(), replace =>
74 { 'memberUid' => \@mems });
76 &error(&text('usave_emodgroup',
82 print "$text{'udel_done'}<p>\n";
84 # Delete from the LDAP db
85 print "$text{'udel_pass'}<br>\n";
86 $rv = $ldap->delete($in{'dn'});
88 &error(&text('usave_edelete', $rv->error));
90 print "$text{'udel_done'}<p>\n";
92 # Delete his addressbook entry
93 if ($config{'addressbook'} && $wascyrus) {
94 print "$text{'udel_book'}<br>\n";
95 $err = &delete_addressbook();
97 print &text('udel_failed', $err),"<p>\n";
100 print "$text{'udel_done'}<p>\n";
104 # Delete his home directory
105 if ($in{'delhome'}) {
106 print "$text{'udel_home'}<br>\n";
107 $home = $uinfo->get_value("homeDirectory");
108 if (-d $home && $home ne "/") {
109 local $realhome = &resolve_links($home);
110 local $qhome = quotemeta($realhome);
111 system("rm -rf $qhome >/dev/null 2>&1");
112 unlink($home); # in case of links
114 print "$text{'udel_done'}<p>\n";
116 # Delete his IMAP mailbox only if home gets deleted, too
117 if ($config{'imap_host'}) {
118 print "$text{'udel_imap'}<br>\n";
119 $imap = &imap_connect();
120 $rv = $imap->delete("user".$config{'imap_foldersep'}.
121 $uinfo->get_value("uid"));
123 print "$text{'udel_done'}<p>\n";
130 &webmin_log("delete", "user", $user{'user'}, \%p);
133 # Show confirmation page
134 if ($home ne "/" && -d $home) {
135 # With option to delete home
136 $size = &nice_size(&disk_usage_kb($home)*1024);
137 $msg = &text('udel_sure', $user, $home, $size);
138 @buts = ( [ undef, $text{'udel_del1'} ],
139 [ "delhome", $text{'udel_del2'} ] );
143 $msg = &text('udel_sure2', $user);
144 @buts = ( [ undef, $text{'udel_del1'} ] );
146 print &ui_confirmation_form(
149 [ [ "dn", $in{'dn'} ],
153 &ui_checkbox("others", 1, $text{'udel_dothers'},
154 $mconfig{'default_other'}),
156 "<font color=#ff0000>$text{'udel_root'}</font>" : ""
161 &ui_print_footer("", $text{'index_return'});
165 # Show all LDAP attributes for user
166 &redirect("raw.cgi?user=1&dn=".&urlize($in{'dn'}));
171 &error_setup($text{'usave_err'});
172 $in{'user'} =~ /^[^:\t]+$/ ||
173 &error(&text('usave_ebadname', $in{'user'}));
174 $in{'user'} =~ s/\r//g;
175 $in{'real'} || &error($text{'usave_ereal'});
176 @users = split(/\n/, $in{'user'});
178 $in{'uid'} =~ /^\-?[0-9]+$/ || &error(&text('usave_euid', $in{'uid'}));
180 $in{'real'} =~ /^[^:]*$/ || &error(&text('usave_ereal', $in{'real'}));
181 $firstname = $in{'firstname'};
182 $lastname = $in{'lastname'};
184 $shell = $in{'shell'} eq '*' ? $in{'othersh'} : $in{'shell'};
186 &check_user_used($ldap, $user) &&
187 &error(&text('usave_einuse', $user));
190 # Check for UID clash
191 if ($in{'new'} && !$access{'umultiple'}) {
192 &check_uid_used($ldap, $uid) &&
193 &error($text{'usave_euidused2'});
196 # Validate IMAP quota
198 if ($config{'quota_support'} && !$in{'quota_def'} &&
199 defined($in{'quota'})) {
200 $in{'quota'} =~ /^\d+$/ || &error($text{'usave_equota'});
201 $quota = $in{'quota'};
204 #load main user group
205 if ($in{'gid'} =~ /^\d+$/) {
209 $gid = &all_getgrnam($in{'gid'});
210 defined($gid) || &error(&text('usave_egid', $in{'gid'}));
212 $grp = &all_getgrgid($gid);
214 # Compute and validate home directory
215 if ($access{'autohome'}) {
216 if ($in{'new'} || $ouser{'user'} ne $user) {
217 $home = &auto_home_dir($access{'home'}, $user, $grp);
220 $home = $ouser{'home'};
223 elsif ($mconfig{'home_base'} && $in{'home_base'}) {
224 $home = &auto_home_dir($mconfig{'home_base'}, $user, $grp);
228 $home =~ /^\// || &error(&text('usave_ehome', $home));
230 if (!$access{'autohome'}) {
231 $home =~ /^\// || &error(&text('usave_ehome', $home));
232 $al = length($access{'home'});
233 if (length($home) < $al ||
234 substr($home, 0, $al) ne $access{'home'}) {
235 &error(&text('usave_ehomepath', $home));
239 local $pfx = $config{'md5'} == 1 || $config{'md5'} == 3 ? "{md5}" :
240 $config{'md5'} == 4 ? "{ssha}" :
241 $config{'md5'} == 0 ? "{crypt}" : "";
242 if ($in{'passmode'} == 0) {
244 if (!$mconfig{'empty_mode'}) {
245 local $err = &useradmin::check_password_restrictions(
247 &error($err) if ($err);
251 elsif ($in{'passmode'} == 1) {
253 $pass = $mconfig{'lock_string'};
255 elsif ($in{'passmode'} == 2) {
256 # Specific encrypted password entered, or possibly no change
257 $pass = $in{'encpass'};
258 $pass = $pfx.$pass if ($pass !~ /^\{[a-z0-9]+\}/i && $pfx);
260 elsif ($in{'passmode'} == 3) {
261 # Normal password entered - check restrictions
262 local $err = &useradmin::check_password_restrictions(
264 &error($err) if ($err);
265 $pass = $pfx.&encrypt_password($in{'pass'});
266 $plainpass = $in{'pass'};
268 if ($in{'disable'} && ($in{'passmode'} == 2 || $in{'passmode'} == 3)) {
269 $pass = $useradmin::disable_string.$pass;
272 # Build useradmin-style hash of user details
273 local %uhash = ( 'user' => $user,
276 'group' => $in{'group'},
280 'plainpass' => $plainpass,
282 'firstname' => $firstname,
283 'lastname' => $lastname );
286 defined(&all_getpwnam($user)) &&
287 &error(&text('usave_einuse', $user));
288 if ($in{'passmode'} == 1 || $in{'passmode'} == 2) {
290 &error($text{'usave_ecyruspass'});
294 # Run the pre-change command
295 &set_user_envs(\%uhash, 'CREATE_USER',
296 $in{'passmode'} == 3 ? $in{'pass'} : "",
298 $merr = &making_changes();
299 &error(&text('usave_emaking', "<tt>$merr</tt>"))
303 if (!-e $home && $in{'makehome'}) {
305 mkdir($home, oct($mconfig{'homedir_perms'})) ||
306 &error(&text('usave_emkdir', $!));
307 chmod(oct($mconfig{'homedir_perms'}), $home) ||
308 &error(&text('usave_echmod', $!));
309 chown($uid, $gid, $home) ||
310 &error(&text('usave_echown', $!));
314 # Get configured properties for new users
315 local @props = &split_props($config{'props'}, \%uhash);
317 push(@props, &split_props($config{'imap_props'},
321 # Build Samba-related properties
323 &samba_properties(1, \%uhash, $in{'passmode'},
324 $in{'pass'}, $schema, \@props, $ldap);
328 # Build mail-related properties
332 # Add any extra LDAP fields
333 &parse_extra_fields($config{'fields'}, \@props, \@rprops,
336 # Add shadow LDAP fields
337 $shadow = &shadow_fields();
339 # Add to the ldap database
340 @classes = ( "posixAccount", "shadowAccount" );
341 if ($schema && $schema->objectclass("person") && $config{'person'}) {
342 push(@classes, "person");
345 push(@classes, split(/\s+/, $config{'other_class'}));
346 push(@classes, $samba_class) if ($in{'samba'});
347 push(@classes, split(' ',$cyrus_class)) if ($in{'cyrus'});
348 @classes = grep { /\S/ } @classes; # Remove empty
350 @classes = &unique(@classes);
351 $base = &get_user_base();
352 $newdn = "uid=$user,$base";
353 @allprops = ( "cn" => $real,
356 "loginShell" => $shell,
357 "homeDirectory" => $home,
359 "userPassword" => $pass,
360 "objectClass" => \@classes,
362 if (&indexoflc("person", @classes) >= 0 &&
363 !&in_props(\@allprops, "sn")) {
364 # Person needs an 'sn' too
365 push(@allprops, "sn", $real);
367 $rv = $ldap->add($newdn, attr => \@allprops);
369 &error(&text('usave_eadd', $rv->error));
373 if ($config{'addressbook'}) {
374 # Create addressbook entry
375 &setup_addressbook(\%uhash);
378 # Disconnect to save the changes
382 # Create imap account
383 &setup_imap(\%uhash, $quota);
385 # Re-connect for later LDAP operations
386 $ldap = &ldap_connect();
389 # Copy files into user's directory
390 if ($in{'makehome'} && $mconfig{'user_files'}) {
391 local $uf = $mconfig{'user_files'};
392 local $shell = $user{'shell'}; $shell =~ s/^(.*)\///g;
393 $uf =~ s/\$group/$in{'gid'}/g;
394 $uf =~ s/\$gid/$user{'gid'}/g;
395 $uf =~ s/\$shell/$shell/g;
396 &useradmin::copy_skel_files($uf, $home, $uid, $gid);
401 $olduser = $uinfo->get_value('uid');
402 if ($olduser ne $user) {
403 defined(&all_getpwnam($user)) &&
404 &error(&text('usave_einuse', $user));
407 # Work out old settings
408 @classes = $uinfo->get_value("objectClass");
409 $wassamba = &indexof($samba_class, @classes) >= 0;
410 @cyrus_class_2 = split(' ',$cyrus_class);
411 $wascyrus = &indexof($cyrus_class_2[0], @classes) >= 0;
412 if ($in{'passmode'} == 1 || $in{'passmode'} == 2) {
413 if (!$wascyrus && $in{'cyrus'}) {
414 &error($text{'usave_ecyruspass'});
418 # Run the pre-change command
419 &set_user_envs(\%uhash, 'MODIFY_USER',
420 $in{'passmode'} == 3 ? $in{'pass'} : "",
422 $merr = &making_changes();
423 &error(&text('usave_emaking', "<tt>$merr</tt>"))
426 # Rename home dir, if needed
427 $oldhome = $uinfo->get_value("homeDirectory");
428 if ($home ne $oldhome && -d $oldhome && !-e $home &&
430 $out = `mv '$oldhome' '$home' 2>&1`;
431 if ($?) { &error(&text('usave_emove', $out)); }
434 # Change GID on files if needed
435 $oldgid = $uinfo->get_value("gidNumber");
436 $olduid = $uinfo->get_value("uidNumber");
437 if ($oldgid != $gid && $in{'chgid'}) {
438 if ($in{'chgid'} == 1) {
439 &useradmin::recursive_change($home, $olduid,
443 &useradmin::recursive_change("/", $olduid,
448 # Change UID on files if needed
449 if ($olduid != $uid && $in{'chuid'}) {
450 if ($in{'chuid'} == 1) {
451 &useradmin::recursive_change($home, $olduid,
455 &useradmin::recursive_change("/", $olduid,
460 # Get properties for modified users
461 local @props = &split_props($config{'mod_props'}, \%uhash);
463 # Work out samba-related property changes
464 $oldpass = $uinfo->get_value('userPassword');
466 # Is a samba user .. add or update props
467 $passmode = $in{'passmode'};
468 if ($passmode == 2 && $wassamba &&
469 $in{'encpass'} eq $oldpass) {
473 &samba_properties(!$wassamba, \%uhash, $passmode,
474 $in{'pass'}, $schema, \@props, $ldap);
477 # Is no longer a samba user .. take away standard
479 &samba_removes(\%uhash, $schema, \@rprops);
482 # Work out imap-related property changes
486 if ($in{'cyrus'} && !$wascyrus) {
487 # Add any extra properties for IMAP users
488 push(@props, &split_props($config{'imap_props'}));
490 elsif (!$in{'cyrus'} && $wascyrus) {
491 # Take away properties for IMAP users
492 push(@rprops, &split_first($config{'imap_props'}));
493 &delete_mail_props();
496 # Add or update any extra LDAP fields
497 &parse_extra_fields($config{'fields'}, \@props, \@rprops,
500 # Add or update shadow LDAP fields
501 $shadow = &shadow_fields();
503 # Update the ldap database
505 push(@classes, $samba_class);
508 @classes = grep { $_ ne $samba_class } @classes;
511 push(@classes, split(' ',$cyrus_class));
514 @cyrus_class_4 = split(' ',$cyrus_class);
515 foreach $one_cyrus_class (@cyrus_class_4) {
516 @classes = grep { $_ ne $one_cyrus_class }
520 push(@classes, "shadowAccount") if ($shadow);
522 @classes = &unique(@classes);
523 @classes = grep { /\S/ } @classes; # Remove empty
524 @rprops = grep { defined($uinfo->get_value($_)) } @rprops;
526 if ($olduser ne $user) {
527 # Need to rename the LDAP dn itself, first
529 $base = &get_user_base();
530 $newdn = "uid=$user,$base";
531 $rv = $ldap->moddn($in{'dn'}, newrdn => "uid=$user");
533 &error(&text('usave_emoddn', $rv->error));
540 # Change the user's properties
541 %allprops = ( "cn" => $real,
544 "loginShell" => $shell,
545 "homeDirectory" => $home,
547 "userPassword" => $pass,
548 "objectClass" => \@classes,
550 if (&indexoflc("person", @classes) >= 0 &&
553 $allprops{'sn'} = $real;
555 $rv = $ldap->modify($newdn, 'replace' => \%allprops,
556 'delete' => \@rprops);
558 &error(&text('usave_emod', $rv->error));
561 if ($olduser ne $user) {
562 # Check if an addressbook dn exists
564 "ou=$olduser, $config{'addressbook'}";
565 $rv = $ldap->search(base => $olda,
567 filter => '(&(objectClass=organizationalUnit))');
568 ($oldbook) = $rv->all_entries;
571 # Need to rename the addressbook dn
572 $rv = $ldap->modify($olda, replace =>
575 &error(&text('usave_emodbook',
579 $rv = $ldap->moddn($olda, newrdn =>
582 &error(&text('usave_emodbookdn',
588 if ($in{'cyrus'} && !$wascyrus) {
589 # Adding IMAP support
590 if ($config{'addressbook'}) {
591 # Create addressbook entry
592 &setup_addressbook();
595 # Setup the imap account as well
596 &setup_imap(\%uhash, $quota);
598 elsif (!$in{'cyrus'} && $wascyrus) {
599 # Removing IMAP support
600 if ($config{'addressbook'}) {
601 # Delete addressbook entry
602 &delete_addressbook();
605 elsif ($in{'cyrus'} && $wascyrus) {
606 # Changing IMAP support
607 if (!$in{'quota_def'} && $config{'quota_support'}) {
608 &set_imap_quota(\%uhash, $in{'quota'});
613 if ($config{'secmode'} != 1) {
614 # Update any groups that the user has been added to/removed from
615 @sgnames = $config{'secmode'} == 2 ? split(/\s+/, $in{'sgid'})
616 : split(/\r?\n/, $in{'sgid'});
617 foreach $gname (@sgnames) {
620 $base = &get_group_base();
621 $rv = $ldap->search(base => $base,
622 filter => &group_filter());
623 foreach $g ($rv->all_entries) {
624 local @mems = $g->get_value("memberUid");
625 local $gname = $g->get_value("cn");
626 local $ldap_group_id = $g->get_value("gidNumber");
628 local $idx = &indexof($olduser, @mems);
629 if ($ingroup{$gname} && $idx<0) {
630 # Need to add to the group
632 push(@sgids, $ldap_group_id);
634 elsif (!$ingroup{$gname} && $idx>=0) {
635 # Need to remove from the group
636 splice(@mems, $idx, 1);
639 # Need to rename in group
641 push(@sgids, $ldap_group_id);
646 local $idx = &indexof($user, @mems);
647 if ($ingroup{$gname} && $idx<0) {
648 # Need to add to the group
650 push(@sgids, $ldap_group_id);
652 elsif (!$ingroup{$gname} && $idx>=0) {
653 # Need to remove from the group
654 splice(@mems, $idx, 1);
656 elsif ($ingroup{$gname} && $idx >=0) {
657 # already in this group
658 push(@sgids, $ldap_group_id);
664 # Actually change the group
665 $rv = $ldap->modify($g->dn(), replace =>
666 { 'memberUid' => \@mems });
668 &error(&text('usave_emodgroup', $g->get_value('cn'),
674 # Get the updated user object
675 $rv = $ldap->search(base => $newdn,
677 filter => &user_filter());
678 ($uinfo) = $rv->all_entries;
679 %user = &dn_to_hash($uinfo);
681 # Run post-change script
682 &set_user_envs(\%user, $in{'new'} ? 'CREATE_USER' : 'MODIFY_USER',
683 $in{'passmode'} == 3 ? $in{'pass'} : "", \@sgids);
686 # Run other modules' scripts
688 $user{'passmode'} = $in{'passmode'};
689 if ($in{'passmode'} == 2 && $user{'pass'} eq $ouser{'pass'}) {
690 # not changing password
691 $user{'passmode'} = 4;
693 $user{'plainpass'} = $in{'pass'} if ($in{'passmode'} == 3);
694 $ldap->unbind(); # force commit?
696 $user{'olduser'} = $ouser{'user'};
697 &useradmin::other_modules("useradmin_modify_user",
701 &useradmin::other_modules("useradmin_create_user",
704 $ldap = &ldap_connect();
709 delete($in{'passmode'});
710 &unlock_user_files();
711 &webmin_log(!$in{'new'} ? 'modify' : 'create', 'user', $user, \%in);
712 &redirect($in{'return'} || "");
715 # Add properties for mail and aliases
718 # Do nothing if no domain is set
719 return if (!$config{'domain'});
721 # Add surname and first name details
722 local ($autofirstname, $autolastname);
723 if ($firstname && $lastname) {
724 $autofirstname = $firstname;
725 $autolastname = $lastname;
727 elsif ($in{'real'} =~ /(\S+)\s+(\S+)$/) {
728 $autofirstname = lc($1);
729 $autolastname = lc($2);
731 elsif ($in{'real'} =~ /(\S+)/) {
732 $autofirstname = lc($1);
735 $autofirstname = lc($in{'user'});
738 if (&in_schema($schema, "mail")) {
739 if ($config{'mailfmt'} == 0) {
741 "$autofirstname.$autolastname\@$config{'domain'}")
745 "$user\@$config{'domain'}")
750 push(@props, "mail", "$autofirstname\@$config{'domain'}")
751 if (&in_schema($schema, "mail"));
755 local $aattr = $config{'maillocaladdress'} || "alias";
756 if (&in_schema($schema, $aattr)) {
757 local @alias = split(/\s+/, $in{'alias'});
759 if (!$config{'alias_same'}) {
760 ($dup, $dupwhat) = &check_duplicates($ldap, $aattr, \@alias, $in{'dn'});
761 $dup && &error(&text('save_ealiasdup', $dupwhat, $dup->dn()));
763 push(@props, $aattr, \@alias);
766 push(@rprops, $aattr);
769 local $battr = $config{'mailroutingaddress'};
770 push(@props, $battr, lc($in{'user'})."\@$config{'imap_host'}")
771 if ($battr ne "") && (&in_schema($schema, $battr));
774 # delete_mail_props()
775 # Take away any extra properties added by mail_props
776 sub delete_mail_props
778 local $aattr = $config{'maillocaladdress'} || "alias";
779 if (&in_schema($schema, $aattr)) {
780 push(@rprops, $aattr);
782 local $battr = $config{'mailroutingaddress'};
783 if (($battr ne "") && &in_schema($schema, $battr)) {
784 push(@rprops, $battr);
786 push(@rprops, "mail")
787 if (&in_schema($schema, "mail"));
790 sub delete_addressbook
792 return &delete_ldap_subtree($ldap, "ou=$user, $config{'addressbook'}");
797 if ($config{'given'}) {
799 if (&in_schema($schema, "gn")) {
800 push(@props, "gn", $firstname);
802 elsif (&in_schema($schema, "givenName")) {
803 push(@props, "givenName", $firstname)
806 if ($lastname && &in_schema($schema, "sn")) {
807 push(@props, "sn", $lastname);
809 if ($firstname || $lastname) {
810 push(@classes, $config{'given_class'});
813 if (&in_schema($schema, "gecos")) {
814 push(@props, "gecos", &remove_accents($in{'real'}));
820 if (&in_schema($schema, "shadowLastChange")) {
821 # Validate shadow-password inputs
822 $in{'min'} =~ /^\-?[0-9]*$/ ||
823 &error(&text('usave_emin', $in{'min'}));
824 if ($in{'min'} ne '') {
825 push(@props, "shadowMin", $in{'min'});
828 push(@rprops, "shadowMin");
830 $in{'max'} =~ /^\-?[0-9]*$/ ||
831 &error(&text('usave_emax', $in{'max'}));
832 if ($in{'max'} ne '') {
833 push(@props, "shadowMax", $in{'max'});
836 push(@rprops, "shadowMax");
838 if ($in{'expired'} ne "" && $in{'expirem'} ne ""
839 && $in{'expirey'} ne "") {
840 eval { $expire = timelocal(0, 0, 12,
843 $in{'expirey'}-1900); };
844 if ($@) { &error($text{'usave_eexpire'}); }
845 push(@props, "shadowExpire", int($expire / (60*60*24)));
848 push(@rprops, "shadowExpire");
850 $in{'warn'} =~ /^\-?[0-9]*$/ ||
851 &error(&text('usave_ewarn', $in{'warn'}));
852 if ($in{'warn'} ne '') {
853 push(@props, "shadowWarning", $in{'warn'});
856 push(@rprops, "shadowWarning");
858 $in{'inactive'} =~ /^\-?[0-9]*$/ ||
859 &error(&text('usave_einactive', $in{'inactive'}));
860 if ($in{'inactive'} ne '') {
861 push(@props, "shadowInactive", $in{'inactive'});
864 push(@rprops, "shadowInactive");
866 if ($in{'passmode'} == 3 ||
867 $in{'passmode'} == 2 && $pass ne $oldpass) {
868 $daynow = int(time() / (60*60*24));
869 push(@props, "shadowLastChange", $daynow);