Handle hostnames with upper-case letters
[webmin.git] / cluster-useradmin / create_user.cgi
1 #!/usr/local/bin/perl
2 # create_user.cgi
3 # Creates a new user on multiple machines
4
5 require './cluster-useradmin-lib.pl';
6 require 'timelocal.pl';
7 &foreign_require("useradmin", "user-lib.pl");
8 &error_setup($text{'usave_err'});
9 &ReadParse();
10 @hosts = &list_useradmin_hosts();
11 @servers = &list_servers();
12
13 # Strip out \n characters in inputs
14 $in{'real'} =~ s/\r|\n//g;
15 $in{'user'} =~ s/\r|\n//g;
16 $in{'pass'} =~ s/\r|\n//g;
17 $in{'encpass'} =~ s/\r|\n//g;
18 $in{'home'} =~ s/\r|\n//g;
19 $in{'gid'} =~ s/\r|\n//g;
20 $in{'uid'} =~ s/\r|\n//g;
21 $in{'othersh'} =~ s/\r|\n//g;
22
23 # Validate username
24 $user{'user'} = $in{'user'};
25 $in{'user'} =~ /^[^:\t]+$/ ||
26         &error(&text('usave_ebadname', $in{'user'}));
27 $err = &useradmin::check_username_restrictions($in{'user'});
28 &error($err) if ($err);
29
30 $| = 1;
31 &ui_print_header(undef, $text{'uedit_title2'}, "");
32
33 # Work out which hosts to create on
34 @already = grep { local ($alr) = grep { $_->{'user'} eq $in{'user'} }
35                                     @{$_->{'users'}};
36                   $alr } @hosts;
37 @hosts = &create_on_parse("usave_header", \@already, $in{'user'});
38
39 # Check for username clash
40 foreach $h (@hosts) {
41         ($s) = grep { $_->{'id'} == $h->{'id'} } @servers;
42         foreach $u (@{$h->{'users'}}) {
43                 if ($u->{'user'} eq $in{'user'}) {
44                         &error(&text('usave_einuse', $s->{'host'}));
45                         }
46                 }
47         }
48
49 # Validate and store basic inputs
50 $in{'uid'} =~ /^[0-9]+$/ || &error(&text('usave_euid', $in{'uid'}));
51 $in{'real'} =~ /^[^:]*$/ || &error(&text('usave_ereal', $in{'real'}));
52 if ($in{'shell'} eq "*") { $in{'shell'} = $in{'othersh'}; }
53 $user{'uid'} = $in{'uid'};
54 ($group) = grep { $_->{'group'} eq $in{'gid'} } @{$hosts[0]->{'groups'}};
55 if ($group) {
56         $user{'gid'} = $group->{'gid'};
57         }
58 else {
59         $user{'gid'} = getgrnam($in{'gid'});
60         }
61 if ($user{'gid'} eq "") { &error(&text('usave_egid', $in{'gid'})); }
62 if ($uconfig{'extra_real'}) {
63         $in{'office'} =~ /^[^:]*$/ || &error($text{'usave_eoffice'});
64         $in{'workph'} =~ /^[^:]*$/ || &error($text{'usave_eworkph'});
65         $in{'homeph'} =~ /^[^:]*$/ || &error($text{'usave_ehomeph'});
66         $user{'real'} = join(",", $in{'real'}, $in{'office'}, $in{'workph'},
67                                   $in{'homeph'});
68         $user{'real'} .= ",$in{'extra'}" if ($in{'extra'});
69         $user{'real'} =~ s/,+$//;
70         }
71 else {
72         $user{'real'} = $in{'real'};
73         }
74 if ($uconfig{'home_base'} && $in{'home_base'}) {
75         $user{'home'} = &auto_home_dir($uconfig{'home_base'}, $in{'user'});
76         }
77 else {
78         $user{'home'} = $in{'home'};
79         }
80 $user{'shell'} = $in{'shell'};
81 foreach $gid (split(/\0/, $in{'sgid'})) {
82         $ingroup{$gid}++;
83         }
84
85 # Store password input
86 $crypted = &foreign_call("useradmin", "encrypt_password", $in{'pass'});
87 if ($in{'passmode'} == 0) {
88         if (!$uconfig{'empty_mode'}) {
89                 local $err = &useradmin::check_password_restrictions(
90                                 "", $user{'user'});
91                 &error($err) if ($err);
92                 }
93         $user{'pass'} = "";
94         }
95 elsif ($in{'passmode'} == 1) { $user{'pass'} = $uconfig{'lock_string'}; }
96 elsif ($in{'passmode'} == 2) { $user{'pass'} = $in{'encpass'}; }
97 elsif ($in{'passmode'} == 3) {
98         local $err = &useradmin::check_password_restrictions(
99                         $in{'pass'}, $user{'user'});
100         &error($err) if ($err);
101         $user{'pass'} = $crypted;
102         }
103
104 $pft = &foreign_call("useradmin", "passfiles_type");
105 if ($pft == 2) {
106         # Validate shadow-password inputs
107         if ($in{'expired'} ne "" && $in{'expirem'} ne ""
108             && $in{'expirey'} ne "") {
109                 eval { $expire = timelocal(0, 0, 12, $in{'expired'},
110                                            $in{'expirem'}-1,
111                                            $in{'expirey'}-1900); };
112                 if ($@) { &error("invalid expiry date"); }
113                 $expire = int($expire / (60*60*24));
114                 }
115         else { $expire = ""; }
116         $in{'min'} =~ /^[0-9]*$/ ||
117                 &error(&text('usave_emin', $in{'min'}));
118         $in{'max'} =~ /^[0-9]*$/ ||
119                 &error(&text('usave_emax', $in{'max'}));
120         $in{'warn'} =~ /^[0-9]*$/ ||
121                 &error(&text('usave_ewarn', $in{'warn'}));
122         $in{'inactive'} =~ /^[0-9]*$/ ||
123                 &error(&text('usave_einactive', $in{'inactive'}));
124         $user{'expire'} = $expire;
125         $user{'min'} = $in{'min'};
126         $user{'max'} = $in{'max'};
127         $user{'warn'} = $in{'warn'};
128         $user{'inactive'} = $in{'inactive'};
129         $user{'change'} = int(time() / (60*60*24));
130         }
131 elsif ($pft == 1 || $pft == 6) {
132         # Validate BSD-password inputs
133         if ($in{'expired'} ne "" && $in{'expirem'} ne ""
134             && $in{'expirey'} ne "") {
135                 eval { $expire = timelocal(59, $in{'expiremi'},
136                                            $in{'expireh'},
137                                            $in{'expired'},
138                                            $in{'expirem'}-1,
139                                            $in{'expirey'}-1900); };
140                 if ($@) { &error($text{'usave_eexpire'}); }
141                 }
142         else { $expire = ""; }
143         if ($in{'changed'} ne "" && $in{'changem'} ne ""
144             && $in{'changey'} ne "") {
145                 eval { $change = timelocal(59, $in{'changemi'},
146                                            $in{'changeh'},
147                                            $in{'changed'},
148                                            $in{'changem'}-1,
149                                            $in{'changey'}-1900); };
150                 if ($@) { &error($text{'usave_echange'}); }
151                 }
152         else { $change = ""; }
153         $in{'class'} =~ /^([^: ]*)$/ ||
154                 &error(&text('usave_eclass', $in{'class'}));
155         $user{'expire'} = $expire;
156         $user{'change'} = $change;
157         $user{'class'} = $in{'class'};
158         }
159 elsif ($pft == 4) {
160         # Validate AIX-style password inputs
161         if ($in{'expired'} ne "" && $in{'expirem'} ne ""        
162                 && $in{'expirey'} ne "" ) {
163                 # Add a leading zero if only 1 digit long
164                 $in{'expirem'} =~ s/^(\d)$/0$1/;
165                 $in{'expired'} =~ s/^(\d)$/0$1/;
166                 $in{'expireh'} =~ s/^(\d)$/0$1/;
167                 $in{'expiremi'} =~ s/^(\d)$/0$1/;
168                 
169                 # Only use the last two digits of the year
170                 $in{'expirey'} =~ s/^\d\d(\d\d)$/$1/;
171                 
172                 # If the user didn't choose the hour and min make them 01
173                 $in{'expireh'} = "01" if $in{'expireh'} eq "";
174                 $in{'expiremi'} = "01" if $in{'expiremi'} eq "";
175                 $expire="$in{'expirem'}$in{'expired'}$in{'expireh'}$in{'expiremi'}$in{'expirey'}";
176                 }
177         else { $expire = ""; }
178         $user{'admin'} = $in{'flags'} =~ /admin/;
179         $user{'admchg'} = $in{'flags'} =~ /admchg/;
180         $user{'nocheck'} = $in{'flags'} =~ /nocheck/;
181         $user{'expire'} = $expire;
182         $user{'min'} = $in{'min'};
183         $user{'max'} = $in{'max'};
184         $user{'warn'} = $in{'warn'};
185         $user{'change'} = time();
186         }
187
188 # Setup error handler for down hosts
189 sub add_error
190 {
191 $add_error_msg = join("", @_);
192 }
193 &remote_error_setup(\&add_error);
194
195 # Loop through selected hosts to create user
196 foreach $host (@hosts) {
197         $add_error_msg = undef;
198         local ($serv) = grep { $_->{'id'} == $host->{'id'} } @servers;
199         &remote_foreign_require($serv->{'host'}, "useradmin", "user-lib.pl");
200         print "<b>",&text('usave_con', &server_name($serv)),"</b><p>\n";
201         print "<ul>\n";
202         if ($add_error_msg) {
203                 # Host is down ..
204                 print &text('usave_failed', $add_error_msg),"<p>\n";
205                 print "</ul>\n";
206                 next;
207                 }
208         local @glist;
209         if ($in{'sgid'} ne '') {
210                 @glist = &remote_foreign_call($serv->{'host'}, "useradmin",
211                                               "list_groups");
212                 }
213
214         # Run the pre-change command
215         $envsgids = join(",", split(/\0/, $in{'sgid'}));
216         $envpass = $in{'passmode'} == 3 ? $in{'pass'} : undef;
217         &remote_eval($serv->{'host'}, "useradmin", <<EOF
218 \$ENV{'USERADMIN_USER'} = '$user{'user'}';
219 \$ENV{'USERADMIN_UID'} = '$user{'uid'}';
220 \$ENV{'USERADMIN_REAL'} = '$user{'real'}';
221 \$ENV{'USERADMIN_SHELL'} = '$user{'shell'}';
222 \$ENV{'USERADMIN_HOME'} = '$user{'home'}';
223 \$ENV{'USERADMIN_GID'} = '$user{'gid'}';
224 \$ENV{'USERADMIN_SECONDARY'} = '$envsgids';
225 \$ENV{'USERADMIN_PASS'} = '$envpass';
226 \$ENV{'USERADMIN_ACTION'} = 'CREATE_USER';
227 EOF
228         );
229         $merr = &remote_foreign_call($serv->{'host'}, "useradmin",
230                                      "making_changes");
231         if (defined($merr)) {
232                 print &text('usave_emaking', "<tt>$merr</tt>"),"<p>\n";
233                 print "</ul>\n";
234                 next;
235                 }
236
237         # Create the home directory
238         local $made_home;
239         if ($in{'servs'} || $host eq $hosts[0]) {
240                 if ($in{'makehome'}) {
241                         local $exists = &remote_eval($serv->{'host'},
242                                 "useradmin", "-e '$user{'home'}'");
243                         if (!$exists) {
244                                 print "$text{'usave_mkhome'}<br>\n";
245                                 local $rv = &remote_eval($serv->{'host'}, "useradmin",
246                                         "mkdir('$user{'home'}', oct('$uconfig{'homedir_perms'}')) && chmod(oct('$uconfig{'homedir_perms'}'), '$user{'home'}') ? undef : \$!");
247                                 $rv && &error(&text('usave_emkdir', $rv));
248                                 $rv = &remote_eval($serv->{'host'}, "useradmin",
249                                         "chown($user{'uid'}, $user{'gid'}, '$user{'home'}')");
250                                 $rv || &error(&text('usave_echown', $rv));
251                                 $made_home = 1;
252                                 print "$text{'udel_done'}<p>\n";
253                                 }
254                         }
255                 }
256
257         # Save user details
258         print "$text{'usave_create'}<br>\n";
259         &remote_foreign_call($serv->{'host'}, "useradmin",
260                              "create_user", \%user);
261         print "$text{'udel_done'}<p>\n";
262
263         # Copy files into user's directory
264         if ($in{'servs'} || $host eq $hosts[0]) {
265                 local $fconfig = &remote_foreign_config($serv->{'host'},
266                                                         "useradmin");
267                 if ($in{'copy_files'} && $made_home) {
268                         print "$text{'usave_copy'}<br>\n";
269                         local $uf = $fconfig->{'user_files'};
270                         $uf =~ s/\$group/$in{'gid'}/g;
271                         $uf =~ s/\$gid/$user{'gid'}/g;
272                         &remote_foreign_call($serv->{'host'}, "useradmin",
273                                 "copy_skel_files", $uf, $user{'home'},
274                                 $user{'uid'}, $user{'gid'});
275                         print "$text{'udel_done'}<p>\n";
276                         }
277                 }
278
279         # Update groups
280         local @sgids = split(/\0/, $in{'sgid'});
281         print "$text{'usave_groups'}<br>\n" if (@sgids);
282         foreach $gid (@sgids) {
283                 foreach $group (@glist) {
284                         if ($group->{'gid'} == $gid) {
285                                 # Add to this group
286                                 local %ogroup = %$group;
287                                 $group->{'members'} = join(",", $user{'user'},
288                                         split(/,/, $group->{'members'}));
289                                 &remote_foreign_call($serv->{'host'},
290                                         "useradmin", "modify_group",
291                                         \%ogroup, $group);
292                                 }
293                         }
294                 }
295         print "$text{'udel_done'}<p>\n" if (@sgids);
296
297         # Run the post-change command
298         &remote_foreign_call($serv->{'host'}, "useradmin", "made_changes");
299
300         if ($in{'others'}) {
301                 # Create in other modules on the server
302                 print "$text{'usave_others'}<br>\n";
303                 $user{'passmode'} = $in{'passmode'};
304                 $user{'plainpass'} = $in{'pass'} if ($in{'passmode'} == 3);
305                 &remote_foreign_call($serv->{'host'}, "useradmin",
306                                      "other_modules", "useradmin_create_user",
307                                      \%user);
308                 print "$text{'udel_done'}<p>\n";
309                 }
310
311         # Update host
312         push(@{$host->{'users'}}, \%user);
313         if (@glist) {
314                 $host->{'groups'} = \@glist;
315                 }
316         &save_useradmin_host($host);
317         print "</ul>\n";
318         }
319 &webmin_log("create", "user", $user{'user'}, \%user);
320
321 &ui_print_footer("", $text{'index_return'});
322