xref: /freebsd/usr.sbin/adduser/adduser.sh (revision 2e3507c25e42292b45a5482e116d278f5515d04d)
1#!/bin/sh
2#
3# SPDX-License-Identifier: BSD-2-Clause
4#
5# Copyright (c) 2002-2004 Michael Telahun Makonnen. All rights reserved.
6#
7# Redistribution and use in source and binary forms, with or without
8# modification, are permitted provided that the following conditions
9# are met:
10# 1. Redistributions of source code must retain the above copyright
11#    notice, this list of conditions and the following disclaimer.
12# 2. Redistributions in binary form must reproduce the above copyright
13#    notice, this list of conditions and the following disclaimer in the
14#    documentation and/or other materials provided with the distribution.
15#
16# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26#
27#       Email: Mike Makonnen <mtm@FreeBSD.Org>
28#
29#
30
31# err msg
32#	Display $msg on stderr, unless we're being quiet.
33#
34err() {
35	if [ -z "$quietflag" ]; then
36		echo 1>&2 ${THISCMD}: ERROR: $*
37	fi
38}
39
40# info msg
41#	Display $msg on stdout, unless we're being quiet.
42#
43info() {
44	if [ -z "$quietflag" ]; then
45		echo ${THISCMD}: INFO: $*
46	fi
47}
48
49# get_nextuid
50#	Output the value of $_uid if it is available for use. If it
51#	is not, output the value of the next higher uid that is available.
52#	If a uid is not specified, output the first available uid, as indicated
53#	by pw(8).
54#
55get_nextuid () {
56	_uid=$1
57	_nextuid=
58
59	if [ -z "$_uid" ]; then
60		_nextuid="`${PWCMD} usernext | cut -f1 -d:`"
61	else
62		while : ; do
63			${PWCMD} usershow $_uid > /dev/null 2>&1
64			if [ ! "$?" -eq 0 ]; then
65				_nextuid=$_uid
66				break
67			fi
68			_uid=$(($_uid + 1))
69		done
70	fi
71	echo $_nextuid
72}
73
74# show_usage
75#	Display usage information for this utility.
76#
77show_usage() {
78	echo "usage: ${THISCMD} [options]"
79	echo "  options may include:"
80	echo "  -C		save to the configuration file only"
81	echo "  -D		do not attempt to create the home directory"
82	echo "  -E		disable this account after creation"
83	echo "  -G		additional groups to add accounts to"
84	echo "  -L		login class of the user"
85	echo "  -M		file permission for home directory"
86	echo "  -N		do not read configuration file"
87	echo "  -S		a nonexistent shell is not an error"
88	echo "  -d		home directory"
89	echo "  -f		file from which input will be received"
90	echo "  -g		default login group"
91	echo "  -h		display this usage message"
92	echo "  -k		path to skeleton home directory"
93	echo "  -m		user welcome message file"
94	echo "  -q		absolute minimal user feedback"
95	echo "  -s		shell"
96	echo "  -u		uid to start at"
97	echo "  -w		password type: no, none, yes or random"
98}
99
100# valid_shells
101#	Outputs a list of valid shells from /etc/shells. Only the
102#	basename of the shell is output.
103#
104valid_shells() {
105	_prefix=
106	cat ${ETCSHELLS} |
107	while read _path _junk ; do
108		case $_path in
109		\#*|'')
110			;;
111		*)
112			echo -n "${_prefix}`basename $_path`"
113			_prefix=' '
114			;;
115		esac
116	done
117
118	# /usr/sbin/nologin is a special case
119	[ -x "${NOLOGIN_PATH}" ] && echo -n " ${NOLOGIN}"
120}
121
122# fullpath_from_shell shell
123#	Given $shell, which is either the full path to a shell or
124#	the basename component of a valid shell, get the
125#	full path to the shell from the /etc/shells file.
126#
127fullpath_from_shell() {
128	_shell=$1
129	[ -z "$_shell" ] && return 1
130
131	# /usr/sbin/nologin is a special case; it needs to be handled
132	# before the cat | while loop, since a 'return' from within
133	# a subshell will not terminate the function's execution, and
134	# the path to the nologin shell might be printed out twice.
135	#
136	if [ "$_shell" = "${NOLOGIN}" -o \
137	    "$_shell" = "${NOLOGIN_PATH}" ]; then
138		echo ${NOLOGIN_PATH}
139		return 0;
140	fi
141
142	cat ${ETCSHELLS} |
143	while read _path _junk ; do
144		case "$_path" in
145		\#*|'')
146			;;
147		*)
148			if [ "$_path" = "$_shell" -o \
149			    "`basename $_path`" = "$_shell" ]; then
150				echo $_path
151				return 0
152			fi
153			;;
154		esac
155	done
156
157	return 1
158}
159
160# shell_exists shell
161#	If the given shell is listed in ${ETCSHELLS} or it is
162#	the nologin shell this function will return 0.
163#	Otherwise, it will return 1. If shell is valid but
164#	the path is invalid or it is not executable it
165#	will emit an informational message saying so.
166#
167shell_exists() {
168	_sh="$1"
169	_shellchk="${GREPCMD} '^$_sh$' ${ETCSHELLS} > /dev/null 2>&1"
170
171	if ! eval $_shellchk; then
172		# The nologin shell is not listed in /etc/shells.
173		if [ "$_sh" != "${NOLOGIN_PATH}" ]; then
174			err "Invalid shell ($_sh) for user $username."
175			return 1
176		fi
177	fi
178	! [ -x "$_sh" ] &&
179	    info "The shell ($_sh) does not exist or is not executable."
180
181	return 0
182}
183
184# save_config
185#	Save some variables to a configuration file.
186#	Note: not all script variables are saved, only those that
187#	      it makes sense to save.
188#
189save_config() {
190	echo "# Configuration file for adduser(8)."     >  ${ADDUSERCONF}
191	echo "# NOTE: only *some* variables are saved." >> ${ADDUSERCONF}
192	echo "# Last Modified on `${DATECMD}`."		>> ${ADDUSERCONF}
193	echo ''				>> ${ADDUSERCONF}
194	echo "defaultHomePerm=$uhomeperm" >> ${ADDUSERCONF}
195	echo "defaultLgroup=$ulogingroup" >> ${ADDUSERCONF}
196	echo "defaultclass=$uclass"	>> ${ADDUSERCONF}
197	echo "defaultgroups=$ugroups"	>> ${ADDUSERCONF}
198	echo "passwdtype=$passwdtype" 	>> ${ADDUSERCONF}
199	echo "homeprefix=$homeprefix" 	>> ${ADDUSERCONF}
200	echo "defaultshell=$ushell"	>> ${ADDUSERCONF}
201	echo "udotdir=$udotdir"		>> ${ADDUSERCONF}
202	echo "msgfile=$msgfile"		>> ${ADDUSERCONF}
203	echo "disableflag=$disableflag" >> ${ADDUSERCONF}
204	echo "uidstart=$uidstart"       >> ${ADDUSERCONF}
205}
206
207# add_user
208#	Add a user to the user database. If the user chose to send a welcome
209#	message or lock the account, do so.
210#
211add_user() {
212
213	# Is this a configuration run? If so, don't modify user database.
214	#
215	if [ -n "$configflag" ]; then
216		save_config
217		return
218	fi
219
220	_uid=
221	_name=
222	_comment=
223	_gecos=
224	_home=
225	_group=
226	_grouplist=
227	_shell=
228	_class=
229	_dotdir=
230	_expire=
231	_pwexpire=
232	_passwd=
233	_upasswd=
234	_passwdmethod=
235
236	_name="-n '$username'"
237	[ -n "$uuid" ] && _uid='-u "$uuid"'
238	[ -n "$ulogingroup" ] && _group='-g "$ulogingroup"'
239	[ -n "$ugroups" ] && _grouplist='-G "$ugroups"'
240	[ -n "$ushell" ] && _shell='-s "$ushell"'
241	[ -n "$uclass" ] && _class='-L "$uclass"'
242	[ -n "$ugecos" ] && _comment='-c "$ugecos"'
243	[ -n "$udotdir" ] && _dotdir='-k "$udotdir"'
244	[ -n "$uexpire" ] && _expire='-e "$uexpire"'
245	[ -n "$upwexpire" ] && _pwexpire='-p "$upwexpire"'
246	if [ -z "$Dflag" -a -n "$uhome" ]; then
247		# The /nonexistent home directory is special. It
248		# means the user has no home directory.
249		if [ "$uhome" = "$NOHOME" ]; then
250			_home='-d "$uhome"'
251		else
252			# Use home directory permissions if specified
253			if [ -n "$uhomeperm" ]; then
254				_home='-m -d "$uhome" -M "$uhomeperm"'
255			else
256				_home='-m -d "$uhome"'
257			fi
258		fi
259	elif [ -n "$Dflag" -a -n "$uhome" ]; then
260		_home='-d "$uhome"'
261	fi
262	case $passwdtype in
263	no)
264		_passwdmethod="-w no"
265		_passwd="-h -"
266		;;
267	yes)
268		# Note on processing the password: The outer double quotes
269		# make literal everything except ` and \ and $.
270		# The outer single quotes make literal ` and $.
271		# We can ensure the \ isn't treated specially by specifying
272		# the -r switch to the read command used to obtain the input.
273		#
274		_passwdmethod="-w yes"
275		_passwd="-h 0"
276		_upasswd='echo "$upass" |'
277		;;
278	none)
279		_passwdmethod="-w none"
280		;;
281	random)
282		_passwdmethod="-w random"
283		;;
284	esac
285
286	_pwcmd="$_upasswd ${PWCMD} useradd $_uid $_name $_group $_grouplist $_comment"
287	_pwcmd="$_pwcmd $_shell $_class $_home $_dotdir $_passwdmethod $_passwd"
288	_pwcmd="$_pwcmd $_expire $_pwexpire"
289
290	if ! _output=`eval $_pwcmd` ; then
291		err "There was an error adding user ($username)."
292		return 1
293	else
294		info "Successfully added ($username) to the user database."
295		if [ "random" = "$passwdtype" ]; then
296			randompass="$_output"
297			info "Password for ($username) is: $randompass"
298		fi
299	fi
300
301	if [ -n "$disableflag" ]; then
302		if ${PWCMD} lock $username ; then
303			info "Account ($username) is locked."
304		else
305			info "Account ($username) could NOT be locked."
306		fi
307	fi
308
309	_line=
310	_owner=
311	_perms=
312	if [ -n "$msgflag" ]; then
313		[ -r "$msgfile" ] && {
314			# We're evaluating the contents of an external file.
315			# Let's not open ourselves up for attack. _perms will
316			# be empty if it's writeable only by the owner. _owner
317			# will *NOT* be empty if the file is owned by root.
318			#
319			_dir="`dirname $msgfile`"
320			_file="`basename $msgfile`"
321			_perms=`/usr/bin/find $_dir -name $_file -perm +07022 -prune`
322			_owner=`/usr/bin/find $_dir -name $_file -user 0 -prune`
323			if [ -z "$_owner" -o -n "$_perms" ]; then
324				err "The message file ($msgfile) may be writeable only by root."
325				return 1
326			fi
327			cat "$msgfile" |
328			while read _line ; do
329				eval echo "$_line"
330			done | ${MAILCMD} -s"Welcome" ${username}
331			info "Sent welcome message to ($username)."
332		}
333	fi
334}
335
336# get_user
337#	Reads username of the account from standard input or from a global
338#	variable containing an account line from a file. The username is
339#	required. If this is an interactive session it will prompt in
340#	a loop until a username is entered. If it is batch processing from
341#	a file it will output an error message and return to the caller.
342#
343get_user() {
344	_input=
345
346	# No need to take down user names if this is a configuration saving run.
347	[ -n "$configflag" ] && return
348
349	while : ; do
350		if [ -z "$fflag" ]; then
351			echo -n "Username: "
352			read _input
353		else
354			_input="`echo "$fileline" | cut -f1 -d:`"
355		fi
356
357		# There *must* be a username, and it must not exist. If
358		# this is an interactive session give the user an
359		# opportunity to retry.
360		#
361		if [ -z "$_input" ]; then
362			err "You must enter a username!"
363			[ -z "$fflag" ] && continue
364		fi
365		${PWCMD} usershow $_input > /dev/null 2>&1
366		if [ "$?" -eq 0 ]; then
367			err "User exists!"
368			[ -z "$fflag" ] && continue
369		fi
370		break
371	done
372	username="$_input"
373}
374
375# get_gecos
376#	Reads extra information about the user. Can be used both in interactive
377#	and batch (from file) mode.
378#
379get_gecos() {
380	_input=
381
382	# No need to take down additional user information for a configuration run.
383	[ -n "$configflag" ] && return
384
385	if [ -z "$fflag" ]; then
386		echo -n "Full name: "
387		read _input
388	else
389		_input="`echo "$fileline" | cut -f7 -d:`"
390	fi
391	ugecos="$_input"
392}
393
394# get_shell
395#	Get the account's shell. Works in interactive and batch mode. It
396#	accepts either the base name of the shell or the full path.
397#	If an invalid shell is entered it will simply use the default shell.
398#
399get_shell() {
400	_input=
401	_fullpath=
402	ushell="$defaultshell"
403
404	# Make sure the current value of the shell is a valid one
405	if [ -z "$Sflag" ]; then
406		if ! shell_exists $ushell ; then
407			info "Using default shell ${defaultshell}."
408			ushell="$defaultshell"
409		fi
410	fi
411
412	if [ -z "$fflag" ]; then
413		echo -n "Shell ($shells) [`basename $ushell`]: "
414		read _input
415	else
416		_input="`echo "$fileline" | cut -f9 -d:`"
417	fi
418	if [ -n "$_input" ]; then
419		if [ -n "$Sflag" ]; then
420			ushell="$_input"
421		else
422			_fullpath=`fullpath_from_shell $_input`
423			if [ -n "$_fullpath" ]; then
424				ushell="$_fullpath"
425			else
426				err "Invalid shell ($_input) for user $username."
427				info "Using default shell ${defaultshell}."
428				ushell="$defaultshell"
429			fi
430		fi
431	fi
432}
433
434# get_homedir
435#	Reads the account's home directory. Used both with interactive input
436#	and batch input.
437#
438get_homedir() {
439	_input=
440	if [ -z "$fflag" ]; then
441		echo -n "Home directory [${homeprefix}/${username}]: "
442		read _input
443	else
444		_input="`echo "$fileline" | cut -f8 -d:`"
445	fi
446
447	if [ -n "$_input" ]; then
448		uhome="$_input"
449		# if this is a configuration run, then user input is the home
450		# directory prefix. Otherwise it is understood to
451		# be $prefix/$user
452		#
453		[ -z "$configflag" ] && homeprefix="`dirname $uhome`" || homeprefix="$uhome"
454	else
455		uhome="${homeprefix}/${username}"
456	fi
457}
458
459# get_homeperm
460#	Reads the account's home directory permissions.
461#
462get_homeperm() {
463	uhomeperm=$defaultHomePerm
464	_input=
465	_prompt=
466
467	if [ -n "$uhomeperm" ]; then
468		_prompt="Home directory permissions [${uhomeperm}]: "
469	else
470		_prompt="Home directory permissions (Leave empty for default): "
471	fi
472	if [ -z "$fflag" ]; then
473		echo -n "$_prompt"
474		read _input
475	fi
476
477	if [ -n "$_input" ]; then
478		uhomeperm="$_input"
479	fi
480}
481
482# get_uid
483#	Reads a numeric userid in an interactive or batch session. Automatically
484#	allocates one if it is not specified.
485#
486get_uid() {
487	uuid=${uidstart}
488	_input=
489	_prompt=
490
491	if [ -n "$uuid" ]; then
492		uuid=`get_nextuid $uuid`
493		_prompt="Uid [$uuid]: "
494	else
495		_prompt="Uid (Leave empty for default): "
496	fi
497	if [ -z "$fflag" ]; then
498		echo -n "$_prompt"
499		read _input
500	else
501		_input="`echo "$fileline" | cut -f2 -d:`"
502	fi
503
504	[ -n "$_input" ] && uuid=$_input
505	uuid=`get_nextuid $uuid`
506	uidstart=$uuid
507}
508
509# get_class
510#	Reads login class of account. Can be used in interactive or batch mode.
511#
512get_class() {
513	uclass="$defaultclass"
514	_input=
515	_class=${uclass:-"default"}
516
517	if [ -z "$fflag" ]; then
518		echo -n "Login class [$_class]: "
519		read _input
520	else
521		_input="`echo "$fileline" | cut -f4 -d:`"
522	fi
523
524	[ -n "$_input" ] && uclass="$_input"
525}
526
527# get_logingroup
528#	Reads user's login group. Can be used in both interactive and batch
529#	modes. The specified value can be a group name or its numeric id.
530#	This routine leaves the field blank if nothing is provided and
531#	a default login group has not been set. The pw(8) command
532#	will then provide a login group with the same name as the username.
533#
534get_logingroup() {
535	ulogingroup="$defaultLgroup"
536	_input=
537
538	if [ -z "$fflag" ]; then
539		echo -n "Login group [${ulogingroup:-$username}]: "
540		read _input
541	else
542		_input="`echo "$fileline" | cut -f3 -d:`"
543	fi
544
545	# Pw(8) will use the username as login group if it's left empty
546	[ -n "$_input" ] && ulogingroup="$_input"
547}
548
549# get_groups
550#	Read additional groups for the user. It can be used in both interactive
551#	and batch modes.
552#
553get_groups() {
554	ugroups="$defaultgroups"
555	_input=
556	_group=${ulogingroup:-"${username}"}
557
558	if [ -z "$configflag" ]; then
559		[ -z "$fflag" ] && echo -n "Login group is $_group. Invite $username"
560		[ -z "$fflag" ] && echo -n " into other groups? [$ugroups]: "
561	else
562		[ -z "$fflag" ] && echo -n "Enter additional groups [$ugroups]: "
563	fi
564	read _input
565
566	[ -n "$_input" ] && ugroups="$_input"
567}
568
569# get_expire_dates
570#	Read expiry information for the account and also for the password. This
571#	routine is used only from batch processing mode.
572#
573get_expire_dates() {
574	upwexpire="`echo "$fileline" | cut -f5 -d:`"
575	uexpire="`echo "$fileline" | cut -f6 -d:`"
576}
577
578# get_password
579#	Read the password in batch processing mode. The password field matters
580#	only when the password type is "yes" or "random". If the field is empty and the
581#	password type is "yes", then it assumes the account has an empty passsword
582#	and changes the password type accordingly. If the password type is "random"
583#	and the password field is NOT empty, then it assumes the account will NOT
584#	have a random password and set passwdtype to "yes."
585#
586get_password() {
587	# We may temporarily change a password type. Make sure it's changed
588	# back to whatever it was before we process the next account.
589	#
590	[ -n "$savedpwtype" ] && {
591		passwdtype=$savedpwtype
592		savedpwtype=
593	}
594
595	# There may be a ':' in the password
596	upass=${fileline#*:*:*:*:*:*:*:*:*:}
597
598	if [ -z "$upass" ]; then
599		case $passwdtype in
600		yes)
601			# if it's empty, assume an empty password
602			passwdtype=none
603			savedpwtype=yes
604			;;
605		esac
606	else
607		case $passwdtype in
608		random)
609			passwdtype=yes
610			savedpwtype=random
611			;;
612		esac
613	fi
614}
615
616# input_from_file
617#	Reads a line of account information from standard input and
618#	adds it to the user database.
619#
620input_from_file() {
621	_field=
622
623	while read -r fileline ; do
624		case "$fileline" in
625		\#*|'')
626			;;
627		*)
628			get_user || continue
629			get_gecos
630			get_uid
631			get_logingroup
632			get_class
633			get_shell
634			get_homedir
635			get_homeperm
636			get_password
637			get_expire_dates
638			ugroups="$defaultgroups"
639
640			add_user
641			;;
642		esac
643	done
644}
645
646# input_interactive
647#	Prompts for user information interactively, and commits to
648#	the user database.
649#
650input_interactive() {
651	_disable=
652	_pass=
653	_passconfirm=
654	_random="no"
655	_emptypass="no"
656	_usepass="yes"
657	_logingroup_ok="no"
658	_groups_ok="no"
659	_all_ok="yes"
660	_another_user="no"
661	case $passwdtype in
662	none)
663		_emptypass="yes"
664		_usepass="yes"
665		;;
666	no)
667		_usepass="no"
668		;;
669	random)
670		_random="yes"
671		;;
672	esac
673
674	get_user
675	get_gecos
676	get_uid
677
678	# The case where group = user is handled elsewhere, so
679	# validate any other groups the user is invited to.
680	until [ "$_logingroup_ok" = yes ]; do
681		get_logingroup
682		_logingroup_ok=yes
683		if [ -n "$ulogingroup" -a "$username" != "$ulogingroup" ]; then
684			if ! ${PWCMD} show group $ulogingroup > /dev/null 2>&1; then
685				echo "Group $ulogingroup does not exist!"
686				_logingroup_ok=no
687			fi
688		fi
689	done
690	until [ "$_groups_ok" = yes ]; do
691		get_groups
692		_groups_ok=yes
693		for i in $ugroups; do
694			if [ "$username" != "$i" ]; then
695				if ! ${PWCMD} show group $i > /dev/null 2>&1; then
696					echo "Group $i does not exist!"
697					_groups_ok=no
698				fi
699			fi
700		done
701	done
702
703	get_class
704	get_shell
705	get_homedir
706	get_homeperm
707
708	while : ; do
709		echo -n "Use password-based authentication? [$_usepass]: "
710		read _input
711		[ -z "$_input" ] && _input=$_usepass
712		case $_input in
713		[Nn][Oo]|[Nn])
714			passwdtype="no"
715			;;
716		[Yy][Ee][Ss]|[Yy][Ee]|[Yy])
717			while : ; do
718				echo -n "Use an empty password? (yes/no) [$_emptypass]: "
719				read _input
720				[ -n "$_input" ] && _emptypass=$_input
721				case $_emptypass in
722				[Nn][Oo]|[Nn])
723					echo -n "Use a random password? (yes/no) [$_random]: "
724					read _input
725					[ -n "$_input" ] && _random="$_input"
726					case $_random in
727					[Yy][Ee][Ss]|[Yy][Ee]|[Yy])
728						passwdtype="random"
729						break
730						;;
731					esac
732					passwdtype="yes"
733					[ -n "$configflag" ] && break
734					trap 'stty echo; exit' 0 1 2 3 15
735					stty -echo
736					echo -n "Enter password: "
737					IFS= read -r upass
738					echo''
739					echo -n "Enter password again: "
740					IFS= read -r _passconfirm
741					echo ''
742					stty echo
743					# if user entered a blank password
744					# explicitly ask again.
745					[ -z "$upass" -a -z "$_passconfirm" ] \
746					    && continue
747					;;
748				[Yy][Ee][Ss]|[Yy][Ee]|[Yy])
749					passwdtype="none"
750					break;
751					;;
752				*)
753					# invalid answer; repeat the loop
754					continue
755					;;
756				esac
757				if [ "$upass" != "$_passconfirm" ]; then
758					echo "Passwords did not match!"
759					continue
760				fi
761				break
762			done
763			;;
764		*)
765			# invalid answer; repeat loop
766			continue
767			;;
768		esac
769		break;
770	done
771	_disable=${disableflag:-"no"}
772	while : ; do
773		echo -n "Lock out the account after creation? [$_disable]: "
774		read _input
775		[ -z "$_input" ] && _input=$_disable
776		case $_input in
777		[Nn][Oo]|[Nn])
778			disableflag=
779			;;
780		[Yy][Ee][Ss]|[Yy][Ee]|[Yy])
781			disableflag=yes
782			;;
783		*)
784			# invalid answer; repeat loop
785			continue
786			;;
787		esac
788		break
789	done
790
791	# Display the information we have so far and prompt to
792	# commit it.
793	#
794	_disable=${disableflag:-"no"}
795	[ -z "$configflag" ] && printf "%-10s : %s\n" Username $username
796	case $passwdtype in
797	yes)
798		_pass='*****'
799		;;
800	no)
801		_pass='<disabled>'
802		;;
803	none)
804		_pass='<blank>'
805		;;
806	random)
807		_pass='<random>'
808		;;
809	esac
810	[ -z "$configflag" ] && printf "%-10s : %s\n" "Password" "$_pass"
811	[ -n "$configflag" ] && printf "%-10s : %s\n" "Pass Type" "$passwdtype"
812	[ -z "$configflag" ] && printf "%-10s : %s\n" "Full Name" "$ugecos"
813	[ -z "$configflag" ] && printf "%-10s : %s\n" "Uid" "$uuid"
814	printf "%-10s : %s\n" "Class" "$uclass"
815	printf "%-10s : %s %s\n" "Groups" "${ulogingroup:-$username}" "$ugroups"
816	printf "%-10s : %s\n" "Home" "$uhome"
817	printf "%-10s : %s\n" "Home Mode" "$uhomeperm"
818	printf "%-10s : %s\n" "Shell" "$ushell"
819	printf "%-10s : %s\n" "Locked" "$_disable"
820	while : ; do
821		echo -n "OK? (yes/no) [$_all_ok]: "
822		read _input
823		if [ -z "$_input" ]; then
824			_input=$_all_ok
825		fi
826		case $_input in
827		[Nn][Oo]|[Nn])
828			return 1
829			;;
830		[Yy][Ee][Ss]|[Yy][Ee]|[Yy])
831			add_user
832			;;
833		*)
834			continue
835			;;
836		esac
837		break
838	done
839	return 0
840}
841
842#### END SUBROUTINE DEFINITION ####
843
844THISCMD=`/usr/bin/basename $0`
845DEFAULTSHELL=/bin/sh
846ADDUSERCONF="${ADDUSERCONF:-/etc/adduser.conf}"
847PWCMD="${PWCMD:-/usr/sbin/pw}"
848MAILCMD="${MAILCMD:-mail}"
849ETCSHELLS="${ETCSHELLS:-/etc/shells}"
850NOHOME="/nonexistent"
851NOLOGIN="nologin"
852NOLOGIN_PATH="/usr/sbin/nologin"
853GREPCMD="/usr/bin/grep"
854DATECMD="/bin/date"
855
856# Set default values
857#
858username=
859uuid=
860uidstart=
861ugecos=
862ulogingroup=
863uclass=
864uhome=
865uhomeperm=
866upass=
867ushell=
868udotdir=/usr/share/skel
869ugroups=
870uexpire=
871upwexpire=
872shells="`valid_shells`"
873passwdtype="yes"
874msgfile=/etc/adduser.msg
875msgflag=
876quietflag=
877configflag=
878fflag=
879infile=
880disableflag=
881Dflag=
882Sflag=
883readconfig="yes"
884homeprefix="/home"
885randompass=
886fileline=
887savedpwtype=
888defaultclass=
889defaultLgroup=
890defaultgroups=
891defaultshell="${DEFAULTSHELL}"
892defaultHomePerm=
893
894# Make sure the user running this program is root. This isn't a security
895# measure as much as it is a useful method of reminding the user to
896# 'su -' before he/she wastes time entering data that won't be saved.
897#
898procowner=${procowner:-`/usr/bin/id -u`}
899if [ "$procowner" != "0" ]; then
900	err 'you must be the super-user (uid 0) to use this utility.'
901	exit 1
902fi
903
904# Override from our conf file
905# Quickly go through the commandline line to see if we should read
906# from our configuration file. The actual parsing of the commandline
907# arguments happens after we read in our configuration file (commandline
908# should override configuration file).
909#
910for _i in $* ; do
911	if [ "$_i" = "-N" ]; then
912		readconfig=
913		break;
914	fi
915done
916if [ -n "$readconfig" ]; then
917	# On a long-lived system, the first time this script is run it
918	# will barf upon reading the configuration file for its perl predecessor.
919	if ( . ${ADDUSERCONF} > /dev/null 2>&1 ); then
920		[ -r ${ADDUSERCONF} ] && . ${ADDUSERCONF} > /dev/null 2>&1
921	fi
922fi
923
924# Process command-line options
925#
926for _switch ; do
927	case $_switch in
928	-L)
929		defaultclass="$2"
930		shift; shift
931		;;
932	-C)
933		configflag=yes
934		shift
935		;;
936	-D)
937		Dflag=yes
938		shift
939		;;
940	-E)
941		disableflag=yes
942		shift
943		;;
944	-k)
945		udotdir="$2"
946		shift; shift
947		;;
948	-f)
949		[ "$2" != "-" ] && infile="$2"
950		fflag=yes
951		shift; shift
952		;;
953	-g)
954		defaultLgroup="$2"
955		shift; shift
956		;;
957	-G)
958		defaultgroups="$2"
959		shift; shift
960		;;
961	-h)
962		show_usage
963		exit 0
964		;;
965	-d)
966		homeprefix="$2"
967		shift; shift
968		;;
969	-m)
970		case "$2" in
971		[Nn][Oo])
972			msgflag=
973			;;
974		*)
975			msgflag=yes
976			msgfile="$2"
977			;;
978		esac
979		shift; shift
980		;;
981	-M)
982		defaultHomePerm=$2
983		shift; shift
984		;;
985	-N)
986		readconfig=
987		shift
988		;;
989	-w)
990		case "$2" in
991		no|none|random|yes)
992			passwdtype=$2
993			;;
994		*)
995			show_usage
996			exit 1
997			;;
998		esac
999		shift; shift
1000		;;
1001	-q)
1002		quietflag=yes
1003		shift
1004		;;
1005	-s)
1006		defaultshell="`fullpath_from_shell $2`"
1007		shift; shift
1008		;;
1009	-S)
1010		Sflag=yes
1011		shift
1012		;;
1013	-u)
1014		uidstart=$2
1015		shift; shift
1016		;;
1017	esac
1018done
1019
1020# If the -f switch was used, get input from a file. Otherwise,
1021# this is an interactive session.
1022#
1023if [ -n "$fflag" ]; then
1024	if [ -z "$infile" ]; then
1025		input_from_file
1026	elif [ -n "$infile" ]; then
1027		if [ -r "$infile" ]; then
1028			input_from_file < $infile
1029		else
1030			err "File ($infile) is unreadable or does not exist."
1031		fi
1032	fi
1033else
1034	input_interactive
1035	while : ; do
1036		if [ -z "$configflag" ]; then
1037			echo -n "Add another user? (yes/no) [$_another_user]: "
1038		else
1039			echo -n "Re-edit the default configuration? (yes/no) [$_another_user]: "
1040		fi
1041		read _input
1042		if [ -z "$_input" ]; then
1043			_input=$_another_user
1044		fi
1045		case $_input in
1046		[Yy][Ee][Ss]|[Yy][Ee]|[Yy])
1047			uidstart=`get_nextuid $uidstart`
1048			input_interactive
1049			continue
1050			;;
1051		[Nn][Oo]|[Nn])
1052			echo "Goodbye!"
1053			;;
1054		*)
1055			continue
1056			;;
1057		esac
1058		break
1059	done
1060fi
1061