1010839021354dbda2ff7e877430fc44c84274d2
[webmin.git] / ldap-useradmin / save_user.cgi
1 #!/usr/local/bin/perl
2 # save_user.cgi
3 # Create, update or delete an LDAP user
4
5 require './ldap-useradmin-lib.pl';
6 use Time::Local;
7 &ReadParse();
8 $ldap = &ldap_connect();
9 $schema = $ldap->schema();
10 &lock_user_files();
11 if (!$in{'new'}) {
12         # Get existing user
13         $rv = $ldap->search(base => $in{'dn'},
14                             scope => 'base',
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'});
20         }
21 else {
22         $access{'ucreate'} || &error($text{'usave_ecreate'});
23         }
24
25 if ($in{'mailboxes'}) {
26         # Just re-direct to mailboxes page
27         &redirect("../mailboxes/list_mail.cgi?user=$ouser{'user'}");
28         exit;
29         }
30 elsif ($in{'switch'}) {
31         # Just re-direct to Usermin switch user program
32         &redirect("../usermin/switch.cgi?user=$ouser{'user'}");
33         exit;
34         }
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");
40         if ($in{'confirm'}) {
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>"))
46                         if (defined($merr));
47
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;
52
53                 # Delete from other modules
54                 %user = &dn_to_hash($uinfo);
55                 if ($in{'others'}) {
56                         print "$text{'udel_other'}<br>\n";
57                         &useradmin::other_modules("useradmin_delete_user",
58                                                   \%user);
59                         print "$text{'udel_done'}<p>\n";
60                         }
61
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);
70                         if ($idx >= 0) {
71                                 # Take out of this group
72                                 splice(@mems, $idx, 1);
73                                 $rv = $ldap->modify($g->dn(), replace =>
74                                         { 'memberUid' => \@mems });
75                                 if ($rv->code) {
76                                         &error(&text('usave_emodgroup',
77                                                      $g->get_value('cn'),
78                                                      $rv->error));
79                                         }
80                                 }
81                         }
82                 print "$text{'udel_done'}<p>\n";
83
84                 # Delete from the LDAP db
85                 print "$text{'udel_pass'}<br>\n";
86                 $rv = $ldap->delete($in{'dn'});
87                 if ($rv->code) {
88                         &error(&text('usave_edelete', $rv->error));
89                         }
90                 print "$text{'udel_done'}<p>\n";
91
92                 # Delete his addressbook entry
93                 if ($config{'addressbook'} && $wascyrus) {
94                         print "$text{'udel_book'}<br>\n";
95                         $err = &delete_addressbook();
96                         if ($err) {
97                                 print &text('udel_failed', $err),"<p>\n";
98                                 }
99                         else {
100                                 print "$text{'udel_done'}<p>\n";
101                                 }
102                         }
103
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
113                                 }
114                         print "$text{'udel_done'}<p>\n";
115
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"));
122                         $imap->logout();
123                         print "$text{'udel_done'}<p>\n";
124                         }
125                 }
126
127                 &made_changes();
128
129                 %p = ( %in, %user );
130                 &webmin_log("delete", "user", $user{'user'}, \%p);
131                 }
132         else {
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'} ] );
140                         }
141                 else {
142                         # Without home
143                         $msg = &text('udel_sure2', $user);
144                         @buts = ( [ undef, $text{'udel_del1'} ] );
145                         }
146                 print &ui_confirmation_form(
147                         "save_user.cgi",
148                         $msg,
149                         [ [ "dn", $in{'dn'} ],
150                           [ "confirm", 1 ],
151                           [ "delete", 1 ] ],
152                         \@buts,
153                         &ui_checkbox("others", 1, $text{'udel_dothers'},
154                                      $mconfig{'default_other'}),
155                         $user eq 'root' ?
156                           "<font color=#ff0000>$text{'udel_root'}</font>" : ""
157                         );
158                 }
159
160         $ldap->unbind();
161         &ui_print_footer("", $text{'index_return'});
162         exit;
163         }
164 elsif ($in{'raw'}) {
165         # Show all LDAP attributes for user
166         &redirect("raw.cgi?user=1&dn=".&urlize($in{'dn'}));
167         exit;
168         }
169 else {
170         # Validate inputs
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'});
177         $user = $users[0];
178         $in{'uid'} =~ /^\-?[0-9]+$/ || &error(&text('usave_euid', $in{'uid'}));
179         $uid = $in{'uid'};
180         $in{'real'} =~ /^[^:]*$/ || &error(&text('usave_ereal', $in{'real'}));
181         $firstname = $in{'firstname'};
182         $lastname = $in{'lastname'};
183         $real = $in{'real'};
184         $shell = $in{'shell'} eq '*' ? $in{'othersh'} : $in{'shell'};
185         if ($in{'new'}) {
186                 &check_user_used($ldap, $user) &&
187                         &error(&text('usave_einuse', $user));
188                 }
189
190         # Check for UID clash
191         if ($in{'new'} && !$access{'umultiple'}) {
192                 &check_uid_used($ldap, $uid) &&
193                         &error($text{'usave_euidused2'});
194                 }
195
196         # Validate IMAP quota
197         $quota = undef;
198         if ($config{'quota_support'} && !$in{'quota_def'} &&
199             defined($in{'quota'})) {
200                 $in{'quota'} =~ /^\d+$/ || &error($text{'usave_equota'});
201                 $quota = $in{'quota'};
202                 }
203         
204         #load main user group
205         if ($in{'gid'} =~ /^\d+$/) {
206                 $gid = $in{'gid'};
207                 }
208         else {
209                 $gid = &all_getgrnam($in{'gid'});
210                 defined($gid) || &error(&text('usave_egid', $in{'gid'}));
211                 }
212         $grp = &all_getgrgid($gid);
213
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);
218                         }
219                 else {
220                         $home = $ouser{'home'};
221                         }
222                 }
223         elsif ($mconfig{'home_base'} && $in{'home_base'}) {
224                 $home = &auto_home_dir($mconfig{'home_base'}, $user, $grp);
225                 }
226         else {
227                 $home = $in{'home'};
228                 $home =~ /^\// || &error(&text('usave_ehome', $home));
229                 }
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));
236                         }
237                 }
238
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) {
243                 # Password is blank
244                 if (!$mconfig{'empty_mode'}) {
245                         local $err = &useradmin::check_password_restrictions(
246                                         "", $user);
247                         &error($err) if ($err);
248                         }
249                 $pass = "";
250                 }
251         elsif ($in{'passmode'} == 1) {
252                 # Password is locked
253                 $pass = $mconfig{'lock_string'};
254                 }
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);
259                 }
260         elsif ($in{'passmode'} == 3) {
261                 # Normal password entered - check restrictions
262                 local $err = &useradmin::check_password_restrictions(
263                                 $in{'pass'}, $user);
264                 &error($err) if ($err);
265                 $pass = $pfx.&encrypt_password($in{'pass'});
266                 $plainpass = $in{'pass'};
267                 }
268         if ($in{'disable'} && ($in{'passmode'} == 2 || $in{'passmode'} == 3)) {
269                 $pass = $useradmin::disable_string.$pass;
270                 }
271
272         # Build useradmin-style hash of user details
273         local %uhash = ( 'user' => $user,
274                          'uid' => $uid,
275                          'gid' => $gid,
276                          'group' => $in{'group'},
277                          'real' => $real,
278                          'shell' => $shell,
279                          'pass' => $pass,
280                          'plainpass' => $plainpass,
281                          'home' => $home,
282                          'firstname' => $firstname,
283                          'lastname' => $lastname );
284
285         if ($in{'new'}) {
286                 defined(&all_getpwnam($user)) &&
287                         &error(&text('usave_einuse', $user));
288                 if ($in{'passmode'} == 1 || $in{'passmode'} == 2) {
289                         if ($in{'cyrus'}) {
290                                 &error($text{'usave_ecyruspass'});
291                                 }
292                         }
293
294                 # Run the pre-change command
295                 &set_user_envs(\%uhash, 'CREATE_USER',
296                                $in{'passmode'} == 3 ? $in{'pass'} : "",
297                                undef);
298                 $merr = &making_changes();
299                 &error(&text('usave_emaking', "<tt>$merr</tt>"))
300                         if (defined($merr));
301
302                 # Create home dir
303                 if (!-e $home && $in{'makehome'}) {
304                         &lock_file($home);
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', $!));
311                         &unlock_file($home);
312                         }
313
314                 # Get configured properties for new users
315                 local @props = &split_props($config{'props'}, \%uhash);
316                 if ($in{'cyrus'}) {
317                         push(@props, &split_props($config{'imap_props'},
318                                                   \%uhash));
319                         }
320
321                 # Build Samba-related properties
322                 if ($in{'samba'}) {
323                         &samba_properties(1, \%uhash, $in{'passmode'},
324                                           $in{'pass'}, $schema, \@props, $ldap);
325                         }
326
327                 if ($in{'cyrus'}) {
328                         # Build mail-related properties
329                         &mail_props();
330                         }
331
332                 # Add any extra LDAP fields
333                 &parse_extra_fields($config{'fields'}, \@props, \@rprops,
334                                     $ldap);
335
336                 # Add shadow LDAP fields
337                 $shadow = &shadow_fields();
338
339                 # Add to the ldap database
340                 @classes = ( "posixAccount", "shadowAccount" );
341                 if ($schema && $schema->objectclass("person") && $config{'person'}) {
342                         push(@classes, "person");
343                         }
344
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
349                 &name_fields();
350                 @classes = &unique(@classes);
351                 $base = &get_user_base();
352                 $newdn = "uid=$user,$base";
353                 @allprops = ( "cn" => $real,
354                               "uid" => \@users,
355                               "uidNumber" => $uid,
356                               "loginShell" => $shell,
357                               "homeDirectory" => $home,
358                               "gidNumber" => $gid,
359                               "userPassword" => $pass,
360                               "objectClass" => \@classes,
361                               @props );
362                 if (&indexoflc("person", @classes) >= 0 &&
363                     !&in_props(\@allprops, "sn")) {
364                         # Person needs an 'sn' too
365                         push(@allprops, "sn", $real);
366                         }
367                 $rv = $ldap->add($newdn, attr => \@allprops);
368                 if ($rv->code) {
369                         &error(&text('usave_eadd', $rv->error));
370                         }
371
372                 if ($in{'cyrus'}) {
373                         if ($config{'addressbook'}) {
374                                 # Create addressbook entry
375                                 &setup_addressbook(\%uhash);
376                                 }
377
378                         # Disconnect to save the changes
379                         $ldap->unbind();
380                         undef($ldap);
381
382                         # Create imap account
383                         &setup_imap(\%uhash, $quota);
384
385                         # Re-connect for later LDAP operations
386                         $ldap = &ldap_connect();
387                         }
388
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);
397                         }
398                 }
399         else {
400                 # Modifying a user
401                 $olduser = $uinfo->get_value('uid');
402                 if ($olduser ne $user) {
403                         defined(&all_getpwnam($user)) &&
404                                 &error(&text('usave_einuse', $user));
405                         }
406
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'});
415                                 }
416                         }
417
418                 # Run the pre-change command
419                 &set_user_envs(\%uhash, 'MODIFY_USER',
420                                $in{'passmode'} == 3 ? $in{'pass'} : "",
421                                undef);
422                 $merr = &making_changes();
423                 &error(&text('usave_emaking', "<tt>$merr</tt>"))
424                         if (defined($merr));
425
426                 # Rename home dir, if needed
427                 $oldhome = $uinfo->get_value("homeDirectory");
428                 if ($home ne $oldhome && -d $oldhome && !-e $home &&
429                     $in{'movehome'}) {
430                         $out = `mv '$oldhome' '$home' 2>&1`;
431                         if ($?) { &error(&text('usave_emove', $out)); }
432                         }
433
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,
440                                                              $oldgid, -1, $gid);
441                                 }
442                         else {
443                                 &useradmin::recursive_change("/", $olduid,
444                                                              $oldgid, -1, $gid);
445                                 }
446                         }
447
448                 # Change UID on files if needed
449                 if ($olduid != $uid && $in{'chuid'}) {
450                         if ($in{'chuid'} == 1) {
451                                 &useradmin::recursive_change($home, $olduid,
452                                                              -1, $uid, -1);
453                                 }
454                         else {
455                                 &useradmin::recursive_change("/", $olduid,
456                                                              -1, $uid, -1);
457                                 }
458                         }
459
460                 # Get properties for modified users
461                 local @props = &split_props($config{'mod_props'}, \%uhash);
462
463                 # Work out samba-related property changes
464                 $oldpass = $uinfo->get_value('userPassword');
465                 if ($in{'samba'}) {
466                         # Is a samba user .. add or update props
467                         $passmode = $in{'passmode'};
468                         if ($passmode == 2 && $wassamba &&
469                             $in{'encpass'} eq $oldpass) {
470                                 # Not being changed
471                                 $passmode = 4;
472                                 }
473                         &samba_properties(!$wassamba, \%uhash, $passmode,
474                                           $in{'pass'}, $schema, \@props, $ldap);
475                         }
476                 elsif ($wassamba) {
477                         # Is no longer a samba user .. take away standard
478                         # samba properties
479                         &samba_removes(\%uhash, $schema, \@rprops);
480                         }
481
482                 # Work out imap-related property changes
483                 if ($in{'cyrus'}) {
484                         &mail_props();
485                         }
486                 if ($in{'cyrus'} && !$wascyrus) {
487                         # Add any extra properties for IMAP users
488                         push(@props, &split_props($config{'imap_props'}));
489                         }
490                 elsif (!$in{'cyrus'} && $wascyrus) {
491                         # Take away properties for IMAP users
492                         push(@rprops, &split_first($config{'imap_props'}));
493                         &delete_mail_props();
494                         }
495
496                 # Add or update any extra LDAP fields
497                 &parse_extra_fields($config{'fields'}, \@props, \@rprops,
498                                     $ldap, $in{'dn'});
499
500                 # Add or update shadow LDAP fields
501                 $shadow = &shadow_fields();
502
503                 # Update the ldap database
504                 if ($in{'samba'}) {
505                         push(@classes, $samba_class);
506                         }
507                 else {
508                         @classes = grep { $_ ne $samba_class } @classes;
509                         }
510                 if ($in{'cyrus'}) {
511                         push(@classes, split(' ',$cyrus_class));
512                         }
513                 else {
514                        @cyrus_class_4 = split(' ',$cyrus_class);
515                        foreach $one_cyrus_class (@cyrus_class_4) {     
516                                @classes = grep { $_ ne $one_cyrus_class }
517                                                @classes;
518                                }
519                         }
520                 push(@classes, "shadowAccount") if ($shadow);
521                 &name_fields();
522                 @classes = &unique(@classes);
523                 @classes = grep { /\S/ } @classes;      # Remove empty
524                 @rprops = grep { defined($uinfo->get_value($_)) } @rprops;
525
526                 if ($olduser ne $user) {
527                         # Need to rename the LDAP dn itself, first
528                         $renaming = 1;
529                         $base = &get_user_base();
530                         $newdn = "uid=$user,$base";
531                         $rv = $ldap->moddn($in{'dn'}, newrdn => "uid=$user");
532                         if ($rv->code) {
533                                 &error(&text('usave_emoddn', $rv->error));
534                                 }
535                         }
536                 else {
537                         $newdn = $in{'dn'};
538                         }
539
540                 # Change the user's properties
541                 %allprops = ( "cn" => $real,
542                               "uid" => \@users,
543                               "uidNumber" => $uid,
544                               "loginShell" => $shell,
545                               "homeDirectory" => $home,
546                               "gidNumber" => $gid,
547                               "userPassword" => $pass,
548                               "objectClass" => \@classes,
549                               @props );
550                 if (&indexoflc("person", @classes) >= 0 &&
551                     !$allprops{'sn'}) {
552                         # Person needs 'sn'
553                         $allprops{'sn'} = $real;
554                         }
555                 $rv = $ldap->modify($newdn, 'replace' => \%allprops,
556                                             'delete' => \@rprops);
557                 if ($rv->code) {
558                         &error(&text('usave_emod', $rv->error));
559                         }
560
561                 if ($olduser ne $user) {
562                         # Check if an addressbook dn exists
563                         local $olda =
564                                 "ou=$olduser, $config{'addressbook'}";
565                         $rv = $ldap->search(base => $olda,
566                                             scope => 'base',
567                                             filter => '(&(objectClass=organizationalUnit))');
568                         ($oldbook) = $rv->all_entries;
569
570                         if ($oldbook) {
571                                 # Need to rename the addressbook dn
572                                 $rv = $ldap->modify($olda, replace =>
573                                         { "ou" => $user });
574                                 if ($rv->code) {
575                                         &error(&text('usave_emodbook',
576                                                      $rv->error));
577                                         }
578
579                                 $rv = $ldap->moddn($olda, newrdn =>
580                                         "ou=$user");
581                                 if ($rv->code) {
582                                         &error(&text('usave_emodbookdn',
583                                                      $rv->error));
584                                         }
585                                 }
586                         }
587
588                 if ($in{'cyrus'} && !$wascyrus) {
589                         # Adding IMAP support
590                         if ($config{'addressbook'}) {
591                                 # Create addressbook entry
592                                 &setup_addressbook();
593                                 }
594
595                         # Setup the imap account as well
596                         &setup_imap(\%uhash, $quota);
597                         }
598                 elsif (!$in{'cyrus'} && $wascyrus) {
599                         # Removing IMAP support
600                         if ($config{'addressbook'}) {
601                                 # Delete addressbook entry
602                                 &delete_addressbook();
603                                 }
604                         }
605                 elsif ($in{'cyrus'} && $wascyrus) {
606                         # Changing IMAP support
607                         if (!$in{'quota_def'} && $config{'quota_support'}) {
608                                 &set_imap_quota(\%uhash, $in{'quota'});
609                                 }
610                         }
611                 }
612
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) {
618                         $ingroup{$gname}++;
619                         }
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");
627                         if ($renaming) {
628                                 local $idx = &indexof($olduser, @mems);
629                                 if ($ingroup{$gname} && $idx<0) {
630                                         # Need to add to the group
631                                         push(@mems, $user);
632                                         push(@sgids, $ldap_group_id);
633                                         }
634                                 elsif (!$ingroup{$gname} && $idx>=0) {
635                                         # Need to remove from the group
636                                         splice(@mems, $idx, 1);
637                                         }
638                                 elsif ($idx >= 0) {
639                                         # Need to rename in group
640                                         $mems[$idx] = $user;
641                                         push(@sgids, $ldap_group_id);
642                                         }
643                                 else { next; }
644                                 }
645                         else {
646                                 local $idx = &indexof($user, @mems);
647                                 if ($ingroup{$gname} && $idx<0) {
648                                         # Need to add to the group
649                                         push(@mems, $user);
650                                         push(@sgids, $ldap_group_id);
651                                         }
652                                 elsif (!$ingroup{$gname} && $idx>=0) {
653                                         # Need to remove from the group
654                                         splice(@mems, $idx, 1);
655                                         }
656                                 elsif ($ingroup{$gname} && $idx >=0) {
657                                         # already in this group
658                                         push(@sgids, $ldap_group_id);
659                                         next;
660                                         }
661                                 else { next; }
662                                 }
663
664                         # Actually change the group
665                         $rv = $ldap->modify($g->dn(), replace =>
666                                 { 'memberUid' => \@mems });
667                         if ($rv->code) {
668                                 &error(&text('usave_emodgroup', $g->get_value('cn'),
669                                              $rv->error));
670                                 }
671                         }
672                 }
673
674         # Get the updated user object
675         $rv = $ldap->search(base => $newdn,
676                             scope => 'base',
677                             filter => &user_filter());
678         ($uinfo) = $rv->all_entries;
679         %user = &dn_to_hash($uinfo);
680
681         # Run post-change script
682         &set_user_envs(\%user, $in{'new'} ? 'CREATE_USER' : 'MODIFY_USER',
683                        $in{'passmode'} == 3 ? $in{'pass'} : "", \@sgids);
684         &made_changes();
685
686         # Run other modules' scripts
687         if ($in{'others'}) {
688                 $user{'passmode'} = $in{'passmode'};
689                 if ($in{'passmode'} == 2 && $user{'pass'} eq $ouser{'pass'}) {
690                         # not changing password
691                         $user{'passmode'} = 4;
692                         }
693                 $user{'plainpass'} = $in{'pass'} if ($in{'passmode'} == 3);
694                 $ldap->unbind();        # force commit?
695                 if (!$in{'new'}) {
696                         $user{'olduser'} = $ouser{'user'};
697                         &useradmin::other_modules("useradmin_modify_user",
698                                                   \%user, \%ouser);
699                         }
700                 else {
701                         &useradmin::other_modules("useradmin_create_user",
702                                                   \%user);
703                         }
704                 $ldap = &ldap_connect();
705                 }
706         }
707 $ldap->unbind();
708 delete($in{'pass'});
709 delete($in{'passmode'});
710 &unlock_user_files();
711 &webmin_log(!$in{'new'} ? 'modify' : 'create', 'user', $user, \%in);
712 &redirect($in{'return'} || "");
713
714 # mail_props()
715 # Add properties for mail and aliases
716 sub mail_props
717 {
718 # Do nothing if no domain is set
719 return if (!$config{'domain'});
720
721 # Add surname and first name details
722 local ($autofirstname, $autolastname);
723 if ($firstname && $lastname) {
724         $autofirstname = $firstname;
725         $autolastname = $lastname;
726         }
727 elsif ($in{'real'} =~ /(\S+)\s+(\S+)$/) {
728         $autofirstname = lc($1);
729         $autolastname = lc($2);
730         }
731 elsif ($in{'real'} =~ /(\S+)/) {
732         $autofirstname = lc($1);
733         }
734 else {
735         $autofirstname = lc($in{'user'});
736         }
737 if ($autolastname) {
738         if (&in_schema($schema, "mail")) {
739                 if ($config{'mailfmt'} == 0) {
740                         push(@props, "mail",
741                                      "$autofirstname.$autolastname\@$config{'domain'}")
742                         }
743                 else {
744                         push(@props, "mail",
745                                      "$user\@$config{'domain'}")
746                         }
747                 }
748         }
749 else {
750         push(@props, "mail", "$autofirstname\@$config{'domain'}")
751                 if (&in_schema($schema, "mail"));
752         }
753
754 # Add extra aliases
755 local $aattr = $config{'maillocaladdress'} || "alias";
756 if (&in_schema($schema, $aattr)) {
757         local @alias = split(/\s+/, $in{'alias'});
758         if ($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()));
762                         }
763                 push(@props, $aattr, \@alias);
764                 }
765         else {
766                 push(@rprops, $aattr);
767                 }
768         }
769 local $battr = $config{'mailroutingaddress'};
770 push(@props, $battr, lc($in{'user'})."\@$config{'imap_host'}")
771         if ($battr ne "") && (&in_schema($schema, $battr));
772 }
773
774 # delete_mail_props()
775 # Take away any extra properties added by mail_props
776 sub delete_mail_props
777 {
778 local $aattr = $config{'maillocaladdress'} || "alias";
779 if (&in_schema($schema, $aattr)) {
780         push(@rprops, $aattr);
781         }
782 local $battr = $config{'mailroutingaddress'};
783 if (($battr ne "") && &in_schema($schema, $battr)) {
784         push(@rprops, $battr);
785         }
786 push(@rprops, "mail")
787         if (&in_schema($schema, "mail"));
788 }
789
790 sub delete_addressbook
791 {
792 return &delete_ldap_subtree($ldap, "ou=$user, $config{'addressbook'}");
793 }
794
795 sub name_fields
796 {
797 if ($config{'given'}) {
798         if ($firstname) {
799                 if (&in_schema($schema, "gn")) {
800                         push(@props, "gn", $firstname);
801                         }
802                 elsif (&in_schema($schema, "givenName")) {
803                         push(@props, "givenName", $firstname)
804                         }
805                 }
806         if ($lastname && &in_schema($schema, "sn")) {
807                 push(@props, "sn", $lastname);
808                 }
809         if ($firstname || $lastname) {
810                 push(@classes, $config{'given_class'});
811                 }
812         }
813 if (&in_schema($schema, "gecos")) {
814         push(@props, "gecos", &remove_accents($in{'real'}));
815         }
816 }
817
818 sub shadow_fields
819 {
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'});
826                 }
827         else {
828                 push(@rprops, "shadowMin");
829                 }
830         $in{'max'} =~ /^\-?[0-9]*$/ ||
831                 &error(&text('usave_emax', $in{'max'}));
832         if ($in{'max'} ne '') {
833                 push(@props, "shadowMax", $in{'max'});
834                 }
835         else {
836                 push(@rprops, "shadowMax");
837                 }
838         if ($in{'expired'} ne "" && $in{'expirem'} ne ""
839             && $in{'expirey'} ne "") {
840                 eval { $expire = timelocal(0, 0, 12,
841                                         $in{'expired'},
842                                         $in{'expirem'}-1,
843                                         $in{'expirey'}-1900); };
844                 if ($@) { &error($text{'usave_eexpire'}); }
845                 push(@props, "shadowExpire", int($expire / (60*60*24)));
846                 }
847         else {
848                 push(@rprops, "shadowExpire");
849                 }
850         $in{'warn'} =~ /^\-?[0-9]*$/ ||
851             &error(&text('usave_ewarn', $in{'warn'}));
852         if ($in{'warn'} ne '') {
853                 push(@props, "shadowWarning", $in{'warn'});
854                 }
855         else {
856                 push(@rprops, "shadowWarning");
857                 }
858         $in{'inactive'} =~ /^\-?[0-9]*$/ ||
859             &error(&text('usave_einactive', $in{'inactive'}));
860         if ($in{'inactive'} ne '') {
861                 push(@props, "shadowInactive", $in{'inactive'});
862                 }
863         else {
864                 push(@rprops, "shadowInactive");
865                 }
866         if ($in{'passmode'} == 3 ||
867             $in{'passmode'} == 2 && $pass ne $oldpass) {
868                 $daynow = int(time() / (60*60*24));
869                 push(@props, "shadowLastChange", $daynow);
870                 }
871         return 1;
872         }
873 else {
874         return 0;
875         }
876 }
877