Handle hostnames with upper-case letters
[webmin.git] / cluster-useradmin / save_user.cgi
1 #!/usr/local/bin/perl
2 # save_user.cgi
3 # Updates an existing user across multiple servers
4
5 require './cluster-useradmin-lib.pl';
6 require 'timelocal.pl';
7 &error_setup($text{'usave_err'});
8 &ReadParse();
9 &foreign_require("useradmin", "user-lib.pl");
10
11 # Strip out \n characters in inputs
12 $in{'real'} =~ s/\r|\n//g;
13 $in{'user'} =~ s/\r|\n//g;
14 $in{'pass'} =~ s/\r|\n//g;
15 $in{'encpass'} =~ s/\r|\n//g;
16 $in{'home'} =~ s/\r|\n//g;
17 $in{'gid'} =~ s/\r|\n//g;
18 $in{'uid'} =~ s/\r|\n//g;
19 $in{'othersh'} =~ s/\r|\n//g;
20
21 # Validate username
22 $user{'user'} = $in{'user'};
23 $in{'user'} =~ /^[^:\t]+$/ ||
24         &error(&text('usave_ebadname', $in{'user'}));
25 $err = &useradmin::check_username_restrictions($in{'user'});
26 &error($err) if ($err);
27
28 # Get host and old user
29 @hosts = &list_useradmin_hosts();
30 @servers = &list_servers();
31 ($host) = grep { $_->{'id'} == $in{'id'} } @hosts;
32 if ($in{'user'} ne $in{'olduser'}) {
33         # Renaming .. check for clash
34         foreach $h (@hosts) {
35                 local $ou = grep { $_->{'user'} eq $in{'olduser'} }
36                                  @{$h->{'users'}};
37                 local $nu = grep { $_->{'user'} eq $in{'user'} }
38                                  @{$h->{'users'}};
39                 local ($serv) = grep { $_->{'id'} == $h->{'id'} } @servers;
40                 &error(&text('usave_einuse', $serv->{'host'})) if ($ou && $nu)
41                 }
42         }
43
44 # Validate basic inputs
45 $in{'uid_def'} || $in{'uid'} =~ /^[0-9]+$/ ||
46         &error(&text('usave_euid', $in{'uid'}));
47 $in{'real_def'} || $in{'real'} =~ /^[^:]*$/ ||
48         &error(&text('usave_ereal', $in{'real'}));
49 $in{'gid_def'} || defined(getgrnam($in{'gid'})) ||
50         &error(&text('usave_egid', $in{'gid'}));
51 if ($uconfig{'extra_real'}) {
52         $in{'office_def'} || $in{'office'} =~ /^[^:]*$/ ||
53                 &error($text{'usave_eoffice'});
54         $in{'workph_def'} || $in{'workph'} =~ /^[^:]*$/ ||
55                 &error($text{'usave_eworkph'});
56         $in{'homeph_def'} || $in{'homeph'} =~ /^[^:]*$/ ||
57                 &error($text{'usave_ehomeph'});
58         }
59 $in{'home_def'} || $in{'home'} =~ /^\// ||
60         &error(&text('usave_ehome', $in{'home'}));
61
62 # Validate password
63 if ($in{'passmode'} == 3) {
64         local $err = &useradmin::check_password_restrictions(
65                         $in{'pass'}, $user{'user'});
66         &error($err) if ($err);
67         }
68
69 $pft = &foreign_call("useradmin", "passfiles_type");
70 if ($pft == 2) {
71         # Validate shadow-password inputs
72         if (!$in{'expire_def'} && $in{'expired'} ne "" &&
73             $in{'expirem'} ne "" && $in{'expirey'} ne "") {
74                 eval { $expire = timelocal(0, 0, 12, $in{'expired'},
75                                            $in{'expirem'}-1,
76                                            $in{'expirey'}-1900); };
77                 if ($@) { &error($text{'usave_eexpire'}); }
78                 $expire = int($expire / (60*60*24));
79                 }
80         else { $expire = ""; }
81         $in{'min_def'} || $in{'min'} =~ /^[0-9]*$/ ||
82                 &error(&text('usave_emin', $in{'min'}));
83         $in{'max_def'} || $in{'max'} =~ /^[0-9]*$/ ||
84                 &error(&text('usave_emax', $in{'max'}));
85         $in{'warn_def'} || $in{'warn'} =~ /^[0-9]*$/ ||
86                 &error(&text('usave_ewarn', $in{'warn'}));
87         $in{'inactive_def'} || $in{'inactive'} =~ /^[0-9]*$/ ||
88                 &error(&text('usave_einactive', $in{'inactive'}));
89         }
90 elsif ($pft == 1 || $pft == 6) {
91         # Validate BSD password inputs
92         if (!$in{'expire_def'} && $in{'expired'} ne "" &&
93             $in{'expirem'} ne "" && $in{'expirey'} ne "") {
94                 eval { $expire = timelocal(59, $in{'expiremi'},
95                                            $in{'expireh'},
96                                            $in{'expired'},
97                                            $in{'expirem'}-1,
98                                            $in{'expirey'}-1900); };
99                 if ($@) { &error($text{'usave_eexpire'}); }
100                 }
101         else { $expire = ""; }
102         if (!$in{'change_def'} && $in{'changed'} ne "" &&
103             $in{'changem'} ne "" && $in{'changey'} ne "") {
104                 eval { $change = timelocal(59, $in{'changemi'},
105                                            $in{'changeh'},
106                                            $in{'changed'},
107                                            $in{'changem'}-1,
108                                            $in{'changey'}-1900); };
109                 if ($@) { &error($text{'usave_echange'}); }
110                 }
111         else { $change = ""; }
112         $in{'class_def'} || $in{'class'} =~ /^([^: ]*)$/ ||
113                 &error(&text('usave_eclass', $in{'class'}));
114         }
115 elsif ($pft == 4) {
116         # Validate AIX password inputs
117         if (!$in{'expire_def'} && $in{'expired'} ne "" && $in{'expirem'} ne ""  
118                 && $in{'expirey'} ne "" ) {
119                 # Add a leading zero if only 1 digit long
120                 $in{'expirem'} =~ s/^(\d)$/0$1/;
121                 $in{'expired'} =~ s/^(\d)$/0$1/;
122                 $in{'expireh'} =~ s/^(\d)$/0$1/;
123                 $in{'expiremi'} =~ s/^(\d)$/0$1/;
124                 
125                 # Only use the last two digits of the year
126                 $in{'expirey'} =~ s/^\d\d(\d\d)$/$1/;
127                 
128                 # If the user didn't choose the hour and min make them 01
129                 $in{'expireh'} = "01" if $in{'expireh'} eq "";
130                 $in{'expiremi'} = "01" if $in{'expiremi'} eq "";
131                 $expire="$in{'expirem'}$in{'expired'}$in{'expireh'}$in{'expiremi'}$in{'expirey'}";
132                 }
133         else { $expire = ""; }
134         }
135
136 # Validate groups
137 foreach $h (@hosts) {
138         map { $hasgroup{$_->{'group'}}++ } @{$h->{'groups'}};
139         }
140 @check = $in{'sgid_def'} == 1 ? split(/\s+/, $in{'sgidadd'}) :
141          $in{'sgid_def'} == 2 ? split(/\s+/, $in{'sgiddel'}) : ( );
142 foreach $c (@check) {
143         $hasgroup{$c} || &error(&text('usave_esecgid', $c));
144         }
145
146 # Setup error handler for down hosts
147 sub mod_error
148 {
149 $mod_error_msg = join("", @_);
150 }
151 &remote_error_setup(\&mod_error);
152
153 # Do the changes across all hosts
154 &ui_print_header(undef, $text{'uedit_title'}, "");
155 $crypted = &foreign_call("useradmin", "encrypt_password", $in{'pass'});
156 foreach $host (@hosts) {
157         $mod_error_msg = undef;
158         ($user) = grep { $_->{'user'} eq $in{'olduser'} } @{$host->{'users'}};
159         next if (!$user);
160         local ($serv) = grep { $_->{'id'} == $host->{'id'} } @servers;
161         print "<b>",&text('usave_uon', $serv->{'desc'} ? $serv->{'desc'} :
162                                        $serv->{'host'}),"</b><p>\n";
163         print "<ul>\n";
164         &remote_foreign_require($serv->{'host'}, "useradmin", "user-lib.pl");
165         if ($mod_error_msg) {
166                 # Host is down ..
167                 print &text('usave_failed', $mod_error_msg),"<p>\n";
168                 print "</ul>\n";
169                 next;
170                 }
171         local @ulist = &remote_foreign_call($serv->{'host'}, "useradmin",
172                                             "list_users");
173         ($user) = grep { $_->{'user'} eq $in{'olduser'} } @ulist;
174         if (!$user) {
175                 # No longer exists?
176                 print "$text{'usave_gone'}<p>\n";
177                 print "</ul>\n";
178                 next;
179                 }
180         local %ouser = %$user;
181
182         # Update changed fields
183         $user->{'user'} = $in{'user'};
184         $user->{'olduser'} = $ouser{'user'};
185         $user->{'uid'} = $in{'uid'} if (!$in{'uid_def'});
186         if ($uconfig{'extra_real'}) {
187                 local @real = split(/,/, $user->{'real'});
188                 $real[0] = $in{'real'} if (!$in{'real_def'});
189                 $real[1] = $in{'office'} if (!$in{'office_def'});
190                 $real[2] = $in{'workph'} if (!$in{'workph_def'});
191                 $real[3] = $in{'homeph'} if (!$in{'homeph_def'});
192                 $real[4] = $in{'extra'} if (!$in{'extra_def'});
193                 $user->{'real'} = join(",", @real);
194                 }
195         else {
196                 $user->{'real'} = $in{'real'} if (!$in{'real_def'});
197                 }
198         if ($in{'home_def'} == 2) {
199                 $user->{'home'} = &auto_home_dir($uconfig{'home_base'},
200                                                  $in{'user'});
201                 }
202         elsif ($in{'home_def'} == 0) {
203                 $user->{'home'} = $in{'home'};
204                 }
205         $user->{'shell'} = $in{'shell'} if (!$in{'shell_def'});
206         if ($in{'passmode'} == 0) {
207                 $user->{'pass'} = "";
208                 }
209         elsif ($in{'passmode'} == 1) {
210                 $user->{'pass'} = $uconfig{'lock_string'};
211                 }
212         elsif ($in{'passmode'} == 2) {
213                 $user->{'pass'} = $in{'encpass'};
214                 }
215         elsif ($in{'passmode'} == 3) {
216                 $user->{'pass'} = $crypted;
217                 }
218         $user->{'passmode'} = $in{'passmode'} < 0 ? 2 : $in{'passmode'};
219         $user->{'gid'} = getgrnam($in{'gid'}) if (!$in{'gid_def'});
220
221         if ($pft == 1 || $pft == 6) {
222                 # Save BSD password inputs
223                 $user->{'expire'} = $expire if (!$in{'expire_def'});
224                 $user->{'change'} = $change if (!$in{'change_def'});
225                 $user->{'class'} = $in{'class'} if (!$in{'class_def'});
226                 }
227         elsif ($pft == 2) {
228                 # Save shadow password inputs
229                 $user->{'min'} = $in{'min'} if (!$in{'min_def'});
230                 $user->{'max'} = $in{'max'} if (!$in{'max_def'});
231                 $user->{'warn'} = $in{'warn'} if (!$in{'warn_def'});
232                 $user->{'inactive'} = $in{'inactive'} if (!$in{'inactive_def'});
233                 $user->{'expire'} = $expire if (!$in{'expire_def'});
234                 $user->{'change'} = 0 if ($in{'forcechange'});
235                 }
236         elsif ($pft == 4) {
237                 # Save AIX password inputs
238                 if (!$in{'flags_def'}) {
239                         $user->{'admin'} = $in{'flags'} =~ /admin/;
240                         $user->{'admchg'} = $in{'flags'} =~ /admchg/;
241                         $user->{'nocheck'} = $in{'flags'} =~ /nocheck/;
242                         }
243                 $user->{'expire'} = $expire if (!$in{'expire_def'});
244                 $user->{'min'} = $in{'min'} if (!$in{'min_def'});
245                 $user->{'max'} = $in{'max'} if (!$in{'max_def'});
246                 $user->{'warn'} = $in{'warn'} if (!$in{'warn_def'});
247                 }
248
249         # Run the pre-change command
250         $envpass = $in{'passmode'} == 3 ? $in{'pass'} : undef;
251         &remote_eval($serv->{'host'}, "useradmin", <<EOF
252 \$ENV{'USERADMIN_USER'} = '$user->{'user'}';
253 \$ENV{'USERADMIN_UID'} = '$user->{'uid'}';
254 \$ENV{'USERADMIN_REAL'} = '$user->{'real'}';
255 \$ENV{'USERADMIN_SHELL'} = '$user->{'shell'}';
256 \$ENV{'USERADMIN_HOME'} = '$user->{'home'}';
257 \$ENV{'USERADMIN_GID'} = '$user->{'gid'}';
258 \$ENV{'USERADMIN_PASS'} = '$envpass';
259 \$ENV{'USERADMIN_ACTION'} = 'MODIFY_USER';
260 EOF
261         );
262         $merr = &remote_foreign_call($serv->{'host'}, "useradmin",
263                                      "making_changes");
264         if (defined($merr)) {
265                 print &text('usave_emaking', "<tt>$merr</tt>"),"<p>\n";
266                 print "</ul>\n";
267                 next;
268                 }
269
270         # Update the user on the server
271         print "$text{'usave_update'}<br>\n";
272         &remote_foreign_call($serv->{'host'}, "useradmin", "modify_user",
273                              \%ouser, $user);
274         print "$text{'udel_done'}<p>\n";
275
276         # Make file changes
277         if ($in{'servs'} || $host eq $hosts[0]) {
278                 if ($ouser{'home'} ne $user->{'home'} && $in{'movehome'}) {
279                         print "$text{'usave_move'}<br>\n";
280                         &remote_eval($serv->{'host'}, "useradmin",
281                                 "-d '$ouser{'home'}' && !-e '$user->{'home'}' && system(\"mv -f '$ouser{'home'}' '$user->{'home'}'\")");
282                         print "$text{'udel_done'}<p>\n";
283                         }
284                 if ($ouser{'gid'} ne $user->{'gid'}) {
285                         if ($in{'chgid'} == 1) {
286                                 print "$text{'usave_gid'}<br>\n";
287                                 &remote_foreign_call(
288                                         $serv->{'host'}, "useradmin",
289                                         "recursive_change", $user->{'home'},
290                                         $ouser{'uid'}, $ouser{'gid'}, -1,
291                                         $user->{'gid'});
292                                 print "$text{'udel_done'}<p>\n";
293                                 }
294                         else {
295                                 print "$text{'usave_gid'}<br>\n";
296                                 &remote_foreign_call(
297                                         $serv->{'host'}, "useradmin",
298                                         "recursive_change", "/",
299                                         $ouser{'uid'}, $ouser{'gid'}, -1,
300                                         $user->{'gid'});
301                                 print "$text{'udel_done'}<p>\n";
302                                 }
303                         }
304                 if ($ouser{'uid'} ne $user->{'uid'}) {
305                         if ($in{'chuid'} == 1) {
306                                 print "$text{'usave_uid'}<br>\n";
307                                 &remote_foreign_call(
308                                         $serv->{'host'}, "useradmin",
309                                         "recursive_change", $user->{'home'},
310                                         $ouser{'uid'}, -1,
311                                         $user->{'uid'}, -1);
312                                 print "$text{'udel_done'}<p>\n";
313                                 }
314                         else {
315                                 print "$text{'usave_uid'}<br>\n";
316                                 &remote_foreign_call(
317                                         $serv->{'host'}, "useradmin",
318                                         "recursive_change", "/",
319                                         $ouser{'uid'}, -1,
320                                         $user->{'uid'}, -1);
321                                 print "$text{'udel_done'}<p>\n";
322                                 }
323                         }
324                 }
325
326         # Rename user in secondary groups
327         local @glist;
328         if ($in{'sgid_def'} || $user->{'user'} ne $ouser{'user'}) {
329                 @glist = &remote_foreign_call($serv->{'host'}, "useradmin",
330                                               "list_groups");
331                 }
332         if ($user->{'user'} ne $ouser{'user'}) {
333                 print "$text{'usave_rgroups'}<br>\n";
334                 foreach $group (@glist) {
335                         local @mems = split(/,/, $group->{'members'});
336                         local $idx = &indexof($ouser{'user'}, @mems);
337                         if ($idx >= 0) {
338                                 local %ogroup = %$group;
339                                 $mems[$idx] = $user->{'user'};
340                                 $group->{'members'} = join(",", @mems);
341                                 &remote_foreign_call($serv->{'host'},
342                                         "useradmin", "modify_group",
343                                         \%ogroup, $group);
344                                 }
345                         }
346                 print "$text{'udel_done'}<p>\n";
347                 }
348
349         # Save secondary group information
350         if ($in{'sgid_def'} == 1) {
351                 # Add to some groups
352                 print "$text{'usave_groups'}<br>\n";
353                 foreach $g (split(/\s+/, $in{'sgidadd'})) {
354                         local ($group) = grep { $_->{'group'} eq $g } @glist;
355                         if ($group) {
356                                 local %ogroup = %$group;
357                                 local @m = &unique(split(/,/,
358                                         $group->{'members'}), $user->{'user'});
359                                 $group->{'members'} = join(",", @m);
360                                 &remote_foreign_call($serv->{'host'},
361                                         "useradmin", "modify_group",
362                                         \%ogroup, $group);
363                                 }
364                         }
365                 print "$text{'udel_done'}<p>\n";
366                 }
367         elsif ($in{'sgid_def'} == 2) {
368                 # Remove from some groups
369                 print "$text{'udel_groups'}<br>\n";
370                 foreach $g (split(/\s+/, $in{'sgiddel'})) {
371                         local ($group) = grep { $_->{'group'} eq $g } @glist;
372                         if ($group) {
373                                 local %ogroup = %$group;
374                                 local @m = grep { $_ ne $user->{'user'} }
375                                             split(/,/, $group->{'members'});
376                                 $group->{'members'} = join(",", @m);
377                                 &remote_foreign_call($serv->{'host'},
378                                         "useradmin", "modify_group",
379                                         \%ogroup, $group);
380                                 }
381                         }
382                 print "$text{'udel_done'}<p>\n";
383                 }
384
385         # Run post-change command
386         &remote_foreign_call($serv->{'host'}, "useradmin", "made_changes");
387
388         if ($in{'others'}) {
389                 # Update the user in other modules
390                 print "$text{'usave_mothers'}<br>\n";
391                 $user->{'passmode'} = $in{'passmode'};
392                 if ($in{'passmode'} == 2 && $user->{'pass'} eq $ouser{'pass'}) {
393                         $user{'passmode'} = 4;
394                         }
395                 $user->{'plainpass'} = $in{'pass'} if ($in{'passmode'} == 3);
396                 &remote_foreign_call($serv->{'host'}, "useradmin",
397                                      "other_modules", "useradmin_modify_user",
398                                      $user, \%ouser);
399                 print "$text{'udel_done'}<p>\n";
400                 }
401
402         # Update in local list
403         $host->{'users'} = \@ulist;
404         if (@glist) {
405                 $host->{'groups'} = \@glist;
406                 }
407         &save_useradmin_host($host);
408         print "</ul>\n";
409         }
410 &webmin_log("modify", "user", $user->{'user'}, $user);
411
412 &ui_print_footer("", $text{'index_return'});
413