Initial import from FreeBSD RELENG_4:
[dragonfly.git] / usr.sbin / adduser / adduser.perl
1 #!/usr/bin/perl
2 #
3 # Copyright (c) 1995-1996 Wolfram Schneider <wosch@FreeBSD.org>. Berlin.
4 # All rights reserved.
5 #
6 # Redistribution and use in source and binary forms, with or without
7 # modification, are permitted provided that the following conditions
8 # are met:
9 # 1. Redistributions of source code must retain the above copyright
10 #    notice, this list of conditions and the following disclaimer.
11 # 2. Redistributions in binary form must reproduce the above copyright
12 #    notice, this list of conditions and the following disclaimer in the
13 #    documentation and/or other materials provided with the distribution.
14 #
15 # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16 # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 # ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19 # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21 # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24 # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25 # SUCH DAMAGE.
26 #
27 # $FreeBSD: src/usr.sbin/adduser/adduser.perl,v 1.44.2.5 2002/08/31 18:29:04 dwmalone Exp $
28
29
30 # read variables
31 sub variables {
32     $verbose = 1;               # verbose = [0-2]
33     $usernameregexp = "^[a-z0-9_][a-z0-9_-]*\$"; # configurable
34     $defaultusernameregexp = $usernameregexp; # remains constant
35     $defaultpasswd = "yes";     # use password for new users
36     $dotdir = "/usr/share/skel"; # copy dotfiles from this dir
37     $dotdir_bak = $dotdir;
38     $send_message = "/etc/adduser.message"; # send message to new user
39     $send_message_bak = '/etc/adduser.message';
40     $config = "/etc/adduser.conf"; # config file for adduser
41     $config_read = 1;           # read config file
42     $logfile = "/var/log/adduser"; # logfile
43     $home = "/home";            # default HOME
44     $etc_shells = "/etc/shells";
45     $etc_passwd = "/etc/master.passwd";
46     $group = "/etc/group";
47     @pwd_mkdb = qw(pwd_mkdb -p); # program for building passwd database
48
49
50     # List of directories where shells located
51     @path = ('/bin', '/usr/bin', '/usr/local/bin');
52     # common shells, first element has higher priority
53     @shellpref = ('csh', 'sh', 'bash', 'tcsh', 'ksh');
54
55     $defaultshell = 'sh';       # defaultshell if not empty
56     $group_uniq = 'USER';
57     $defaultgroup = $group_uniq;# login groupname, $group_uniq means username
58     $defaultclass = '';
59
60     $uid_start = 1000;          # new users get this uid
61     $uid_end   = 32000;         # max. uid
62
63     # global variables
64     # passwd
65     $username = '';             # $username{username} = uid
66     $uid = '';                  # $uid{uid} = username
67     $pwgid = '';                # $pwgid{pwgid} = username; gid from passwd db
68
69     $password = '';             # password for new users
70
71     # group
72     $groupname ='';             # $groupname{groupname} = gid
73     $groupmembers = '';         # $groupmembers{gid} = members of group/kommalist
74     $gid = '';                  # $gid{gid} = groupname;    gid form group db
75     @group_comments;            # Comments in the group file
76
77     # shell
78     $shell = '';                # $shell{`basename sh`} = sh
79
80     umask 022;                  # don't give login group write access
81
82     $ENV{'PATH'} = "/sbin:/bin:/usr/sbin:/usr/bin";
83     @passwd_backup = '';
84     @group_backup = '';
85     @message_buffer = '';
86     @user_variable_list = '';   # user variables in /etc/adduser.conf
87     $do_not_delete = '## DO NOT DELETE THIS LINE!';
88 }
89
90 # read shell database, see also: shells(5)
91 sub shells_read {
92     local($sh);
93     local($err) = 0;
94
95     print "Check $etc_shells\n" if $verbose;
96     open(S, $etc_shells) || die "$etc_shells:$!\n";
97
98     while(<S>) {
99         if (/^\s*\//) {
100             s/^\s*//; s/\s+.*//; # chop
101             $sh = $_;
102             if (-x  $sh) {
103                 $shell{&basename($sh)} = $sh;
104             } else {
105                 warn "Shell: $sh not executable!\n";
106                 $err++;
107             }
108         }
109     }
110
111     # Allow /nonexistent and /bin/date as a valid shell for system utils
112     push(@list, "/nonexistent");
113     push(@shellpref, "no") if !grep(/^no$/, @shellpref);
114     $shell{"no"} = "/nonexistent";
115
116     push(@list, "/bin/date");
117     push(@shellpref, "date") if !grep(/^date$/, @shellpref);
118     $shell{"date"} = "/bin/date";
119
120     return $err;
121 }
122
123 # add new shells if possible
124 sub shells_add {
125     local($sh,$dir,@list);
126
127     return 1 unless $verbose;
128
129     foreach $sh (@shellpref) {
130         # all known shells
131         if (!$shell{$sh}) {
132             # shell $sh is not defined as login shell
133             foreach $dir (@path) {
134                 if (-x "$dir/$sh") {
135                     # found shell
136                     if (&confirm_yn("Found shell: $dir/$sh. Add to $etc_shells?", "yes")) {
137                         push(@list, "$dir/$sh");
138                         push(@shellpref, "$sh");
139                         $shell{&basename("$dir/$sh")} = "$dir/$sh";
140                         $changes++;
141                     }
142                 }
143             }
144         }
145     }
146     &append_file($etc_shells, @list) if $#list >= 0;
147 }
148
149 # choose your favourite shell and return the shell
150 sub shell_default {
151     local($e,$i,$new_shell);
152     local($sh);
153
154     $sh = &shell_default_valid($defaultshell);
155     return $sh unless $verbose;
156
157     $new_shell = &confirm_list("Enter your default shell:", 0,
158                        $sh, sort(keys %shell));
159     print "Your default shell is: $new_shell -> $shell{$new_shell}\n";
160     $changes++ if $new_shell ne $sh;
161     return $new_shell;
162 }
163
164 sub shell_default_valid {
165     local($sh) = @_;
166     local($s,$e);
167
168     return $sh if $shell{$sh};
169
170     foreach $e (@shellpref) {
171         $s = $e;
172         last if defined($shell{$s});
173     }
174     $s = "sh" unless $s;
175     warn "Shell ``$sh'' is undefined, use ``$s''\n";
176     return $s;
177 }
178
179 # return default home partition (e.g. "/home")
180 # create base directory if nesseccary
181 sub home_partition {
182     local($home) = @_;
183     $home = &stripdir($home);
184     local($h) = $home;
185
186     return $h if !$verbose && $h eq &home_partition_valid($h);
187
188     while(1) {
189         $h = &confirm_list("Enter your default HOME partition:", 1, $home, "");
190         $h = &stripdir($h);
191         last if $h eq &home_partition_valid($h);
192     }
193
194     $changes++ if $h ne $home;
195     return $h;
196 }
197
198 sub home_partition_valid {
199     local($h) = @_;
200
201     $h = &stripdir($h);
202     # all right (I hope)
203     return $h if $h =~ "^/" && -e $h && -w $h && (-d $h || -l $h);
204
205     # Errors or todo
206     if ($h !~ "^/") {
207         warn "Please use absolute path for home: ``$h''.\a\n";
208         return 0;
209     }
210
211     if (-e $h) {
212         warn "$h exists, but is not a directory or symlink!\n"
213             unless -d $h || -l $h;
214         warn "$h is not writable!\n"
215             unless -w $h;
216         return 0;
217     } else {
218         # create home partition
219         return $h if &mkdir_home($h);
220     }
221     return 0;
222 }
223
224 # check for valid passwddb
225 sub passwd_check {
226     system(@pwd_mkdb, '-C', $etc_passwd);
227     die "\nInvalid $etc_passwd - cannot add any users!\n" if $?;
228 }
229
230 # read /etc/passwd
231 sub passwd_read {
232     local($p_username, $pw, $p_uid, $p_gid, $sh, %shlist);
233
234     print "Check $etc_passwd\n" if $verbose;
235     open(P, "$etc_passwd") || die "$etc_passwd: $!\n";
236
237     while(<P>) {
238         chop;
239         push(@passwd_backup, $_);
240         # ignore comments
241         next if /^\s*$/;
242         next if /^\s*#/;
243
244         ($p_username, $pw, $p_uid, $p_gid, $sh) = (split(/:/, $_))[0..3,9];
245
246         print "$p_username already exists with uid: $username{$p_username}!\n"
247             if $username{$p_username} && $verbose;
248         $username{$p_username} = $p_uid;
249         print "User $p_username: uid $p_uid exists twice: $uid{$p_uid}\n"
250             if $uid{$p_uid} && $verbose && $p_uid;    # don't warn for uid 0
251         print "User $p_username: illegal shell: ``$sh''\n"
252             if ($verbose && $sh &&
253                 !$shell{&basename($sh)} &&
254                 $p_username !~ /^(news|xten|bin|nobody|uucp)$/ &&
255                 $sh !~ /\/(pppd|sliplogin|nologin|nonexistent)$/);
256         $uid{$p_uid} = $p_username;
257         $pwgid{$p_gid} = $p_username;
258     }
259     close P;
260 }
261
262 # read /etc/group
263 sub group_read {
264     local($g_groupname,$pw,$g_gid, $memb);
265
266     print "Check $group\n" if $verbose;
267     open(G, "$group") || die "$group: $!\n";
268     while(<G>) {
269         chop;
270         push(@group_backup, $_);
271         # Ignore empty lines
272         next if /^\s*$/;
273         # Save comments to restore later
274         if (/^\s*\#/) {
275             push(@group_comments, $_);
276             next;
277         }
278
279         ($g_groupname, $pw, $g_gid, $memb) = (split(/:/, $_))[0..3];
280
281         $groupmembers{$g_gid} = $memb;
282         warn "Groupname exists twice: $g_groupname:$g_gid ->  $g_groupname:$groupname{$g_groupname}\n"
283             if $groupname{$g_groupname} && $verbose;
284         $groupname{$g_groupname} = $g_gid;
285         warn "Groupid exists twice:   $g_groupname:$g_gid -> $gid{$g_gid}:$g_gid\n"
286             if $gid{$g_gid} && $verbose;
287         $gid{$g_gid} = $g_groupname;
288     }
289     close G;
290 }
291
292 # check gids /etc/passwd <-> /etc/group
293 sub group_check {
294     local($c_gid, $c_username, @list);
295
296     foreach $c_gid (keys %pwgid) {
297         if (!$gid{$c_gid}) {
298             $c_username = $pwgid{$c_gid};
299             warn "User ``$c_username'' has gid $c_gid but a group with this " .
300                 "gid does not exist.\n" if $verbose;
301         }
302     }
303 }
304
305 #
306 # main loop for creating new users
307 #
308
309 # return username
310 sub new_users_name {
311     local($name);
312
313     while(1) {
314         $name = &confirm_list("Enter username", 1, $usernameregexp, "");
315         last if (&new_users_name_valid($name));
316     }
317     return $name;
318 }
319
320 sub new_users_name_valid {
321     local($name) = @_;
322
323     if ($name eq $usernameregexp) { # user/admin just pressed <Return>
324         warn "Please enter a username\a\n";
325         return 0;
326     } elsif (length($name) > 16) {
327         warn "Username is longer than 16 characters.\a\n";
328         return 0;
329     } elsif ($name =~ /[:\n]/) {
330         warn "Username cannot contain colon or newline characters.\a\n";
331         return 0;
332     } elsif ($name !~ /$usernameregexp/) {
333         if ($usernameregexp eq $defaultusernameregexp) {
334             warn "Illegal username.\n" .
335                 "Please use only lowercase Roman, decimal, underscore, " .
336                 "or hyphen characters.\n" .
337                 "Additionally, a username should not start with a hyphen.\a\n";
338         } else {
339             warn "Username doesn't match the regexp /$usernameregexp/\a\n";
340         }
341         return 0;
342     } elsif (defined($username{$name})) {
343         warn "Username ``$name'' already exists!\a\n"; return 0;
344     }
345     return 1;
346 }
347
348 # return full name
349 sub new_users_fullname {
350     local($name) = @_;
351     local($fullname);
352
353     while(1) {
354         $fullname = &confirm_list("Enter full name", 1, "", "");
355         last if $fullname eq &new_users_fullname_valid($fullname);
356     }
357     $fullname = $name unless $fullname;
358     return $fullname;
359 }
360
361 sub new_users_fullname_valid {
362     local($fullname) = @_;
363
364     return $fullname if $fullname !~ /:/;
365
366     warn "``:'' is not allowed!\a\n";
367     return 0;
368 }
369
370 # return shell (full path) for user
371 sub new_users_shell {
372     local($sh);
373
374     $sh = &confirm_list("Enter shell", 0, $defaultshell, keys %shell);
375     return $shell{$sh};
376 }
377
378 # return home (full path) for user
379 # Note that the home path defaults to $home/$name for batch
380 sub new_users_home {
381     local($name) = @_;
382     local($userhome);
383
384     while(1) {
385         $userhome = &confirm_list("Enter home directory (full path)", 1, "$home/$name", "");
386         last if $userhome =~ /^\//;
387         warn qq{Home directory "$userhome" is not a full path\a\n};
388     }
389     return $userhome;
390 }
391
392 # return free uid and gid
393 sub new_users_id {
394     local($name) = @_;
395     local($u_id, $g_id) = &next_id($name);
396     local($u_id_tmp, $e);
397
398     while(1) {
399         $u_id_tmp = &confirm_list("Uid", 1, $u_id, "");
400         last if $u_id_tmp =~ /^[0-9]+$/ && $u_id_tmp <= $uid_end &&
401                 ! $uid{$u_id_tmp};
402         if ($uid{$u_id_tmp}) {
403             warn "Uid ``$u_id_tmp'' in use!\a\n";
404             $uid_start = $u_id_tmp;
405             ($u_id, $g_id) = &next_id($name);
406             next;
407         } else {
408             warn "Wrong uid.\a\n";
409         }
410     }
411     # use calculated uid
412     # return ($u_id_tmp, $g_id) if $u_id_tmp eq $u_id;
413     # recalculate gid
414     $uid_start = $u_id_tmp;
415     return &next_id($name);
416 }
417
418 # return login class for user
419 sub new_users_class {
420     local($def) = @_;
421     local($class);
422
423     $class = &confirm_list("Enter login class:", 1, $def, ($def, "default"));
424     $class = "" if $class eq "default";
425     return $class;
426 }
427
428 # add user to group
429 sub add_group {
430     local($gid, $name) = @_;
431
432     return 0 if
433         $groupmembers{$gid} =~ /^(.+,)?$name(,.+)?$/;
434
435     $groupmembers_bak{$gid} = $groupmembers{$gid};
436     $groupmembers{$gid} .= "," if $groupmembers{$gid};
437     $groupmembers{$gid} .= "$name";
438
439     return $name;
440 }
441
442
443 # return login group
444 sub new_users_grplogin {
445     local($name, $defaultgroup, $new_users_ok) = @_;
446     local($group_login, $group);
447
448     $group = $name;
449     $group = $defaultgroup if $defaultgroup ne $group_uniq;
450
451     if ($new_users_ok) {
452         # clean up backup
453         foreach $e (keys %groupmembers_bak) { delete $groupmembers_bak{$e}; }
454     } else {
455         # restore old groupmembers, user was not accept
456         foreach $e (keys %groupmembers_bak) {
457             $groupmembers{$e} = $groupmembers_bak{$e};
458         }
459     }
460
461     while(1) {
462         $group_login = &confirm_list("Login group", 1, $group,
463                                      ($name, $group));
464         last if $group_login eq $group;
465         last if $group_login eq $name;
466         last if defined $groupname{$group_login};
467         if ($group_login eq $group_uniq) {
468             $group_login = $name; last;
469         }
470
471         if (defined $gid{$group_login}) {
472             # convert numeric groupname (gid) to groupname
473             $group_login = $gid{$group_login};
474             last;
475         }
476         warn "Group does not exist!\a\n";
477     }
478
479     #if (defined($groupname{$group_login})) {
480     #   &add_group($groupname{$group_login}, $name);
481     #}
482
483     return ($group_login, $group_uniq) if $group_login eq $name;
484     return ($group_login, $group_login);
485 }
486
487 # return other groups (string)
488 sub new_users_groups {
489     local($name, $other_groups) = @_;
490     local($string) =
491         "Login group is ``$group_login''. Invite $name into other groups:";
492     local($e, $flag);
493     local($new_groups,$groups);
494
495     $other_groups = "no" unless $other_groups;
496
497     while(1) {
498         $groups = &confirm_list($string, 1, $other_groups,
499                                 ("no", $other_groups, "guest"));
500         # no other groups
501         return "" if $groups eq "no";
502
503         ($flag, $new_groups) = &new_users_groups_valid($groups);
504         last unless $flag;
505     }
506     $new_groups =~ s/\s*$//;
507     return $new_groups;
508 }
509
510 sub new_users_groups_valid {
511     local($groups) = @_;
512     local($e, $new_groups);
513     local($flag) = 0;
514
515     foreach $e (split(/[,\s]+/, $groups)) {
516         # convert numbers to groupname
517         if ($e =~ /^[0-9]+$/ && $gid{$e}) {
518             $e = $gid{$e};
519         }
520         if (defined($groupname{$e})) {
521             if ($e eq $group_login) {
522                 # do not add user to a group if this group
523                 # is also the login group.
524             } elsif (&add_group($groupname{$e}, $name)) {
525                 $new_groups .= "$e ";
526             } else {
527                 warn "$name is already member of group ``$e''\n";
528             }
529         } else {
530             warn "Group ``$e'' does not exist\a\n"; $flag++;
531         }
532     }
533     return ($flag, $new_groups);
534 }
535
536 # your last change
537 sub new_users_ok {
538
539     print <<EOF;
540
541 Name:     $name
542 Password: ****
543 Fullname: $fullname
544 Uid:      $u_id
545 Gid:      $g_id ($group_login)
546 Class:    $class
547 Groups:   $group_login $new_groups
548 HOME:     $userhome
549 Shell:    $sh
550 EOF
551
552     return &confirm_yn("OK?", "yes");
553 }
554
555 # make password database
556 sub new_users_pwdmkdb {
557     local($last) = shift;
558     local($name) = shift;
559
560     system(@pwd_mkdb, '-u', $name, $etc_passwd);
561     if ($?) {
562         warn "$last\n";
563         warn "``@pwd_mkdb'' failed\n";
564         exit($? >> 8);
565     }
566 }
567
568 # update group database
569 sub new_users_group_update {
570     local($e, @a);
571
572     # Add *new* group
573     if (!defined($groupname{$group_login}) &&
574         !defined($gid{$groupname{$group_login}})) {
575         push(@group_backup, "$group_login:*:$g_id:");
576         $groupname{$group_login} = $g_id;
577         $gid{$g_id} = $group_login;
578         # $groupmembers{$g_id} = $group_login;
579     }
580
581     if ($new_groups || defined($groupname{$group_login}) ||
582         defined($gid{$groupname{$group_login}}) &&
583                 $gid{$groupname{$group_login}} ne "+") {
584         # new user is member of some groups
585         # new login group is already in name space
586         rename($group, "$group.bak");
587         #warn "$group_login $groupname{$group_login} $groupmembers{$groupname{$group_login}}\n";
588
589         # Restore comments from the top of the group file
590         @a = @group_comments;
591         foreach $e (sort {$a <=> $b} (keys %gid)) {
592             push(@a, "$gid{$e}:*:$e:$groupmembers{$e}");
593         }
594         &append_file($group, @a);
595     } else {
596         &append_file($group, "$group_login:*:$g_id:");
597     }
598
599 }
600
601 sub new_users_passwd_update {
602     # update passwd/group variables
603     push(@passwd_backup, $new_entry);
604     $username{$name} = $u_id;
605     $uid{$u_id} = $name;
606     $pwgid{$g_id} = $name;
607 }
608
609 # send message to new user
610 sub new_users_sendmessage {
611     return 1 if $send_message eq "no";
612
613     local($cc) =
614         &confirm_list("Send message to ``$name'' and:",
615                       1, "no", ("root", "second_mail_address", "no"));
616     local($e);
617     $cc = "" if $cc eq "no";
618
619     foreach $e (@message_buffer) {
620         print eval "\"$e\"";
621     }
622     print "\n";
623
624     local(@message_buffer_append) = ();
625     if (!&confirm_yn("Add anything to default message", "no")) {
626         print "Use ``.'' or ^D alone on a line to finish your message.\n";
627         push(@message_buffer_append, "\n");
628         while($read = <STDIN>) {
629             last if $read eq "\.\n";
630             push(@message_buffer_append, $read);
631         }
632     }
633
634     &sendmessage("$name $cc", (@message_buffer, @message_buffer_append))
635         if (&confirm_yn("Send message", "yes"));
636 }
637
638 sub sendmessage {
639     local($to, @message) = @_;
640     local($e);
641
642     if (!open(M, "| mail -s Welcome $to")) {
643         warn "Cannot send mail to: $to!\n";
644         return 0;
645     } else {
646         foreach $e (@message) {
647             print M eval "\"$e\"";
648         }
649         close M;
650     }
651 }
652
653
654 sub new_users_password {
655
656     # empty password
657     return "" if $defaultpasswd ne "yes";
658
659     local($password);
660
661     while(1) {
662         system("stty -echo");
663         $password = &confirm_list("Enter password", 1, "", "");
664         system("stty echo");
665         print "\n";
666         if ($password ne "") {
667             system("stty -echo");
668             $newpass = &confirm_list("Enter password again", 1, "", "");
669             system("stty echo");
670             print "\n";
671             last if $password eq $newpass;
672             print "They didn't match, please try again\n";
673         }
674         elsif (&confirm_yn("Use an empty password?", "yes")) {
675             last;
676         }
677     }
678
679     return $password;
680 }
681
682
683 sub new_users {
684
685     print "\n" if $verbose;
686     print "Ok, let's go.\n" .
687           "Don't worry about mistakes. I will give you the chance later to " .
688           "correct any input.\n" if $verbose;
689
690     # name: Username
691     # fullname: Full name
692     # sh: shell
693     # userhome: home path for user
694     # u_id: user id
695     # g_id: group id
696     # class: login class
697     # group_login: groupname of g_id
698     # new_groups: some other groups
699     local($name, $group_login, $fullname, $sh, $u_id, $g_id, $class, $new_groups);
700     local($userhome);
701     local($groupmembers_bak, $cryptpwd);
702     local($new_users_ok) = 1;
703
704
705     $new_groups = "no";
706     $new_groups = "no" unless $groupname{$new_groups};
707
708     while(1) {
709         $name = &new_users_name;
710         $fullname = &new_users_fullname($name);
711         $sh = &new_users_shell;
712         $userhome = &new_users_home($name);
713         ($u_id, $g_id) = &new_users_id($name);
714         $class = &new_users_class($defaultclass);
715         ($group_login, $defaultgroup) =
716             &new_users_grplogin($name, $defaultgroup, $new_users_ok);
717         # do not use uniq username and login group
718         $g_id = $groupname{$group_login} if (defined($groupname{$group_login}));
719
720         $new_groups = &new_users_groups($name, $new_groups);
721         $password = &new_users_password;
722
723
724         if (&new_users_ok) {
725             $new_users_ok = 1;
726
727             $cryptpwd = "";
728             $cryptpwd = crypt($password, &salt) if $password ne "";
729             # obscure perl bug
730             $new_entry = "$name\:" . "$cryptpwd" .
731                 "\:$u_id\:$g_id\:$class\:0:0:$fullname:$userhome:$sh";
732             &append_file($etc_passwd, "$new_entry");
733             &new_users_pwdmkdb("$new_entry", $name);
734             &new_users_group_update;
735             &new_users_passwd_update;  print "Added user ``$name''\n";
736             &new_users_sendmessage;
737             &adduser_log("$name:*:$u_id:$g_id($group_login):$fullname");
738             &home_create($userhome, $name, $group_login);
739         } else {
740             $new_users_ok = 0;
741         }
742         if (!&confirm_yn("Add another user?", "yes")) {
743             print "Goodbye!\n" if $verbose;
744             last;
745         }
746         print "\n" if !$verbose;
747     }
748 }
749
750 # ask for password usage
751 sub password_default {
752     local($p) = $defaultpasswd;
753     if ($verbose) {
754         $p = &confirm_yn("Use passwords", $defaultpasswd);
755         $changes++ unless $p;
756     }
757     return "yes" if (($defaultpasswd eq "yes" && $p) ||
758                      ($defaultpasswd eq "no" && !$p));
759     return "no";    # otherwise
760 }
761
762 # misc
763 sub check_root {
764     die "You are not root!\n" if $< && !$test;
765 }
766
767 sub usage {
768     warn <<USAGE;
769 usage: adduser
770     [-check_only]
771     [-class login_class]
772     [-config_create]
773     [-dotdir dotdir]
774     [-group login_group]
775     [-h|-help]
776     [-home home]
777     [-message message_file]
778     [-noconfig]
779     [-shell shell]
780     [-s|-silent|-q|-quiet]
781     [-uid uid_start]
782     [-v|-verbose]
783
784 home=$home shell=$defaultshell dotdir=$dotdir login_group=$defaultgroup
785 login_class=$defaultclass message_file=$send_message uid_start=$uid_start
786 USAGE
787     exit 1;
788 }
789
790 # uniq(1)
791 sub uniq {
792     local(@list) = @_;
793     local($e, $last, @array);
794
795     foreach $e (sort @list) {
796         push(@array, $e) unless $e eq $last;
797         $last = $e;
798     }
799     return @array;
800 }
801
802 # see /usr/src/usr.bin/passwd/local_passwd.c or librcypt, crypt(3)
803 sub salt {
804     local($salt);               # initialization
805     local($i, $rand);
806     local(@itoa64) = ( '0' .. '9', 'a' .. 'z', 'A' .. 'Z' ); # 0 .. 63
807
808     warn "calculate salt\n" if $verbose > 1;
809     # to64
810     for ($i = 0; $i < 27; $i++) {
811         srand(time + $rand + $$); 
812         $rand = rand(25*29*17 + $rand);
813         $salt .=  $itoa64[$rand & $#itoa64];
814     }
815     warn "Salt is: $salt\n" if $verbose > 1;
816
817     return $salt;
818 }
819
820
821 # print banner
822 sub copyright {
823     return;
824 }
825
826 # hints
827 sub hints {
828     if ($verbose) {
829         print "Use option ``-silent'' if you don't want to see " .
830               "all warnings and questions.\n\n";
831     } else {
832         print "Use option ``-verbose'' if you want to see more warnings and " .
833               "questions \nor try to repair bugs.\n\n";
834     }
835 }
836
837 #
838 sub parse_arguments {
839     local(@argv) = @_;
840
841     while ($_ = $argv[0], /^-/) {
842         shift @argv;
843         last if /^--$/;
844         if    (/^--?(v|verbose)$/)      { $verbose = 1 }
845         elsif (/^--?(s|silent|q|quiet)$/)  { $verbose = 0 }
846         elsif (/^--?(debug)$/)      { $verbose = 2 }
847         elsif (/^--?(h|help|\?)$/)      { &usage }
848         elsif (/^--?(home)$/)    { $home = $argv[0]; shift @argv }
849         elsif (/^--?(shell)$/)   { $defaultshell = $argv[0]; shift @argv }
850         elsif (/^--?(dotdir)$/)  { $dotdir = $argv[0]; shift @argv }
851         elsif (/^--?(uid)$/)     { $uid_start = $argv[0]; shift @argv }
852         elsif (/^--?(class)$/)   { $defaultclass = $argv[0]; shift @argv }
853         elsif (/^--?(group)$/)   { $defaultgroup = $argv[0]; shift @argv }
854         elsif (/^--?(check_only)$/) { $check_only = 1 }
855         elsif (/^--?(message)$/) { $send_message = $argv[0]; shift @argv;
856                                    $sendmessage = 1; }
857         elsif (/^--?(batch)$/)   {
858             warn "The -batch option is not supported anymore.\n",
859             "Please use the pw(8) command line tool!\n";
860             exit(0);
861         }
862         # see &config_read
863         elsif (/^--?(config_create)$/)  { &create_conf; }
864         elsif (/^--?(noconfig)$/)       { $config_read = 0; }
865         else                        { &usage }
866     }
867     #&usage if $#argv < 0;
868 }
869
870 sub basename {
871     local($name) = @_;
872     $name =~ s|/+$||;
873     $name =~ s|.*/+||;
874     return $name;
875 }
876
877 sub dirname {
878     local($name) = @_;
879     $name = &stripdir($name);
880     $name =~ s|/+[^/]+$||;
881     $name = "/" unless $name;   # dirname of / is /
882     return $name;
883 }
884
885 # return 1 if $file is a readable file or link
886 sub filetest {
887     local($file, $verb) = @_;
888
889     if (-e $file) {
890         if (-f $file || -l $file) {
891             return 1 if -r _;
892             warn "$file unreadable\n" if $verbose;
893         } else {
894             warn "$file is not a plain file or link\n" if $verbose;
895         }
896     }
897     return 0;
898 }
899
900 # create configuration files and exit
901 sub create_conf {
902     $create_conf = 1;
903     if ($send_message ne 'no') {
904         &message_create($send_message);
905     } else {
906         &message_create($send_message_bak);
907     }
908     &config_write(1);
909     exit(0);
910 }
911
912 # log for new user in /var/log/adduser
913 sub adduser_log {
914     local($string) = @_;
915     local($e);
916
917     return 1 if $logfile eq "no";
918
919     local($sec, $min, $hour, $mday, $mon, $year) = localtime;
920     $year += 1900;
921     $mon++;
922
923     foreach $e ('sec', 'min', 'hour', 'mday', 'mon', 'year') {
924         # '7' -> '07'
925         eval "\$$e = 0 . \$$e" if (eval "\$$e" < 10);
926     }
927
928     &append_file($logfile, "$year/$mon/$mday $hour:$min:$sec $string");
929 }
930
931 # create HOME directory, copy dotfiles from $dotdir to $HOME
932 sub home_create {
933     local($homedir, $name, $group) = @_;
934     local($rootdir);
935
936     if (-e "$homedir") {
937         warn "HOME directory ``$homedir'' already exists.\a\n";
938         return 0;
939     }
940
941     # if the home directory prefix doesn't exist, create it
942     # First, split the directory into a list; then remove the user's dir
943     @dir = split('/', $homedir); pop(@dir);
944     # Put back together & strip to get directory prefix
945     $rootdir = &stripdir(join('/', @dir));
946
947     if (!&mkdirhier("$rootdir")) {
948             # warn already displayed
949             return 0;
950     }
951
952     if ($dotdir eq 'no') {
953         if (!mkdir("$homedir", 0755)) {
954             warn "$dir: $!\n"; return 0;
955         }
956         system 'chown', "$name:$group", $homedir;
957         return !$?;
958     }
959
960     # copy files from  $dotdir to $homedir
961     # rename 'dot.foo' files to '.foo'
962     print "Copy files from $dotdir to $homedir\n" if $verbose;
963     system('cp', '-R', $dotdir, $homedir);
964     system('chmod', '-R', 'u+wrX,go-w', $homedir);
965     system('chown', '-Rh', "$name:$group", $homedir);
966
967     # security
968     opendir(D, $homedir);
969     foreach $file (readdir(D)) {
970         if ($file =~ /^dot\./ && -f "$homedir/$file") {
971             $file =~ s/^dot\././;
972             rename("$homedir/dot$file", "$homedir/$file");
973         }
974         chmod(0600, "$homedir/$file")
975             if ($file =~ /^\.(rhosts|Xauthority|kermrc|netrc)$/);
976         chmod(0700, "$homedir/$file")
977             if ($file =~ /^(Mail|prv|\.(iscreen|term))$/);
978     }
979     closedir D;
980     return 1;
981 }
982
983 # makes a directory hierarchy
984 sub mkdir_home {
985     local($dir) = @_;
986     $dir = &stripdir($dir);
987     local($user_partition) = "/usr";
988     local($dirname) = &dirname($dir);
989
990     -e $dirname || &mkdirhier($dirname);
991
992     if (((stat($dirname))[0]) == ((stat("/"))[0])){
993         # home partition is on root partition
994         # create home partition on $user_partition and make
995         # a symlink from $dir to $user_partition/`basename $dir`
996         # For instance: /home -> /usr/home
997
998         local($basename) = &basename($dir);
999         local($d) = "$user_partition/$basename";
1000
1001
1002         if (-d $d) {
1003             warn "Oops, $d already exists.\n" if $verbose;
1004         } else {
1005             print "Create $d\n" if $verbose;
1006             if (!mkdir("$d", 0755)) {
1007                 warn "$d: $!\a\n"; return 0;
1008             }
1009         }
1010
1011         unlink($dir);           # symlink to nonexist file
1012         print "Create symlink: $dir -> $d\n" if $verbose;
1013         if (!symlink("$d", $dir)) {
1014             warn "Symlink $d: $!\a\n"; return 0;
1015         }
1016     } else {
1017         print "Create $dir\n" if $verbose;
1018         if (!mkdir("$dir", 0755)) {
1019             warn "Directory ``$dir'': $!\a\n"; return 0;
1020         }
1021     }
1022     return 1;
1023 }
1024
1025 sub mkdirhier {
1026     local($dir) = @_;
1027     local($d,$p);
1028
1029     $dir = &stripdir($dir);
1030
1031     foreach $d (split('/', $dir)) {
1032         $dir = "$p/$d";
1033         $dir =~ s|^//|/|;
1034         if (! -e "$dir") {
1035             print "Create $dir\n" if $verbose;
1036             if (!mkdir("$dir", 0755)) {
1037                 warn "$dir: $!\n"; return 0;
1038             }
1039         }
1040         $p .= "/$d";
1041     }
1042     return 1;
1043 }
1044
1045 # stript unused '/'
1046 # F.i.: //usr///home// -> /usr/home
1047 sub stripdir {
1048     local($dir) = @_;
1049
1050     $dir =~ s|/+|/|g;           # delete double '/'
1051     $dir =~ s|/$||;             # delete '/' at end
1052     return $dir if $dir ne "";
1053     return '/';
1054 }
1055
1056 # Read one of the elements from @list. $confirm is default.
1057 # If !$allow accept only elements from @list.
1058 sub confirm_list {
1059     local($message, $allow, $confirm, @list) = @_;
1060     local($read, $c, $print);
1061
1062     $print = "$message" if $message;
1063     $print .= " " unless $message =~ /\n$/ || $#list == 0;
1064
1065     $print .= join($", &uniq(@list)); #"
1066     $print .= " " unless $message =~ /\n$/ && $#list == 0;
1067     print "$print";
1068     print "\n" if (length($print) + length($confirm)) > 60;
1069     print "[$confirm]: ";
1070
1071     chop($read = <STDIN>);
1072     $read =~ s/^\s*//;
1073     $read =~ s/\s*$//;
1074     return $confirm if $read eq "";
1075     return "$read" if $allow;
1076
1077     foreach $c (@list) {
1078         return $read if $c eq $read;
1079     }
1080     warn "$read: is not allowed!\a\n";
1081     return &confirm_list($message, $allow, $confirm, @list);
1082 }
1083
1084 # YES or NO question
1085 # return 1 if &confirm("message", "yes") and answer is yes
1086 #       or if &confirm("message", "no") an answer is no
1087 # otherwise 0
1088 sub confirm_yn {
1089     local($message, $confirm) = @_;
1090     local($yes) = '^(yes|YES|y|Y)$';
1091     local($no) = '^(no|NO|n|N)$';
1092     local($read, $c);
1093
1094     if ($confirm && ($confirm =~ "$yes" || $confirm == 1)) {
1095         $confirm = "y";
1096     } else {
1097         $confirm = "n";
1098     }
1099     print "$message (y/n) [$confirm]: ";
1100     chop($read = <STDIN>);
1101     $read =~ s/^\s*//;
1102     $read =~ s/\s*$//;
1103     return 1 unless $read;
1104
1105     if (($confirm eq "y" && $read =~ "$yes") ||
1106         ($confirm eq "n" && $read =~ "$no")) {
1107         return 1;
1108     }
1109
1110     if ($read !~ "$yes" && $read !~ "$no") {
1111         warn "Wrong value. Enter again!\a\n";
1112         return &confirm_yn($message, $confirm);
1113     }
1114     return 0;
1115 }
1116
1117 # allow configuring usernameregexp
1118 sub usernameregexp_default {
1119     local($r) = $usernameregexp;
1120
1121     while ($verbose) {
1122         $r = &confirm_list("Usernames must match regular expression:", 1,
1123             $r, "");
1124         eval "'foo' =~ /$r/";
1125         last unless $@;
1126         warn "Invalid regular expression\a\n";
1127     }
1128     $changes++ if $r ne $usernameregexp;
1129     return $r;
1130 }
1131
1132 # test if $dotdir exist
1133 # return "no" if $dotdir not exist or dotfiles should not copied
1134 sub dotdir_default {
1135     local($dir) = $dotdir;
1136
1137     return &dotdir_default_valid($dir) unless $verbose;
1138     while($verbose) {
1139         $dir = &confirm_list("Copy dotfiles from:", 1,
1140             $dir, ("no", $dotdir_bak, $dir));
1141         last if $dir eq &dotdir_default_valid($dir);
1142     }
1143     warn "Do not copy dotfiles.\n" if $verbose && $dir eq "no";
1144
1145     $changes++ if $dir ne $dotdir;
1146     return $dir;
1147 }
1148
1149 sub dotdir_default_valid {
1150     local($dir) = @_;
1151
1152     return $dir if (-e $dir && -r _ && (-d _ || -l $dir) && $dir =~ "^/");
1153     return $dir if $dir eq "no";
1154     warn "Dotdir ``$dir'' is not a directory\a\n";
1155     return "no";
1156 }
1157
1158 # ask for messages to new users
1159 sub message_default {
1160     local($file) = $send_message;
1161     local(@d) = ($file, $send_message_bak, "no");
1162
1163     while($verbose) {
1164         $file = &confirm_list("Send message from file:", 1, $file, @d);
1165         last if $file eq "no";
1166         last if &filetest($file, 1);
1167
1168         # maybe create message file
1169         &message_create($file) if &confirm_yn("Create ``$file''?", "yes");
1170         last if &filetest($file, 0);
1171         last if !&confirm_yn("File ``$file'' does not exist, try again?",
1172                              "yes");
1173     }
1174
1175     if ($file eq "no" || !&filetest($file, 0)) {
1176         warn "Do not send message\n" if $verbose;
1177         $file = "no";
1178     } else {
1179         &message_read($file);
1180     }
1181
1182     $changes++ if $file ne $send_message && $verbose;
1183     return $file;
1184 }
1185
1186 # create message file
1187 sub message_create {
1188     local($file) = @_;
1189
1190     rename($file, "$file.bak");
1191     if (!open(M, "> $file")) {
1192         warn "Messagefile ``$file'': $!\n"; return 0;
1193     }
1194     print M <<EOF;
1195 #
1196 # Message file for adduser(8)
1197 #   comment: ``#''
1198 #   default variables: \$name, \$fullname, \$password
1199 #   other variables:  see /etc/adduser.conf after
1200 #                    line  ``$do_not_delete''
1201 #
1202
1203 \$fullname,
1204
1205 your account ``\$name'' was created.
1206 Have fun!
1207
1208 See also chpass(1), finger(1), passwd(1)
1209 EOF
1210     close M;
1211     return 1;
1212 }
1213
1214 # read message file into buffer
1215 sub message_read {
1216     local($file) = @_;
1217     @message_buffer = '';
1218
1219     if (!open(R, "$file")) {
1220         warn "File ``$file'':$!\n"; return 0;
1221     }
1222     while(<R>) {
1223         push(@message_buffer, $_) unless /^\s*#/;
1224     }
1225     close R;
1226 }
1227
1228 # write @list to $file with file-locking
1229 sub append_file {
1230     local($file,@list) = @_;
1231     local($e);
1232     local($LOCK_EX) = 2;
1233     local($LOCK_NB) = 4;
1234     local($LOCK_UN) = 8;
1235
1236     open(F, ">> $file") || die "$file: $!\n";
1237     print "Lock $file.\n" if $verbose > 1;
1238     while(!flock(F, $LOCK_EX | $LOCK_NB)) {
1239         warn "Cannot lock file: $file\a\n";
1240         die "Sorry, give up\n"
1241             unless &confirm_yn("Try again?", "yes");
1242     }
1243     print F join("\n", @list) . "\n";
1244     close F;
1245     print "Unlock $file.\n" if $verbose > 1;
1246     flock(F, $LOCK_UN);
1247 }
1248
1249 # return free uid+gid
1250 # uid == gid if possible
1251 sub next_id {
1252     local($group) = @_;
1253
1254     $uid_start = 1000 if ($uid_start <= 0 || $uid_start >= $uid_end);
1255     # looking for next free uid
1256     while($uid{$uid_start}) {
1257         $uid_start++;
1258         $uid_start = 1000 if $uid_start >= $uid_end;
1259         print "$uid_start\n" if $verbose > 1;
1260     }
1261
1262     local($gid_start) = $uid_start;
1263     # group for user (username==groupname) already exist
1264     if ($groupname{$group}) {
1265         $gid_start = $groupname{$group};
1266     }
1267     # gid is in use, looking for another gid.
1268     # Note: uid an gid are not equal
1269     elsif ($gid{$uid_start}) {
1270         while($gid{$gid_start} || $uid{$gid_start}) {
1271             $gid_start--;
1272             $gid_start = $uid_end if $gid_start < 100;
1273         }
1274     }
1275     return ($uid_start, $gid_start);
1276 }
1277
1278 # read config file
1279 sub config_read {
1280     local($opt) = @_;
1281     local($user_flag) = 0;
1282
1283     # don't read config file
1284     return 1 if $opt =~ /-(noconfig|config_create)/ || !$config_read;
1285
1286     if(!open(C, "$config")) {
1287         warn "$config: $!\n"; return 0;
1288     }
1289
1290     while(<C>) {
1291         # user defined variables
1292         /^$do_not_delete/ && $user_flag++;
1293         # found @array or $variable
1294         if (s/^(\w+\s*=\s*\()/\@$1/ || s/^(\w+\s*=)/\$$1/) {
1295             eval $_;
1296             #warn "$_";
1297         }
1298         # lines with '^##' are not saved
1299         push(@user_variable_list, $_)
1300             if $user_flag && !/^##/ && (s/^[\$\@]// || /^[#\s]/);
1301     }
1302     #warn "X @user_variable_list X\n";
1303     close C;
1304 }
1305
1306
1307 # write config file
1308 sub config_write {
1309     local($silent) = @_;
1310
1311     # nothing to do
1312     return 1 unless ($changes || ! -e $config || !$config_read || $silent);
1313
1314     if (!$silent) {
1315         if (-e $config) {
1316             return 1 if &confirm_yn("\nWrite your changes to $config?", "no");
1317         } else {
1318             return 1 unless
1319                 &confirm_yn("\nWrite your configuration to $config?", "yes");
1320         }
1321     }
1322
1323     rename($config, "$config.bak");
1324     open(C, "> $config") || die "$config: $!\n";
1325
1326     # prepare some variables
1327     $send_message = "no" unless $send_message;
1328     $defaultpasswd = "no" unless $defaultpasswd;
1329     local($shpref) = "'" . join("', '", @shellpref) . "'";
1330     local($shpath) = "'" . join("', '", @path) . "'";
1331     local($user_var) = join('', @user_variable_list);
1332
1333     print C <<EOF;
1334 #
1335 # $config - automatic generated by adduser(8)
1336 #
1337 # Note: adduser read *and* write this file.
1338 #       You may change values, but don't add new things before the
1339 #       line ``$do_not_delete''
1340 #
1341
1342 # verbose = [0-2]
1343 verbose = $verbose
1344
1345 # regular expression usernames are checked against (see perlre(1))
1346 # usernameregexp = 'regexp'
1347 usernameregexp = '$usernameregexp'
1348
1349 # use password for new users
1350 # defaultpasswd =  yes | no
1351 defaultpasswd = $defaultpasswd
1352
1353 # copy dotfiles from this dir ("/usr/share/skel" or "no")
1354 dotdir = "$dotdir"
1355
1356 # send this file to new user ("/etc/adduser.message" or "no")
1357 send_message = "$send_message"
1358
1359 # config file for adduser ("/etc/adduser.conf")
1360 config = "$config"
1361
1362 # logfile ("/var/log/adduser" or "no")
1363 logfile = "$logfile"
1364
1365 # default HOME directory ("/home")
1366 home = "$home"
1367
1368 # List of directories where shells located
1369 # path = ('/bin', '/usr/bin', '/usr/local/bin')
1370 path = ($shpath)
1371
1372 # common shell list, first element has higher priority
1373 # shellpref = ('bash', 'tcsh', 'ksh', 'csh', 'sh')
1374 shellpref = ($shpref)
1375
1376 # defaultshell if not empty ("bash")
1377 defaultshell = "$defaultshell"
1378
1379 # defaultgroup ('USER' for same as username or any other valid group)
1380 defaultgroup = $defaultgroup
1381
1382 # defaultclass if not empty
1383 defaultclass = "$defaultclass"
1384
1385 # new users get this uid (1000)
1386 uid_start = "$uid_start"
1387
1388 $do_not_delete
1389 ## your own variables, see /etc/adduser.message
1390 $user_var
1391
1392 ## end
1393 EOF
1394     close C;
1395 }
1396
1397 ################
1398 # main
1399 #
1400 $test = 0;            # test mode, only for development
1401 $check_only = 0;
1402
1403 &check_root;        # you must be root to run this script!
1404 &variables;          # initialize variables
1405 &config_read(@ARGV);    # read variables form config-file
1406 &parse_arguments(@ARGV);    # parse arguments
1407
1408 if (!$check_only) {
1409     &copyright; &hints;
1410 }
1411
1412 # check
1413 $changes = 0;
1414 &passwd_check;                  # check for valid passwdb
1415 &shells_read;                   # read /etc/shells
1416 &passwd_read;                   # read /etc/master.passwd
1417 &group_read;                    # read /etc/group
1418 &group_check;                   # check for incon*
1419 exit 0 if $check_only;          # only check consistence and exit
1420
1421
1422 # interactive
1423 # some questions
1424 $usernameregexp = &usernameregexp_default; # regexp to check usernames against
1425 &shells_add;                    # maybe add some new shells
1426 $defaultshell = &shell_default; # enter default shell
1427 $home = &home_partition($home); # find HOME partition
1428 $dotdir = &dotdir_default;      # check $dotdir
1429 $send_message = &message_default;   # send message to new user
1430 $defaultpasswd = &password_default; # maybe use password
1431 &config_write(!$verbose);       # write variables in file
1432
1433 # main loop for creating new users
1434 &new_users;          # add new users
1435
1436 #end