Handle hostnames with upper-case letters
[webmin.git] / ldap-useradmin / batch_exec.cgi
1 #!/usr/local/bin/perl
2 # batch_exec.cgi
3 # Execute create/modify/delete commands in a batch file
4
5 require './ldap-useradmin-lib.pl';
6 $access{'batch'} || &error($text{'batch_ecannot'});
7 if ($ENV{'REQUEST_METHOD'} eq 'GET') {
8         &ReadParse();
9         }
10 else {
11         &ReadParseMime();
12         }
13 if ($in{'source'} == 0) {
14         $data = $in{'file'};
15         $data =~ /\S/ || &error($text{'batch_efile'});
16         }
17 elsif ($in{'source'} == 1) {
18         open(LOCAL, $in{'local'}) || &error($text{'batch_elocal'});
19         while(<LOCAL>) {
20                 $data .= $_;
21                 }
22         close(LOCAL);
23         }
24 elsif ($in{'source'} == 2) {
25         $data = $in{'text'};
26         $data =~ /\S/ || &error($text{'batch_etext'});
27         }
28
29 &ui_print_unbuffered_header(undef, $text{'batch_title'}, "");
30
31 $ldap = &ldap_connect();
32 $schema = $ldap->schema();
33 $pft = $schema->attribute("shadowLastChange") ? 2 : 0;
34 &lock_user_files();
35
36 # Work out a good base UID for new users
37 $newuid = $mconfig{'base_uid'};
38 $newgid = $mconfig{'base_gid'};
39 @glist = &list_groups();
40
41 # Process the file
42 $lnum = $created = $modified = $deleted = 0;
43 print "<pre>\n";
44 LINE: foreach $line (split(/[\r\n]+/, $data)) {
45         $lnum++;
46         $line =~ s/^\s*#.*$//;
47         next if ($line !~ /\S/);
48         local @line = split(/:/, $line, -1);
49         local %user;
50         if ($line[0] eq 'create') {
51                 # Creating a new user
52                 local @attrs;
53                 if ($pft == 2) {
54                         # SYSV-style passwd and shadow information
55                         if (@line < 13) {
56                                 print &text('batch_elen', $lnum, 13),"\n";
57                                 next;
58                                 }
59                         $user{'min'} = $line[8];
60                         $user{'max'} = $line[9];
61                         $user{'warn'} = $line[10];
62                         $user{'inactive'} = $line[11];
63                         $user{'expire'} = $line[12];
64                         if ($in{'forcechange'} == 1){
65                             $user{'change'} = 0;
66                         } else {
67                             $user{'change'} = $line[2] eq '' ? '' :
68                                                 int(time() / (60*60*24));
69                         }
70                         @attrs = @line[13 .. $#line];
71                         }
72                 else {
73                         # Classic passwd file information
74                         if (@line < 8) {
75                                 print &text('batch_elen', $lnum, 8),"\n";
76                                 next;
77                                 }
78                         @attrs = @line[9 .. $#line];
79                         }
80
81                 # Parse common fields
82                 if (!$line[1]) {
83                         print &text('batch_eline', $lnum),"\n";
84                         next;
85                         }
86                 $user{'user'} = $line[1];
87                 $err = &useradmin::check_username_restrictions($user{'user'});
88                 if ($err) {
89                         print &text('batch_echeck', $lnum, $err),"\n";
90                         next;
91                         }
92                 if (&check_user_used($ldap, $user{'user'})) {
93                         print &text('batch_euser', $lnum, $user{'user'}),"\n";
94                         next;
95                         }
96                 if ($line[3] !~ /^\d+$/) {
97                         # make up a UID
98                         while(&check_uid_used($ldap, $newuid) ||
99                               $mconfig{'new_user_gid'} &&
100                               &check_gid_used($ldap, $newuid)) {
101                                 $newuid++;
102                                 }
103                         $user{'uid'} = $newuid;
104                         }
105                 else {
106                         # use the given UID
107                         if (&check_uid_used($ldap, $line[3])) {
108                                 print &text('batch_ecaccess', $lnum,
109                                             $text{'usave_euidused2'}),"\n";
110                                 next;
111                                 }
112                         $user{'uid'} = $line[3];
113                         }
114                 if (!-r $line[7]) {
115                         print &text('batch_eshell', $lnum, $line[7]),"\n";
116                         next;
117                         }
118                 $user{'shell'} = $line[7];
119                 $user{'real'} = $line[5];
120                 local @gids = split(/[ ,]+/, $line[4]);
121                 $user{'gid'} = $gids[0];
122                 local $grp = &all_getgrgid($gids[0]);
123
124                 if ($line[6] eq '' && $mconfig{'home_base'}) {
125                         # Choose home dir automatically
126                         $user{'home'} = &auto_home_dir(
127                                 $mconfig{'home_base'}, $user{'user'}, $user{'gid'});
128                         }
129                 elsif ($line[6] !~ /^\//) {
130                         print &text('batch_ehome', $lnum,$line[6]),"\n";
131                         next;
132                         }
133                 else {
134                         # Use given home dir
135                         $user{'home'} = $line[6];
136                         }
137
138                 # Work out secondary group membership
139                 local @secs;
140                 if (@gids > 1) {
141                         local $i;
142                         for($i=1; $i<@gids; $i++) {
143                                 local ($group) =
144                                     grep { $_->{'gid'} eq $gids[$i] } @glist;
145                                 push(@secs, $group) if ($group);
146                                 }
147                         }
148
149                 # Work out password
150                 if ($in{'crypt'}) {
151                         $user{'pass'} = $line[2];
152                         $user{'passmode'} = 2;
153                         }
154                 elsif ($line[2] eq 'x') {
155                         # No login allowed
156                         $user{'pass'} = $mconfig{'lock_string'};
157                         $user{'passmode'} = 1;
158                         }
159                 elsif ($line[2] eq '') {
160                         # No password needed
161                         $user{'pass'} = '';
162                         $user{'passmode'} = 0;
163                         }
164                 else {
165                         # Normal password
166                         $user{'pass'} = &encrypt_password($line[2]);
167                         $user{'passmode'} = 3;
168                         $user{'plainpass'} = $line[2];
169                         }
170
171                 $user{'ldap_attrs'} ||= [ ];
172                 if ($in{'samba'}) {
173                         # Add Samba-specific properties
174                         push(@{$user{'ldap_class'}}, $config{'samba_class'});
175                         &samba_properties(1, \%user, $user{'passmode'},
176                                           $user{'plainpass'}, $schema,
177                                           $user{'ldap_attrs'}, $ldap);
178                         }
179
180                 # Add extra LDAP attrs
181                 foreach $a (@attrs) {
182                         next if (!$a);
183                         if ($a =~ /^([^=]+)=(.*)/) {
184                                 push(@{$user{'ldap_attrs'}}, $1, $2);
185                                 }
186                         else {
187                                 print &text('batch_eattr', $lnum, $a),"\n";
188                                 next LINE;
189                                 }
190                         }
191
192                 # Run the before command
193                 &set_user_envs(\%user, 'CREATE_USER', $user{'plainpass'},
194                                [ map { $_->{'gid'} } @secs ]);
195                 $merr = &making_changes();
196                 &error(&text('usave_emaking', "<tt>$merr</tt>"))
197                         if (defined($merr));
198
199                 if ($user{'gid'} !~ /^\d+$/) {
200                         # Need to create a new group for the user
201                         if (&check_group_used($ldap, $user{'user'})) {
202                                 print &text('batch_egtaken', $lnum,
203                                             $user{'user'}),"\n";
204                                 next;
205                                 }
206
207                         if ($mconfig{'new_user_gid'}) {
208                                 $newgid = $user{'uid'};
209                                 }
210                         else {
211                                 while(&check_gid_used($ldap, $newgid)) {
212                                         $newgid++;
213                                         }
214                                 }
215                         local %group;
216                         $group{'group'} = $user{'user'};
217                         $user{'gid'} = $group{'gid'} = $newgid;
218                         &create_group(\%group);
219                         }
220
221                 # Create home directory
222                 if ($in{'makehome'} && !-d $user{'home'}) {
223                         &lock_file($user{'home'});
224                         if (!mkdir($user{'home'}, oct($mconfig{'homedir_perms'}))) {
225                                 print &text('batch_emkdir', $user{'home'}, $!),"\n";
226                                 }
227                         chmod(oct($mconfig{'homedir_perms'}), $user{'home'});
228                         chown($user{'uid'}, $user{'gid'}, $user{'home'});
229                         &unlock_file($user{'home'});
230                         }
231
232                 # Create the user!
233                 &create_user(\%user);
234
235                 # Add user to some secondary groups
236                 local $group;
237                 foreach $group (@secs) {
238                         local @mems = split(/,/ , $group->{'members'});
239                         push(@mems, $user{'user'});
240                         $group->{'members'} = join(",", @mems);
241                         &modify_group($group, $group);
242                         }
243
244                 # Re-get the new user object
245                 $base = &get_user_base();
246                 $newdn = "uid=$user{'user'},$base";
247                 $rv = $ldap->search(base => $newdn,
248                                     scope => 'base',
249                                     filter => &user_filter());
250                 ($uinfo) = $rv->all_entries;
251                 %user = &dn_to_hash($uinfo);
252
253                 # Call the post command
254                 &set_user_envs(\%user, 'CREATE_USER', $user{'plainpass'},
255                                [ map { $_->{'gid'} } @secs ]);
256                 &made_changes();
257
258                 # Call other modules, ignoring any failures
259                 $error_must_die = 1;
260                 eval {
261                         &other_modules("useradmin_create_user", \%user)
262                                 if ($in{'others'});
263                         };
264                 $other_err = $@;
265                 $error_must_die = 0;
266
267                 if ($in{'copy'} && $in{'makehome'}) {
268                         # Copy files to user's home directory
269                         local $uf = $mconfig{'user_files'};
270                         local $shell = $user{'shell'}; $shell =~ s/^(.*)\///g;
271                         if ($group = &all_getgrgid($user{'gid'})) {
272                                 $uf =~ s/\$group/$group/g;
273                                 }
274                         $uf =~ s/\$gid/$user{'gid'}/g;
275                         $uf =~ s/\$shell/$shell/g;
276                         &useradmin::copy_skel_files($uf, $user{'home'},
277                                          $user{'uid'}, $user{'gid'});
278                         }
279
280                 print "<b>",&text('batch_created',$user{'user'}),"</b>\n";
281                 print "<b><i>",&text('batch_eother', $other_err),"</i></b>\n"
282                         if ($other_err);
283                 $created++;
284                 }
285         elsif ($line[0] eq 'delete') {
286                 # Deleting an existing user
287                 if (@line != 2) {
288                         print &text('batch_elen', $lnum, 2),"\n";
289                         next;
290                         }
291                 local @ulist = &list_users();
292                 local ($user) = grep { $_->{'user'} eq $line[1] } @ulist;
293                 if (!$user) {
294                         print &text('batch_enouser', $lnum, $line[1]),"\n";
295                         next;
296                         }
297                 if (!$mconfig{'delete_root'} && $user->{'uid'} <= 10) {
298                         print &text('batch_edaccess', $lnum,
299                                     $text{'udel_eroot'}),"\n";
300                         next;
301                         }
302
303                 # Run the before command
304                 &set_user_envs($user, 'DELETE_USER', undef,
305                                [ &secondary_groups($user->{'user'}) ]);
306                 $merr = &making_changes();
307                 &error(&text('usave_emaking', "<tt>$merr</tt>"))
308                         if (defined($merr));
309
310                 # Delete from other modules, ignoring errors
311                 $error_must_die = 1;
312                 eval {
313                         &other_modules("useradmin_delete_user", $user)
314                                 if ($in{'others'});
315                         };
316                 $other_err = $@;
317                 $error_must_die = 0;
318
319                 # Delete the user entry
320                 &delete_user($user);
321
322                 # Delete the user from groups
323                 foreach $g (&list_groups()) {
324                         @mems = split(/,/, $g->{'members'});
325                         $idx = &indexof($user->{'user'}, @mems);
326                         if ($idx >= 0) {
327                                 splice(@mems, $idx, 1);
328                                 %newg = %$g;
329                                 $newg{'members'} = join(',', @mems);
330                                 &modify_group($g, \%newg);
331                                 }
332                         $mygroup = $g if ($g->{'group'} eq $user->{'user'});
333                         }
334
335                 # Delete the user's group
336                 if ($mygroup && !$mygroup->{'members'}) {
337                         local $another;
338                         foreach $ou (&list_users()) {
339                                 $another++
340                                         if ($ou->{'gid'} == $mygroup->{'gid'});
341                                 }
342                         if (!$another) {
343                                 &delete_group($mygroup);
344                                 }
345                         }
346                 &made_changes();
347
348                 # Delete his addressbook entry
349                 if ($config{'addressbook'}) {
350                         &delete_ldap_subtree($ldap,
351                                 "ou=$user->{'user'}, $config{'addressbook'}");
352                         }
353
354                 # Delete his home directory
355                 if ($in{'delhome'} && $user->{'home'} !~ /^\/+$/) {
356                         if ($mconfig{'delete_only'}) {
357                                 &lock_file($user->{'home'});
358                                 &system_logged("find \"$user->{'home'}\" ! -type d -user $user->{'uid'} | xargs rm -f >/dev/null 2>&1");
359                                 &system_logged("find \"$user->{'home'}\" -type d -user $user->{'uid'} | xargs rmdir >/dev/null 2>&1");
360                                 rmdir($user->{'home'});
361                                 &unlock_file($user->{'home'});
362                                 }
363                         else {
364                                 &system_logged("rm -rf \"$user->{'home'}\" >/dev/null 2>&1");
365                                 }
366                         }
367
368                 print "<b>",&text('batch_deleted',$user->{'user'}),"</b>\n";
369                 print "<b><i>",&text('batch_eother', $other_err),"</i></b>\n"
370                         if ($other_err);
371                 $deleted++;
372                 }
373         elsif ($line[0] eq 'modify') {
374                 # Modifying an existing user
375                 local $wlen = $pft == 5 ? 11 :
376                               $pft == 4 ? 13 :
377                               $pft == 2 ? 14 :
378                               $pft == 1 || $pft == 6 ? 12 : 9;
379                 if (@line < $wlen) {
380                         print &text('batch_elen', $lnum, $wlen),"\n";
381                         next;
382                         }
383                 local @attrs = @line[$wlen .. $#line];
384                 local @ulist = &list_users();
385                 local ($user) = grep { $_->{'user'} eq $line[1] } @ulist;
386                 if (!$user) {
387                         print &text('batch_enouser', $lnum, $line[1]),"\n";
388                         next;
389                         }
390                 %olduser = %user = %$user;
391                 $user{'olduser'} = $user->{'user'};
392
393                 # Update supplied fields
394                 $user{'user'} = $line[2] if ($line[2] ne '');
395                 if ($in{'crypt'} && $line[3] ne '') {
396                         # Changing to pre-encrypted password
397                         $user{'pass'} = $line[3];
398                         $user{'passmode'} = 2;
399                         }
400                 elsif ($line[3] eq 'x') {
401                         # No login allowed
402                         $user{'pass'} = $mconfig{'lock_string'};
403                         $user{'passmode'} = 1;
404                         }
405                 elsif ($line[3] ne '') {
406                         # Normal password
407                         $user{'pass'} = &encrypt_password($line[3]);
408                         $user{'passmode'} = 3;
409                         $user{'plainpass'} = $line[3];
410                         }
411                 else {
412                         # No change
413                         $user{'passmode'} = 4;
414                         }
415                 $user{'uid'} = $line[4] if ($line[4] ne '');
416                 $user{'gid'} = $line[5] if ($line[5] ne '');
417                 $user{'real'} = $line[6] if ($line[6] ne '');
418                 $user{'home'} = $line[7] if ($line[7] ne '');
419                 $user{'shell'} = $line[8] if ($line[8] ne '');
420
421                 if ($pft == 2) {
422                         # SYSV-style passwd and shadow information
423                         $user{'min'}=$line[9] if ($line[9] ne '');
424                         $user{'max'}=$line[10] if ($line[10] ne '');
425                         $user{'warn'}=$line[11] if ($line[11] ne '');
426                         $user{'inactive'}=$line[12]
427                                 if ($line[12] ne '');
428                         $user{'expire'}=$line[13] if ($line[13] ne '');
429                         if ($in{'forcechange'} == 1){
430                             $user{'change'} = 0;
431                         } elsif ($line[3] ne ''){
432                             $user{'change'}= int(time() / (60*60*24));
433                             }
434                         }
435
436                 # Work out Samba properties
437                 $wassamba = &indexof($config{'samba_class'},
438                                      @{$user{'ldap_class'}}) >= 0;
439                 $user{'ldap_attrs'} ||= [ ];
440                 if ($wassamba) {
441                         # Need to update Samba attributes
442                         &samba_properties(0, \%user, $user{'passmode'},
443                                           $user{'plainpass'}, $schema,
444                                           $user{'ldap_attrs'});
445                         }
446
447                 # Set extra LDAP attrs
448                 foreach $a (@attrs) {
449                         next if (!$a);
450                         if ($a =~ /^([^=]+)=(.*)/) {
451                                 push(@{$user{'ldap_attrs'}}, $1, $2);
452                                 }
453                         else {
454                                 print &text('batch_eattr', $lnum, $a),"\n";
455                                 next LINE;
456                                 }
457                         }
458
459                 # Run the before command
460                 &set_user_envs(\%user, 'MODIFY_USER', $user{'plainpass'},
461                                [ &secondary_groups($user{'user'}) ]);
462                 $merr = &making_changes();
463                 &error(&text('usave_emaking', "<tt>$merr</tt>"))
464                         if (defined($merr));
465
466                 # Move home directory if needed
467                 if ($olduser{'home'} ne $user{'home'} && $in{'movehome'} &&
468                     $user{'home'} ne '/' && $olduser{'home'} ne '/') {
469                         if (-d $olduser{'home'} && !-e $user{'home'}) {
470                                 local $out = &backquote_logged(
471                                         "mv \"$olduser{'home'}\" ".
472                                         "\"$user{'home'}\" 2>&1");
473                                 if ($?) { &error(&text('batch_emove',
474                                                  $lnum, $out)); }
475                                 }
476                         }
477
478                 # Change UIDs and GIDs
479                 if ($olduser{'gid'} != $user{'gid'} && $in{'chgid'}) {
480                         if ($in{'chgid'} == 1) {
481                                 &useradmin::recursive_change(
482                                         $user{'home'}, $olduser{'uid'},
483                                         $olduser{'gid'}, -1, $user{'gid'});
484                                 }
485                         else {
486                                 &useradmin::recursive_change(
487                                         "/", $olduser{'uid'},
488                                         $olduser{'gid'}, -1, $user{'gid'});
489                                 }
490                         }
491                 if ($olduser{'uid'} != $user{'uid'} && $in{'chuid'}) {
492                         if ($in{'chuid'} == 1) {
493                                 &useradmin::recursive_change(
494                                         $user{'home'}, $olduser{'uid'},
495                                         -1, $user{'uid'}, -1);
496                                 }
497                         else {
498                                 &useradmin::recursive_change(
499                                         "/", $olduser{'uid'},
500                                         -1, $user{'uid'}, -1);
501                                 }
502                         }
503
504                 # Actually modify the user
505                 &modify_user(\%olduser, \%user);
506
507                 # If the user has been renamed, update any secondary groups
508                 if ($olduser{'user'} ne $user{'user'}) {
509                         foreach $group (@glist) {
510                                 local @mems = split(/,/, $group->{'members'});
511                                 local $idx = &indexof($olduser{'user'}, @mems);
512                                 if ($idx >= 0) {
513                                         $mems[$idx] = $user{'user'};
514                                         $group->{'members'} = join(",", @mems);
515                                         &modify_group($group, $group);
516                                         }
517                                 }
518                         }
519
520                 &made_changes();
521
522                 # Modify in other modules, ignoring errors
523                 $error_must_die = 1;
524                 eval {
525                         &other_modules("useradmin_modify_user",
526                                        \%user, \%olduser)
527                                 if ($in{'others'});
528                         };
529                 $error_must_die = 0;
530                 $other_err = $@;
531
532                 print "<b>",&text('batch_modified',$olduser{'user'}),"</b>\n";
533                 print "<b><i>",&text('batch_eother', $other_err),"</i></b>\n"
534                         if ($other_err);
535                 $modified++;
536                 }
537         else {
538                 print &text('batch_eaction', $lnum, $line[0]),"\n";
539                 next;
540                 }
541         }
542 print "</pre>\n";
543 &unlock_user_files();
544 &webmin_log("batch", undef, $in{'source'} == 1 ? $in{'local'} : undef,
545             { 'created' => $created, 'modified' => $modified,
546               'deleted' => $deleted, 'lnum' => $lnum } );
547
548 &ui_print_footer("batch_form.cgi", $text{'batch_return'},
549                  "", $text{'index_return'});
550
551 # check_user(\%user, [\%olduser])
552 # Check access control restrictions for a user
553 sub check_user
554 {
555 # check if uid is within range
556 if ($access{'lowuid'} && $_[0]->{'uid'} < $access{'lowuid'}) {
557         return &text('usave_elowuid', $access{'lowuid'});
558         }
559 if ($access{'hiuid'} && $_[0]->{'uid'} > $access{'hiuid'}) {
560         return &text('usave_ehiuid', $access{'hiuid'});
561         }
562 if ($_[1] && !$access{'uuid'} && $_[1]->{'uid'} != $_[0]->{'uid'}) {
563         return $text{'usave_euuid'};
564         }
565
566 # make sure home dir is under the allowed root
567 if (!$access{'autohome'}) {
568         $al = length($access{'home'});
569         if (length($_[0]->{'home'}) < $al ||
570             substr($_[0]->{'home'}, 0, $al) ne $access{'home'}) {
571                 return &text('usave_ehomepath', $_[0]->{'home'});
572                 }
573         }
574
575 # check for invalid shell
576 if ($access{'shells'} ne '*' &&
577     &indexof($_[0]->{'shell'}, split(/\s+/, $access{'shells'})) < 0) {
578         return &text('usave_eshell', $_[0]->{'shell'});
579         }
580
581 # check for invalid primary group (unless one is dynamically assigned)
582 if ($user{'gid'} ne '') {
583         local $ng = &all_getgrgid($_[0]->{'gid'});
584         local $ni = &can_use_group(\%access, $ng);
585         if ($_[1]) {
586                 if ($_[1]->{'gid'} != $_[0]->{'gid'}) {
587                         local $og = &all_getgrgid($_[1]->{'gid'});
588                         local $oi = &can_use_group(\%access, $og);
589                         if (!$ni) { return &text('usave_eprimary', $ng); }
590                         if (!$oi) { return &text('usave_eprimaryr', $og); }
591                         }
592                 }
593         else {
594                 return &text('usave_eprimary', $ng) if (!$ni);
595                 }
596         }
597 return undef;
598 }
599
600 sub secondary_groups
601 {
602 local @secs;
603 foreach $g (@glist) {
604         @mems = split(/,/, $g->{'members'});
605         if (&indexof($_[0], @mems) >= 0) {
606                 push(@secs, $g->{'gid'});
607                 }
608         }
609 return @secs;
610 }
611