xref: /openbsd/usr.sbin/adduser/adduser.perl (revision 3cab2bb3)
1#!/usr/bin/perl
2#
3#	$OpenBSD: adduser.perl,v 1.63 2014/10/01 09:56:36 mpi 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' );
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$/);
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 "auto") {
962        $salt = "";
963    } elsif ($encryptionmethod =~ /^blowfish/ ) {
964        ($encryptionmethod, $salt) = split(/\,/, $encryptionmethod);
965	$salt = 7 unless $salt;		# default rounds if unspecified
966    } else {
967        warn "$encryptionmethod encryption method invalid\n" if ($verbose > 0);
968	warn "Falling back to blowfish,7...\n" if ($verbose > 0);
969	$encryptionmethod = "blowfish";
970	$salt = 7;
971    }
972
973    warn "Salt is: $salt\n" if $verbose > 1;
974
975    return $salt;
976}
977
978# Encrypt a password using the selected method
979sub encrypt {
980    local($pass, $salt) = ($_[0], $_[1]);
981    local(@args, $crypt);
982
983    if ($encryptionmethod eq "blowfish") {
984        @args = ("-b", $salt);
985    } elsif ($encryptionmethod eq "auto") {
986        @args = ("-c", $log_cl);
987    }
988
989    open2(\*ENCRD, \*ENCWR, "/usr/bin/encrypt", @args);
990    print ENCWR "$pass\n";
991    close ENCWR;
992    $crypt = <ENCRD>;
993    close ENCRD;
994    chomp $crypt;
995    die "encrypt failed" if (wait == -1 || $? != 0);
996    return($crypt);
997}
998
999# hints
1000sub hints {
1001    if ($verbose) {
1002	print "Use option ``-silent'' if you don't want to see " .
1003	      "all warnings and questions.\n\n";
1004    }
1005}
1006
1007#
1008sub parse_arguments {
1009    local(@argv) = @_;
1010
1011    while ($_ = $argv[0], /^-/) {
1012	shift @argv;
1013	last if /^--$/;
1014	if    (/^--?(v|verbose)$/)	{ $verbose = 1 }
1015	elsif (/^--?(s|silent|q|quiet)$/)  { $verbose = 0 }
1016	elsif (/^--?(debug)$/)	    { $verbose = 2 }
1017	elsif (/^--?(h|help|\?)$/)	{ &usage }
1018	elsif (/^--?(home)$/)	 { $home = $argv[0]; shift @argv }
1019	elsif (/^--?(shell)$/)	 { $defaultshell = $argv[0]; shift @argv }
1020	elsif (/^--?(class)$/)	 { $defaultclass = $argv[0]; shift @argv }
1021	elsif (/^--?(dotdir)$/)	 { $dotdir = $argv[0]; shift @argv }
1022	elsif (/^--?(uid_start)$/)	 { $uid_start = $argv[0]; shift @argv }
1023	elsif (/^--?(uid_end)$/)	 { $uid_end = $argv[0]; shift @argv }
1024	elsif (/^--?(group)$/)	 { $defaultgroup = $argv[0]; shift @argv }
1025	elsif (/^--?(check_only)$/) { $check_only = 1 }
1026	elsif (/^--?(message)$/) {
1027	    $send_message = $argv[0]; shift @argv;
1028	    $message_file = &choosetxt_yn_default($send_message, $message_file);
1029	}
1030	elsif (/^--?(unencrypted)$/)	{ $unencrypted = 1 }
1031	elsif (/^--?(batch)$/)	 {
1032	    @batch = splice(@argv, 0, 4); $verbose = 0;
1033	    die "batch: too few arguments\n" if $#batch < 0;
1034	}
1035	# see &config_read
1036	elsif (/^--?(config_create)$/)	{ &hints; &create_conf; exit(0); }
1037	elsif (/^--?(noconfig)$/)	{ $config_read = 0; }
1038	elsif (/^--?(e|encryption)$/) {
1039	    $encryptionmethod = $argv[0];
1040	    shift @argv;
1041	}
1042	else			    { &usage }
1043    }
1044    #&usage if $#argv < 0;
1045}
1046
1047sub basename {
1048    local($name) = @_;
1049    $name =~ s|/+$||;
1050    $name =~ s|.*/+||;
1051    return $name;
1052}
1053
1054sub dirname {
1055    local($name) = @_;
1056    $name = &stripdir($name);
1057    $name =~ s|/+[^/]+$||;
1058    $name = "/" unless $name;	# dirname of / is /
1059    return $name;
1060}
1061
1062# return 1 if $file is a readable file or link
1063sub filetest {
1064    local($file, $verbose) = @_;
1065
1066    if (-e $file) {
1067	if (-f $file || -l $file) {
1068	    return 1 if -r _;
1069	    warn "$file unreadable\n" if $verbose;
1070	} else {
1071	    warn "$file is not a plain file or link\n" if $verbose;
1072	}
1073    }
1074    return 0;
1075}
1076
1077# create or recreate configuration file prompting for values
1078sub create_conf {
1079    $create_conf = 1;
1080
1081    &shells_read;			# Pull in /etc/shells info
1082    &shells_add;			# maybe add some new shells
1083    $defaultshell = &shell_default;	# enter default shell
1084    &login_conf_read;			# read /etc/login.conf
1085    $defaultclass = &class_default;	# default login.conf class
1086    $home = &home_partition($home);	# find HOME partition
1087    $dotdir = &dotdir_default;		# check $dotdir
1088    $send_message = &message_default;   # send message to new user
1089    $defaultpasswd = &password_default; # maybe use password
1090    $defaultencryption = &encryption_default;	# Encryption method
1091
1092    &config_write(1);
1093}
1094
1095# log for new user in /var/log/adduser
1096sub adduser_log {
1097    local($string) = @_;
1098    local($e);
1099
1100    return 1 if $logfile eq "no";
1101
1102    local($sec, $min, $hour, $mday, $mon, $year) = localtime;
1103    $year += 1900;
1104    $mon++;
1105
1106    foreach $e ('sec', 'min', 'hour', 'mday', 'mon') {
1107	# '7' -> '07'
1108	eval "\$$e = 0 . \$$e" if (eval "\$$e" < 10);
1109    }
1110
1111    &append_file($logfile, "$year/$mon/$mday $hour:$min:$sec $string");
1112}
1113
1114# create HOME directory, copy dotfiles from $dotdir to $HOME
1115sub home_create {
1116    local($name, $group) = @_;
1117    local($homedir) = "$home/$name";
1118
1119    if (-e "$homedir") {
1120	warn "HOME Directory ``$homedir'' already exists\a\n";
1121	return 0;
1122    }
1123
1124    if ($dotdir eq 'no') {
1125	if (!mkdir("$homedir", 0755)) {
1126	    warn "mkdir $homedir: $!\n"; return 0;
1127	}
1128	system 'chown', "$name:$group", $homedir;
1129	return !$?;
1130    }
1131
1132    # copy files from  $dotdir to $homedir
1133    # rename 'dot.foo' files to '.foo'
1134    print "Copy files from $dotdir to $homedir\n" if $verbose;
1135    system("cp", "-R", $dotdir, $homedir);
1136    system("chmod", "-R", "u+wrX,go-w", $homedir);
1137    system("chown", "-R", "$name:$group", $homedir);
1138
1139    # security
1140    opendir(D, $homedir);
1141    foreach $file (readdir(D)) {
1142	if ($file =~ /^dot\./ && -f "$homedir/$file") {
1143	    $file =~ s/^dot\././;
1144	    rename("$homedir/dot$file", "$homedir/$file");
1145	}
1146	chmod(0600, "$homedir/$file")
1147	    if ($file =~ /^\.(rhosts|Xauthority|kermrc|netrc)$/);
1148	chmod(0700, "$homedir/$file")
1149	    if ($file =~ /^(Mail|prv|\.(iscreen|term))$/);
1150    }
1151    closedir D;
1152    return 1;
1153}
1154
1155# makes a directory hierarchy
1156sub mkdir_home {
1157    local($dir) = @_;
1158    $dir = &stripdir($dir);
1159    local($user_partition) = "/usr";
1160    local($dirname) = &dirname($dir);
1161
1162
1163    -e $dirname || &mkdirhier($dirname);
1164
1165    if (((stat($dirname))[0]) == ((stat("/"))[0])){
1166	# home partition is on root partition
1167	# create home partition on $user_partition and make
1168	# a symlink from $dir to $user_partition/`basename $dir`
1169	# For instance: /home -> /usr/home
1170
1171	local($basename) = &basename($dir);
1172	local($d) = "$user_partition/$basename";
1173
1174
1175	if (-d $d) {
1176	    warn "Oops, $d already exists\n" if $verbose;
1177	} else {
1178	    print "Create $d\n" if $verbose;
1179	    if (!mkdir("$d", 0755)) {
1180		warn "$d: $!\a\n"; return 0;
1181	    }
1182	}
1183
1184	unlink($dir);		# symlink to nonexist file
1185	print "Create symlink: $dir -> $d\n" if $verbose;
1186	if (!symlink("$d", $dir)) {
1187	    warn "Symlink $d: $!\a\n"; return 0;
1188	}
1189    } else {
1190	print "Create $dir\n" if $verbose;
1191	if (!mkdir("$dir", 0755)) {
1192	    warn "Directory ``$dir'': $!\a\n"; return 0;
1193	}
1194    }
1195    return 1;
1196}
1197
1198sub mkdirhier {
1199    local($dir) = @_;
1200    local($d,$p);
1201
1202    $dir = &stripdir($dir);
1203
1204    foreach $d (split('/', $dir)) {
1205	$dir = "$p/$d";
1206	$dir =~ s|^//|/|;
1207	if (! -e "$dir") {
1208	    print "Create $dir\n" if $verbose;
1209	    if (!mkdir("$dir", 0755)) {
1210		warn "$dir: $!\n"; return 0;
1211	    }
1212	}
1213	$p .= "/$d";
1214    }
1215    return 1;
1216}
1217
1218# stript unused '/'
1219# e.g.: //usr///home// -> /usr/home
1220sub stripdir {
1221    local($dir) = @_;
1222
1223    $dir =~ s|/+|/|g;		# delete double '/'
1224    $dir =~ s|/$||;		# delete '/' at end
1225    return $dir if $dir ne "";
1226    return '/';
1227}
1228
1229# Read one of the elements from @list. $confirm is the default.
1230# If !$allow then accept only elements from @list.
1231sub confirm_list {
1232    local($message, $allow, $confirm, @list) = @_;
1233    local($read, $c, $print);
1234
1235    $print = "$message" if $message;
1236    $print .= " " unless $message =~ /\n$/ || $#list == 0;
1237
1238    $print .= join($", &uniq(@list)); #"
1239    $print .= " " unless $message =~ /\n$/ && $#list == 0;
1240    print "$print";
1241    print "\n" if (length($print) + length($confirm)) > 60;
1242    print "[$confirm]: ";
1243
1244    chop($read = <STDIN>);
1245    $read =~ s/^\s*//;
1246    $read =~ s/\s*$//;
1247    return $confirm if $read eq "";
1248    return "$read" if $allow;
1249
1250    foreach $c (@list) {
1251	return $read if $c eq $read;
1252    }
1253    warn "$read: is not allowed!\a\n";
1254    return &confirm_list($message, $allow, $confirm, @list);
1255}
1256
1257# YES, NO, DEFAULT or userstring
1258# 1. return "" if "no" or no string is provided by the user.
1259# 2. return the $default parameter if "yes" or "default" provided.
1260# otherwise return user provided string.
1261sub confirm_yn_default {
1262    local($message, $confirm, $default) = @_;
1263
1264    print "$message [$confirm]: ";
1265    chop($read = <STDIN>);
1266    $read =~ s/^\s*//;
1267    $read =~ s/\s*$//;
1268    return "" unless $read;
1269
1270    return choosetxt_yn_default($read, $default);
1271}
1272
1273sub choosetxt_yn_default {
1274    local($read, $default) = @_;
1275
1276    if ($read =~ "$no") {
1277	return "";
1278    }
1279    if ($read eq "default") {
1280	return $default;
1281    }
1282    if ($read =~ "$yes") {
1283	if ($verbose == 1) {
1284	    return $read;
1285	}
1286	return $default;
1287    }
1288    return $read;
1289}
1290
1291# YES or NO question
1292# return 1 if &confirm("message", "yes") and answer is yes
1293#	or if &confirm("message", "no") and answer is no
1294# otherwise return 0
1295sub confirm_yn {
1296    local($message, $confirm) = @_;
1297    local($read, $c);
1298
1299    if ($confirm && ($confirm =~ "$yes" || $confirm == 1)) {
1300	$confirm = "y";
1301    } else {
1302	$confirm = "n";
1303    }
1304    print "$message (y/n) [$confirm]: ";
1305    chop($read = <STDIN>);
1306    $read =~ s/^\s*//;
1307    $read =~ s/\s*$//;
1308    return 1 unless $read;
1309
1310    if (($confirm eq "y" && $read =~ "$yes") ||
1311	($confirm eq "n" && $read =~ "$no")) {
1312	return 1;
1313    }
1314
1315    if ($read !~ "$yes" && $read !~ "$no") {
1316	warn "Wrong value. Enter again!\a\n";
1317	return &confirm_yn($message, $confirm);
1318    }
1319    return 0;
1320}
1321
1322# test if $dotdir exist
1323# return "no" if $dotdir not exist or dotfiles should not copied
1324sub dotdir_default {
1325    local($dir) = $dotdir;
1326
1327    return &dotdir_default_valid($dir) unless $verbose;
1328    while($verbose) {
1329	$dir = &confirm_list("Copy dotfiles from:", 1,
1330	    $dir, ("no", $dotdir_bak, $dir));
1331	last if $dir eq &dotdir_default_valid($dir);
1332    }
1333    warn "Do not copy dotfiles.\n" if $verbose && $dir eq "no";
1334
1335    $changes++ if $dir ne $dotdir;
1336    return $dir;
1337}
1338
1339sub dotdir_default_valid {
1340    local($dir) = @_;
1341
1342    return $dir if (-e $dir && -r _ && (-d _ || -l $dir) && $dir =~ "^/");
1343    return $dir if $dir eq "no";
1344    warn "Dotdir ``$dir'' is not a directory\a\n";
1345    return "no";
1346}
1347
1348# ask for messages to new users
1349sub message_default {
1350    local($tmp_message_file) = $message_file;
1351
1352    while($verbose) {
1353	$send_message = "no";
1354
1355	$message_file = &confirm_yn_default(
1356			    "Send welcome message?: /path/file default no",
1357				"no", $tmp_message_file);
1358	if ($message_file eq "") {
1359	    $message_file = $tmp_message_file;
1360	    last;
1361	}
1362	if ($message_file =~ $yes) {
1363	    $message_file = &confirm_yn_default(
1364		 	     "Really? Type the filepath, 'default' or 'no'",
1365			     "no", $tmp_message_file);
1366	    if ($message_file eq "") {
1367	        $message_file = $tmp_message_file;
1368	        last;
1369	    }
1370	}
1371
1372	# try and create the message file
1373	if (&filetest($message_file, 0)) {
1374	    if (&confirm_yn("File ``$message_file'' exists. Overwrite?:",
1375			    "no")) {
1376	        print "Retry: choose a different location\n";
1377	        next;
1378	    }
1379	    if (&message_create($message_file)) {
1380		print "Message file ``$message_file'' overwritten\n"
1381		    if $verbose;
1382	    }
1383	} else {
1384	    if (&message_create($message_file)) {
1385		print "Message file ``$message_file'' created\n" if $verbose;
1386	    }
1387	}
1388
1389	if (&filetest($message_file, 0)) {
1390	    $send_message = "yes";
1391	    last;
1392	}
1393	last if !&confirm_yn("Unable to create ``$message_file'', try again?",
1394			     "yes");
1395    }
1396
1397    if ($send_message eq "no" || !&filetest($message_file, 0)) {
1398	warn "Do not send message(s)\n" if $verbose;
1399	$send_message = "no";
1400    } else {
1401	&message_read($message_file);
1402    }
1403
1404    $changes++ if $tmp_message_file ne $message_file && $verbose;
1405    return $send_message;
1406}
1407
1408# create message file
1409sub message_create {
1410    local($file) = @_;
1411
1412    rename($file, "$file.bak");
1413    if (!open(M, "> $file")) {
1414	warn "Messagefile ``$file'': $!\n"; return 0;
1415    }
1416    print M <<EOF;
1417#
1418# Message file for adduser(8)
1419#   comment: ``#''
1420#   default variables: \$name, \$fullname, \$password
1421#   other variables:  see /etc/adduser.conf after
1422#		     line  ``$do_not_delete''
1423#
1424
1425\$fullname,
1426
1427your account ``\$name'' was created.
1428Have fun!
1429
1430See also chpass(1), finger(1), passwd(1)
1431EOF
1432    close M;
1433    return 1;
1434}
1435
1436# read message file into buffer
1437sub message_read {
1438    local($file) = @_;
1439    @message_buffer = ();
1440
1441    if (!open(R, "$file")) {
1442	warn "File ``$file'':$!\n"; return 0;
1443    }
1444    while(<R>) {
1445	push(@message_buffer, $_) unless /^\s*#/;
1446    }
1447    close R;
1448}
1449
1450# write @list to $file with file-locking
1451sub append_file {
1452    local($file,@list) = @_;
1453    local($e);
1454
1455    open(F, ">> $file") || die "$file: $!\n";
1456    print "Lock $file.\n" if $verbose > 1;
1457    while(!flock(F, LOCK_EX | LOCK_NB)) {
1458	warn "Cannot lock file: $file\a\n";
1459	die "Sorry, gave up\n"
1460	    unless &confirm_yn("Try again?", "yes");
1461    }
1462    print F join("\n", @list) . "\n";
1463    print "Unlock $file.\n" if $verbose > 1;
1464    flock(F, LOCK_UN);
1465    close F;
1466}
1467
1468# return free uid+gid
1469# uid == gid if possible
1470sub next_id {
1471    local($group) = @_;
1472
1473    $uid_start = 1000 if ($uid_start <= 0 || $uid_start >= $uid_end);
1474    # looking for next free uid
1475    while($uid{$uid_start}) {
1476	$uid_start++;
1477	$uid_start = 1000 if $uid_start >= $uid_end;
1478	print "$uid_start\n" if $verbose > 1;
1479    }
1480
1481    local($gid_start) = $uid_start;
1482    # group for user (username==groupname) already exist
1483    if ($groupname{$group}) {
1484	$gid_start = $groupname{$group};
1485    }
1486    # gid is in use, looking for another gid.
1487    # Note: uid and gid are not equal
1488    elsif ($gid{$uid_start}) {
1489	while($gid{$gid_start} || $uid{$gid_start}) {
1490	    $gid_start--;
1491	    $gid_start = $uid_end if $gid_start < 100;
1492	}
1493    }
1494    return ($uid_start, $gid_start);
1495}
1496
1497# read config file - typically /etc/adduser.conf
1498sub config_read {
1499    local($opt) = join " ", @_;
1500    local($user_flag) = 0;
1501
1502    # don't read config file
1503    return 1 if $opt =~ /-(noconfig|config_create)/ || !$config_read;
1504
1505    if (!-f $config) {
1506        warn("Couldn't find $config: creating a new adduser configuration file\n");
1507        &create_conf;
1508    }
1509
1510    if (!open(C, "$config")) {
1511	warn "$config: $!\n"; return 0;
1512    }
1513
1514    while(<C>) {
1515	# user defined variables
1516	/^$do_not_delete/ && $user_flag++;
1517	# found @array or $variable
1518	if (s/^(\w+\s*=\s*\()/\@$1/ || s/^(\w+\s*=)/\$$1/) {
1519	    eval $_;
1520	    #warn "$_";
1521	}
1522	next if /^$/;
1523	# lines with '^##' are not saved
1524	push(@user_variable_list, $_)
1525	    if $user_flag && !/^##/ && (s/^[\$\@]// || /^[#\s]/);
1526    }
1527    #warn "X @user_variable_list X\n";
1528    close C;
1529}
1530
1531
1532# write config file
1533sub config_write {
1534    local($silent) = @_;
1535
1536    # nothing to do
1537    return 1 unless ($changes || ! -e $config || !$config_read || $silent);
1538
1539    if (!$silent) {
1540	if (-e $config) {
1541	    return 1 if &confirm_yn("\nWrite your changes to $config?", "no");
1542	} else {
1543	    return 1 unless
1544		&confirm_yn("\nWrite your configuration to $config?", "yes");
1545	}
1546    }
1547
1548    rename($config, "$config.bak");
1549    open(C, "> $config") || die "$config: $!\n";
1550
1551    # prepare some variables
1552    $send_message = "no" unless $send_message;
1553    $defaultpasswd = "no" unless $defaultpasswd;
1554    local($shpref) = "'" . join("', '", @shellpref) . "'";
1555    local($shpath) = "'" . join("', '", @path) . "'";
1556    local($user_var) = join('', @user_variable_list);
1557    local($def_lc) = "'" . join("', '", @login_classes) . "'";
1558
1559    print C <<EOF;
1560#
1561# $config - automatic generated by adduser(8)
1562#
1563# Note: adduser reads *and* writes this file.
1564#	You may change values, but don't add new things before the
1565#	line ``$do_not_delete''
1566#	Also, unquoted strings may cause warnings
1567#
1568
1569# verbose = [0-2]
1570verbose = $verbose
1571
1572# Get new password for new users
1573# defaultpasswd =  yes | no
1574defaultpasswd = "$defaultpasswd"
1575
1576# Default encryption method for user passwords
1577# Methods are all those listed in login.conf(5)
1578encryptionmethod = "$defaultencryption"
1579
1580# copy dotfiles from this dir ("/etc/skel" or "no")
1581dotdir = "$dotdir"
1582
1583# send message to user? ("yes" or "no")
1584send_message = "$send_message"
1585
1586# send this file to new user ("/etc/adduser.message")
1587message_file = "$message_file"
1588
1589# config file for adduser ("/etc/adduser.conf")
1590config = "$config"
1591
1592# logfile ("/var/log/adduser" or "no")
1593logfile = "$logfile"
1594
1595# default HOME directory ("/home")
1596home = "$home"
1597
1598# List of directories where shells located
1599# path = ('/bin', '/usr/bin', '/usr/local/bin')
1600path = ($shpath)
1601
1602# common shell list, first element has higher priority
1603# shellpref = ('bash', 'tcsh', 'ksh', 'csh', 'sh')
1604shellpref = ($shpref)
1605
1606# defaultshell if not empty ("bash")
1607defaultshell = "$defaultshell"
1608
1609# defaultgroup ('USER' for same as username or any other valid group)
1610defaultgroup = "$defaultgroup"
1611
1612# new users get this uid
1613uid_start = $uid_start
1614uid_end = $uid_end
1615
1616# default login.conf(5) login class
1617defaultclass = "$defaultclass"
1618
1619# login classes available from login.conf(5)
1620# login_classes = ('default', 'daemon', 'staff')
1621login_classes = ($def_lc)
1622
1623$do_not_delete
1624## your own variables, see /etc/adduser.message
1625EOF
1626    print C "$user_var\n" if ($user_var ne '');
1627    print C "\n## end\n";
1628    close C;
1629}
1630
1631# check for sane variables
1632sub variable_check {
1633	# Check uid_start & uid_end
1634	warn "WARNING: uid_start < 1000!\n" if($uid_start < 1000);
1635	die "ERROR: uid_start >= uid_end!\n" if($uid_start >= $uid_end);
1636	# unencrypted really only usable in batch mode
1637	warn "WARNING: unencrypted only effective in batch mode\n"
1638	    if($#batch < 0 && $unencrypted);
1639}
1640
1641sub cleanup {
1642    local($sig) = @_;
1643
1644    print STDERR "Caught signal SIG$sig -- cleaning up.\n";
1645    system("stty", "echo");
1646    exit(0);
1647}
1648
1649END {
1650    if (-e $etc_ptmp && defined(fileno(PTMP))) {
1651	    close PTMP;
1652	    unlink($etc_ptmp) || warn "Error: unable to remove $etc_ptmp: $!\nPlease verify that $etc_ptmp no longer exists!\n";
1653    }
1654}
1655