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 # Compute and validate home directory
205 if ($access{'autohome'}) {
206 if ($in{'new'} || $ouser{'user'} ne $user) {
207 $home = &auto_home_dir($access{'home'}, $user, $grp);
210 $home = $ouser{'home'};
213 elsif ($mconfig{'home_base'} && $in{'home_base'}) {
214 $home = &auto_home_dir($mconfig{'home_base'}, $user);
218 $home =~ /^\// || &error(&text('usave_ehome', $home));
220 if (!$access{'autohome'}) {
221 $home =~ /^\// || &error(&text('usave_ehome', $home));
222 $al = length($access{'home'});
223 if (length($home) < $al ||
224 substr($home, 0, $al) ne $access{'home'}) {
225 &error(&text('usave_ehomepath', $home));
229 local $pfx = $config{'md5'} == 1 || $config{'md5'} == 3 ? "{md5}" :
230 $config{'md5'} == 4 ? "{ssha}" :
231 $config{'md5'} == 0 ? "{crypt}" : "";
232 if ($in{'passmode'} == 0) {
234 if (!$mconfig{'empty_mode'}) {
235 local $err = &useradmin::check_password_restrictions(
237 &error($err) if ($err);
241 elsif ($in{'passmode'} == 1) {
243 $pass = $mconfig{'lock_string'};
245 elsif ($in{'passmode'} == 2) {
246 # Specific encrypted password entered, or possibly no change
247 $pass = $in{'encpass'};
248 $pass = $pfx.$pass if ($pass !~ /^\{[a-z0-9]+\}/i && $pfx);
250 elsif ($in{'passmode'} == 3) {
251 # Normal password entered - check restrictions
252 local $err = &useradmin::check_password_restrictions(
254 &error($err) if ($err);
255 $pass = $pfx.&encrypt_password($in{'pass'});
256 $plainpass = $in{'pass'};
258 if ($in{'disable'} && ($in{'passmode'} == 2 || $in{'passmode'} == 3)) {
259 $pass = $useradmin::disable_string.$pass;
261 if ($in{'gid'} =~ /^\d+$/) {
265 $gid = &all_getgrnam($in{'gid'});
266 defined($gid) || &error(&text('usave_egid', $in{'gid'}));
268 $grp = &all_getgrgid($gid);
270 # Build useradmin-style hash of user details
271 local %uhash = ( 'user' => $user,
274 'group' => $in{'group'},
278 'plainpass' => $plainpass,
280 'firstname' => $firstname,
281 'lastname' => $lastname );
284 defined(&all_getpwnam($user)) &&
285 &error(&text('usave_einuse', $user));
286 if ($in{'passmode'} == 1 || $in{'passmode'} == 2) {
288 &error($text{'usave_ecyruspass'});
292 # Run the pre-change command
293 &set_user_envs(\%uhash, 'CREATE_USER',
294 $in{'passmode'} == 3 ? $in{'pass'} : "",
296 $merr = &making_changes();
297 &error(&text('usave_emaking', "<tt>$merr</tt>"))
301 if (!-e $home && $in{'makehome'}) {
303 mkdir($home, oct($mconfig{'homedir_perms'})) ||
304 &error(&text('usave_emkdir', $!));
305 chmod(oct($mconfig{'homedir_perms'}), $home) ||
306 &error(&text('usave_echmod', $!));
307 chown($uid, $gid, $home) ||
308 &error(&text('usave_echown', $!));
312 # Get configured properties for new users
313 local @props = &split_props($config{'props'}, \%uhash);
315 push(@props, &split_props($config{'imap_props'},
319 # Build Samba-related properties
321 &samba_properties(1, \%uhash, $in{'passmode'},
322 $in{'pass'}, $schema, \@props, $ldap);
326 # Build mail-related properties
330 # Add any extra LDAP fields
331 &parse_extra_fields($config{'fields'}, \@props, \@rprops,
334 # Add shadow LDAP fields
335 $shadow = &shadow_fields();
337 # Add to the ldap database
338 @classes = ( "posixAccount", "shadowAccount" );
339 if ($schema && $schema->objectclass("person") && $config{'person'}) {
340 push(@classes, "person");
343 push(@classes, split(/\s+/, $config{'other_class'}));
344 push(@classes, $samba_class) if ($in{'samba'});
345 push(@classes, split(' ',$cyrus_class)) if ($in{'cyrus'});
346 @classes = grep { /\S/ } @classes; # Remove empty
348 @classes = &unique(@classes);
349 $base = &get_user_base();
350 $newdn = "uid=$user,$base";
351 @allprops = ( "cn" => $real,
354 "loginShell" => $shell,
355 "homeDirectory" => $home,
357 "userPassword" => $pass,
358 "objectClass" => \@classes,
360 if (&indexoflc("person", @classes) >= 0 &&
361 !&in_props(\@allprops, "sn")) {
362 # Person needs an 'sn' too
363 push(@allprops, "sn", $real);
365 $rv = $ldap->add($newdn, attr => \@allprops);
367 &error(&text('usave_eadd', $rv->error));
371 if ($config{'addressbook'}) {
372 # Create addressbook entry
373 &setup_addressbook(\%uhash);
376 # Disconnect to save the changes
380 # Create imap account
381 &setup_imap(\%uhash, $quota);
383 # Re-connect for later LDAP operations
384 $ldap = &ldap_connect();
387 # Copy files into user's directory
388 if ($in{'makehome'} && $mconfig{'user_files'}) {
389 local $uf = $mconfig{'user_files'};
390 local $shell = $user{'shell'}; $shell =~ s/^(.*)\///g;
391 $uf =~ s/\$group/$in{'gid'}/g;
392 $uf =~ s/\$gid/$user{'gid'}/g;
393 $uf =~ s/\$shell/$shell/g;
394 &useradmin::copy_skel_files($uf, $home, $uid, $gid);
399 $olduser = $uinfo->get_value('uid');
400 if ($olduser ne $user) {
401 defined(&all_getpwnam($user)) &&
402 &error(&text('usave_einuse', $user));
405 # Work out old settings
406 @classes = $uinfo->get_value("objectClass");
407 $wassamba = &indexof($samba_class, @classes) >= 0;
408 @cyrus_class_2 = split(' ',$cyrus_class);
409 $wascyrus = &indexof($cyrus_class_2[0], @classes) >= 0;
410 if ($in{'passmode'} == 1 || $in{'passmode'} == 2) {
411 if (!$wascyrus && $in{'cyrus'}) {
412 &error($text{'usave_ecyruspass'});
416 # Run the pre-change command
417 &set_user_envs(\%uhash, 'MODIFY_USER',
418 $in{'passmode'} == 3 ? $in{'pass'} : "",
420 $merr = &making_changes();
421 &error(&text('usave_emaking', "<tt>$merr</tt>"))
424 # Rename home dir, if needed
425 $oldhome = $uinfo->get_value("homeDirectory");
426 if ($home ne $oldhome && -d $oldhome && !-e $home &&
428 $out = `mv '$oldhome' '$home' 2>&1`;
429 if ($?) { &error(&text('usave_emove', $out)); }
432 # Change GID on files if needed
433 $oldgid = $uinfo->get_value("gidNumber");
434 $olduid = $uinfo->get_value("uidNumber");
435 if ($oldgid != $gid && $in{'chgid'}) {
436 if ($in{'chgid'} == 1) {
437 &useradmin::recursive_change($home, $olduid,
441 &useradmin::recursive_change("/", $olduid,
446 # Change UID on files if needed
447 if ($olduid != $uid && $in{'chuid'}) {
448 if ($in{'chuid'} == 1) {
449 &useradmin::recursive_change($home, $olduid,
453 &useradmin::recursive_change("/", $olduid,
458 # Get properties for modified users
459 local @props = &split_props($config{'mod_props'}, \%uhash);
461 # Work out samba-related property changes
462 $oldpass = $uinfo->get_value('userPassword');
464 # Is a samba user .. add or update props
465 $passmode = $in{'passmode'};
466 if ($passmode == 2 && $wassamba &&
467 $in{'encpass'} eq $oldpass) {
471 &samba_properties(!$wassamba, \%uhash, $passmode,
472 $in{'pass'}, $schema, \@props, $ldap);
475 # Is no longer a samba user .. take away standard
477 &samba_removes(\%uhash, $schema, \@rprops);
480 # Work out imap-related property changes
484 if ($in{'cyrus'} && !$wascyrus) {
485 # Add any extra properties for IMAP users
486 push(@props, &split_props($config{'imap_props'}));
488 elsif (!$in{'cyrus'} && $wascyrus) {
489 # Take away properties for IMAP users
490 push(@rprops, &split_first($config{'imap_props'}));
491 &delete_mail_props();
494 # Add or update any extra LDAP fields
495 &parse_extra_fields($config{'fields'}, \@props, \@rprops,
498 # Add or update shadow LDAP fields
499 $shadow = &shadow_fields();
501 # Update the ldap database
503 push(@classes, $samba_class);
506 @classes = grep { $_ ne $samba_class } @classes;
509 push(@classes, split(' ',$cyrus_class));
512 @cyrus_class_4 = split(' ',$cyrus_class);
513 foreach $one_cyrus_class (@cyrus_class_4) {
514 @classes = grep { $_ ne $one_cyrus_class }
518 push(@classes, "shadowAccount") if ($shadow);
520 @classes = &unique(@classes);
521 @classes = grep { /\S/ } @classes; # Remove empty
522 @rprops = grep { defined($uinfo->get_value($_)) } @rprops;
524 if ($olduser ne $user) {
525 # Need to rename the LDAP dn itself, first
527 $base = &get_user_base();
528 $newdn = "uid=$user,$base";
529 $rv = $ldap->moddn($in{'dn'}, newrdn => "uid=$user");
531 &error(&text('usave_emoddn', $rv->error));
538 # Change the user's properties
539 %allprops = ( "cn" => $real,
542 "loginShell" => $shell,
543 "homeDirectory" => $home,
545 "userPassword" => $pass,
546 "objectClass" => \@classes,
548 if (&indexoflc("person", @classes) >= 0 &&
551 $allprops{'sn'} = $real;
553 $rv = $ldap->modify($newdn, 'replace' => \%allprops,
554 'delete' => \@rprops);
556 &error(&text('usave_emod', $rv->error));
559 if ($olduser ne $user) {
560 # Check if an addressbook dn exists
562 "ou=$olduser, $config{'addressbook'}";
563 $rv = $ldap->search(base => $olda,
565 filter => '(&(objectClass=organizationalUnit))');
566 ($oldbook) = $rv->all_entries;
569 # Need to rename the addressbook dn
570 $rv = $ldap->modify($olda, replace =>
573 &error(&text('usave_emodbook',
577 $rv = $ldap->moddn($olda, newrdn =>
580 &error(&text('usave_emodbookdn',
586 if ($in{'cyrus'} && !$wascyrus) {
587 # Adding IMAP support
588 if ($config{'addressbook'}) {
589 # Create addressbook entry
590 &setup_addressbook();
593 # Setup the imap account as well
594 &setup_imap(\%uhash, $quota);
596 elsif (!$in{'cyrus'} && $wascyrus) {
597 # Removing IMAP support
598 if ($config{'addressbook'}) {
599 # Delete addressbook entry
600 &delete_addressbook();
603 elsif ($in{'cyrus'} && $wascyrus) {
604 # Changing IMAP support
605 if (!$in{'quota_def'} && $config{'quota_support'}) {
606 &set_imap_quota(\%uhash, $in{'quota'});
611 if ($config{'secmode'} != 1) {
612 # Update any groups that the user has been added to/removed from
613 @sgnames = $config{'secmode'} == 2 ? split(/\s+/, $in{'sgid'})
614 : split(/\r?\n/, $in{'sgid'});
615 foreach $gname (@sgnames) {
618 $base = &get_group_base();
619 $rv = $ldap->search(base => $base,
620 filter => &group_filter());
621 foreach $g ($rv->all_entries) {
622 local @mems = $g->get_value("memberUid");
623 local $gname = $g->get_value("cn");
624 local $ldap_group_id = $g->get_value("gidNumber");
626 local $idx = &indexof($olduser, @mems);
627 if ($ingroup{$gname} && $idx<0) {
628 # Need to add to the group
630 push(@sgids, $ldap_group_id);
632 elsif (!$ingroup{$gname} && $idx>=0) {
633 # Need to remove from the group
634 splice(@mems, $idx, 1);
637 # Need to rename in group
639 push(@sgids, $ldap_group_id);
644 local $idx = &indexof($user, @mems);
645 if ($ingroup{$gname} && $idx<0) {
646 # Need to add to the group
648 push(@sgids, $ldap_group_id);
650 elsif (!$ingroup{$gname} && $idx>=0) {
651 # Need to remove from the group
652 splice(@mems, $idx, 1);
654 elsif ($ingroup{$gname} && $idx >=0) {
655 # already in this group
656 push(@sgids, $ldap_group_id);
662 # Actually change the group
663 $rv = $ldap->modify($g->dn(), replace =>
664 { 'memberUid' => \@mems });
666 &error(&text('usave_emodgroup', $g->get_value('cn'),
672 # Get the updated user object
673 $rv = $ldap->search(base => $newdn,
675 filter => &user_filter());
676 ($uinfo) = $rv->all_entries;
677 %user = &dn_to_hash($uinfo);
679 # Run post-change script
680 &set_user_envs(\%user, $in{'new'} ? 'CREATE_USER' : 'MODIFY_USER',
681 $in{'passmode'} == 3 ? $in{'pass'} : "", \@sgids);
684 # Run other modules' scripts
686 $user{'passmode'} = $in{'passmode'};
687 if ($in{'passmode'} == 2 && $user{'pass'} eq $ouser{'pass'}) {
688 # not changing password
689 $user{'passmode'} = 4;
691 $user{'plainpass'} = $in{'pass'} if ($in{'passmode'} == 3);
692 $ldap->unbind(); # force commit?
694 $user{'olduser'} = $ouser{'user'};
695 &useradmin::other_modules("useradmin_modify_user",
699 &useradmin::other_modules("useradmin_create_user",
702 $ldap = &ldap_connect();
707 delete($in{'passmode'});
708 &unlock_user_files();
709 &webmin_log(!$in{'new'} ? 'modify' : 'create', 'user', $user, \%in);
710 &redirect($in{'return'} || "");
713 # Add properties for mail and aliases
716 # Do nothing if no domain is set
717 return if (!$config{'domain'});
719 # Add surname and first name details
720 local ($autofirstname, $autolastname);
721 if ($firstname && $lastname) {
722 $autofirstname = $firstname;
723 $autolastname = $lastname;
725 elsif ($in{'real'} =~ /(\S+)\s+(\S+)$/) {
726 $autofirstname = lc($1);
727 $autolastname = lc($2);
729 elsif ($in{'real'} =~ /(\S+)/) {
730 $autofirstname = lc($1);
733 $autofirstname = lc($in{'user'});
736 if (&in_schema($schema, "mail")) {
737 if ($config{'mailfmt'} == 0) {
739 "$autofirstname.$autolastname\@$config{'domain'}")
743 "$user\@$config{'domain'}")
748 push(@props, "mail", "$autofirstname\@$config{'domain'}")
749 if (&in_schema($schema, "mail"));
753 local $aattr = $config{'maillocaladdress'} || "alias";
754 if (&in_schema($schema, $aattr)) {
755 local @alias = split(/\s+/, $in{'alias'});
757 if (!$config{'alias_same'}) {
758 ($dup, $dupwhat) = &check_duplicates($ldap, $aattr, \@alias, $in{'dn'});
759 $dup && &error(&text('save_ealiasdup', $dupwhat, $dup->dn()));
761 push(@props, $aattr, \@alias);
764 push(@rprops, $aattr);
767 local $battr = $config{'mailroutingaddress'};
768 push(@props, $battr, lc($in{'user'})."\@$config{'imap_host'}")
769 if ($battr ne "") && (&in_schema($schema, $battr));
772 # delete_mail_props()
773 # Take away any extra properties added by mail_props
774 sub delete_mail_props
776 local $aattr = $config{'maillocaladdress'} || "alias";
777 if (&in_schema($schema, $aattr)) {
778 push(@rprops, $aattr);
780 local $battr = $config{'mailroutingaddress'};
781 if (($battr ne "") && &in_schema($schema, $battr)) {
782 push(@rprops, $battr);
784 push(@rprops, "mail")
785 if (&in_schema($schema, "mail"));
788 sub delete_addressbook
790 return &delete_ldap_subtree($ldap, "ou=$user, $config{'addressbook'}");
795 if ($config{'given'}) {
797 if (&in_schema($schema, "gn")) {
798 push(@props, "gn", $firstname);
800 elsif (&in_schema($schema, "givenName")) {
801 push(@props, "givenName", $firstname)
804 if ($lastname && &in_schema($schema, "sn")) {
805 push(@props, "sn", $lastname);
807 if ($firstname || $lastname) {
808 push(@classes, $config{'given_class'});
811 if (&in_schema($schema, "gecos")) {
812 push(@props, "gecos", &remove_accents($in{'real'}));
818 if (&in_schema($schema, "shadowLastChange")) {
819 # Validate shadow-password inputs
820 $in{'min'} =~ /^\-?[0-9]*$/ ||
821 &error(&text('usave_emin', $in{'min'}));
822 if ($in{'min'} ne '') {
823 push(@props, "shadowMin", $in{'min'});
826 push(@rprops, "shadowMin");
828 $in{'max'} =~ /^\-?[0-9]*$/ ||
829 &error(&text('usave_emax', $in{'max'}));
830 if ($in{'max'} ne '') {
831 push(@props, "shadowMax", $in{'max'});
834 push(@rprops, "shadowMax");
836 if ($in{'expired'} ne "" && $in{'expirem'} ne ""
837 && $in{'expirey'} ne "") {
838 eval { $expire = timelocal(0, 0, 12,
841 $in{'expirey'}-1900); };
842 if ($@) { &error($text{'usave_eexpire'}); }
843 push(@props, "shadowExpire", int($expire / (60*60*24)));
846 push(@rprops, "shadowExpire");
848 $in{'warn'} =~ /^\-?[0-9]*$/ ||
849 &error(&text('usave_ewarn', $in{'warn'}));
850 if ($in{'warn'} ne '') {
851 push(@props, "shadowWarning", $in{'warn'});
854 push(@rprops, "shadowWarning");
856 $in{'inactive'} =~ /^\-?[0-9]*$/ ||
857 &error(&text('usave_einactive', $in{'inactive'}));
858 if ($in{'inactive'} ne '') {
859 push(@props, "shadowInactive", $in{'inactive'});
862 push(@rprops, "shadowInactive");
864 if ($in{'passmode'} == 3 ||
865 $in{'passmode'} == 2 && $pass ne $oldpass) {
866 $daynow = int(time() / (60*60*24));
867 push(@props, "shadowLastChange", $daynow);