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