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