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