1107543Sscottl#!/bin/sh
2107543Sscottl#
3127076Smtm# Copyright (c) 2002-2004 Michael Telahun Makonnen. All rights reserved.
4107543Sscottl#
5107543Sscottl# Redistribution and use in source and binary forms, with or without
6107543Sscottl# modification, are permitted provided that the following conditions
7107543Sscottl# are met:
8107543Sscottl# 1. Redistributions of source code must retain the above copyright
9107543Sscottl#    notice, this list of conditions and the following disclaimer.
10107543Sscottl# 2. Redistributions in binary form must reproduce the above copyright
11107543Sscottl#    notice, this list of conditions and the following disclaimer in the
12107543Sscottl#    documentation and/or other materials provided with the distribution.
13107543Sscottl#
14107543Sscottl# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
15107543Sscottl# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
16107543Sscottl# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
17107543Sscottl# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
18107543Sscottl# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
19107543Sscottl# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
20107543Sscottl# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
21107543Sscottl# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22107543Sscottl# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
23107543Sscottl# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24107543Sscottl#
25116624Smtm#       Email: Mike Makonnen <mtm@FreeBSD.Org>
26107543Sscottl#
27107543Sscottl# $FreeBSD$
28107543Sscottl#
29107543Sscottl
30107543Sscottl# err msg
31202312Sjoel#	Display $msg on stderr, unless we're being quiet.
32202312Sjoel#
33107543Sscottlerr() {
34107543Sscottl	if [ -z "$quietflag" ]; then
35202312Sjoel		echo 1>&2 ${THISCMD}: ERROR: $*
36107543Sscottl	fi
37107543Sscottl}
38107543Sscottl
39107543Sscottl# info msg
40202312Sjoel#	Display $msg on stdout, unless we're being quiet.
41202312Sjoel#
42107543Sscottlinfo() {
43107543Sscottl	if [ -z "$quietflag" ]; then
44202312Sjoel		echo ${THISCMD}: INFO: $*
45107543Sscottl	fi
46107543Sscottl}
47107543Sscottl
48107543Sscottl# get_nextuid
49107543Sscottl#	Output the value of $_uid if it is available for use. If it
50107543Sscottl#	is not, output the value of the next higher uid that is available.
51107543Sscottl#	If a uid is not specified, output the first available uid, as indicated
52107543Sscottl#	by pw(8).
53202312Sjoel#
54107543Sscottlget_nextuid () {
55107543Sscottl	_uid=$1
56107543Sscottl	_nextuid=
57107543Sscottl
58107543Sscottl	if [ -z "$_uid" ]; then
59107543Sscottl		_nextuid="`${PWCMD} usernext | cut -f1 -d:`"
60107543Sscottl	else
61107543Sscottl		while : ; do
62107543Sscottl			${PWCMD} usershow $_uid > /dev/null 2>&1
63107543Sscottl			if [ ! "$?" -eq 0 ]; then
64107543Sscottl				_nextuid=$_uid
65107543Sscottl				break
66107543Sscottl			fi
67107543Sscottl			_uid=$(($_uid + 1))
68107543Sscottl		done
69107543Sscottl	fi
70107543Sscottl	echo $_nextuid
71107543Sscottl}
72107543Sscottl
73107543Sscottl# show_usage
74107543Sscottl#	Display usage information for this utility.
75107543Sscottl#
76107543Sscottlshow_usage() {
77107543Sscottl	echo "usage: ${THISCMD} [options]"
78107543Sscottl	echo "  options may include:"
79107543Sscottl	echo "  -C		save to the configuration file only"
80127076Smtm	echo "  -D		do not attempt to create the home directory"
81107543Sscottl	echo "  -E		disable this account after creation"
82107543Sscottl	echo "  -G		additional groups to add accounts to"
83107543Sscottl	echo "  -L		login class of the user"
84175719Smtm	echo "  -M		file permission for home directory"
85107543Sscottl	echo "  -N		do not read configuration file"
86127076Smtm	echo "  -S		a nonexistent shell is not an error"
87107543Sscottl	echo "  -d		home directory"
88107543Sscottl	echo "  -f		file from which input will be received"
89112519Smtm	echo "  -g		default login group"
90107543Sscottl	echo "  -h		display this usage message"
91107543Sscottl	echo "  -k		path to skeleton home directory"
92107543Sscottl	echo "  -m		user welcome message file"
93107543Sscottl	echo "  -q		absolute minimal user feedback"
94107543Sscottl	echo "  -s		shell"
95107543Sscottl	echo "  -u		uid to start at"
96107543Sscottl	echo "  -w		password type: no, none, yes or random"
97107543Sscottl}
98107543Sscottl
99107543Sscottl# valid_shells
100107543Sscottl#	Outputs a list of valid shells from /etc/shells. Only the
101107543Sscottl#	basename of the shell is output.
102107543Sscottl#
103107543Sscottlvalid_shells() {
104107543Sscottl	_prefix=
105107543Sscottl	cat ${ETCSHELLS} |
106107543Sscottl	while read _path _junk ; do
107107543Sscottl		case $_path in
108107543Sscottl		\#*|'')
109107543Sscottl			;;
110107543Sscottl		*)
111107543Sscottl			echo -n "${_prefix}`basename $_path`"
112107543Sscottl			_prefix=' '
113107543Sscottl			;;
114107543Sscottl		esac
115107543Sscottl	done
116116627Smtm
117127635Scperciva	# /usr/sbin/nologin is a special case
118116627Smtm	[ -x "${NOLOGIN_PATH}" ] && echo -n " ${NOLOGIN}"
119107543Sscottl}
120107543Sscottl
121107543Sscottl# fullpath_from_shell shell
122130160Smtm#	Given $shell, which is either the full path to a shell or
123130160Smtm#	the basename component of a valid shell, get the
124107543Sscottl#	full path to the shell from the /etc/shells file.
125107543Sscottl#
126107543Sscottlfullpath_from_shell() {
127107543Sscottl	_shell=$1
128107543Sscottl	[ -z "$_shell" ] && return 1
129107543Sscottl
130135616Sroam	# /usr/sbin/nologin is a special case; it needs to be handled
131135616Sroam	# before the cat | while loop, since a 'return' from within
132135616Sroam	# a subshell will not terminate the function's execution, and
133135616Sroam	# the path to the nologin shell might be printed out twice.
134135616Sroam	#
135135616Sroam	if [ "$_shell" = "${NOLOGIN}" -o \
136135616Sroam	    "$_shell" = "${NOLOGIN_PATH}" ]; then
137135616Sroam		echo ${NOLOGIN_PATH}
138135616Sroam		return 0;
139135616Sroam	fi
140135616Sroam
141107543Sscottl	cat ${ETCSHELLS} |
142107543Sscottl	while read _path _junk ; do
143107543Sscottl		case "$_path" in
144107543Sscottl		\#*|'')
145107543Sscottl			;;
146107543Sscottl		*)
147130160Smtm			if [ "$_path" = "$_shell" -o \
148130160Smtm			    "`basename $_path`" = "$_shell" ]; then
149107543Sscottl				echo $_path
150107543Sscottl				return 0
151107543Sscottl			fi
152107543Sscottl			;;
153107543Sscottl		esac
154107543Sscottl	done
155116627Smtm
156107543Sscottl	return 1
157107543Sscottl}
158107543Sscottl
159116627Smtm# shell_exists shell
160116627Smtm#	If the given shell is listed in ${ETCSHELLS} or it is
161116627Smtm#	the nologin shell this function will return 0.
162116627Smtm#	Otherwise, it will return 1. If shell is valid but
163116627Smtm#	the path is invalid or it is not executable it
164116627Smtm#	will emit an informational message saying so.
165116627Smtm#
166202312Sjoelshell_exists() {
167116627Smtm	_sh="$1"
168116627Smtm	_shellchk="${GREPCMD} '^$_sh$' ${ETCSHELLS} > /dev/null 2>&1"
169116627Smtm
170116627Smtm	if ! eval $_shellchk; then
171116627Smtm		# The nologin shell is not listed in /etc/shells.
172116627Smtm		if [ "$_sh" != "${NOLOGIN_PATH}" ]; then
173116627Smtm			err "Invalid shell ($_sh) for user $username."
174116627Smtm			return 1
175116627Smtm		fi
176116627Smtm	fi
177116627Smtm	! [ -x "$_sh" ] &&
178116628Smtm	    info "The shell ($_sh) does not exist or is not executable."
179116627Smtm
180116627Smtm	return 0
181116627Smtm}
182116627Smtm
183107543Sscottl# save_config
184107543Sscottl#	Save some variables to a configuration file.
185107543Sscottl#	Note: not all script variables are saved, only those that
186107543Sscottl#	      it makes sense to save.
187107543Sscottl#
188107543Sscottlsave_config() {
189107543Sscottl	echo "# Configuration file for adduser(8)."     >  ${ADDUSERCONF}
190107543Sscottl	echo "# NOTE: only *some* variables are saved." >> ${ADDUSERCONF}
191109751Sfjoe	echo "# Last Modified on `${DATECMD}`."		>> ${ADDUSERCONF}
192107543Sscottl	echo ''				>> ${ADDUSERCONF}
193175719Smtm	echo "defaultHomePerm=$uhomeperm" >> ${ADDUSERCONF}
194112433Smtm	echo "defaultLgroup=$ulogingroup" >> ${ADDUSERCONF}
195107543Sscottl	echo "defaultclass=$uclass"	>> ${ADDUSERCONF}
196107543Sscottl	echo "defaultgroups=$ugroups"	>> ${ADDUSERCONF}
197107543Sscottl	echo "passwdtype=$passwdtype" 	>> ${ADDUSERCONF}
198107543Sscottl	echo "homeprefix=$homeprefix" 	>> ${ADDUSERCONF}
199107543Sscottl	echo "defaultshell=$ushell"	>> ${ADDUSERCONF}
200107543Sscottl	echo "udotdir=$udotdir"		>> ${ADDUSERCONF}
201107543Sscottl	echo "msgfile=$msgfile"		>> ${ADDUSERCONF}
202107543Sscottl	echo "disableflag=$disableflag" >> ${ADDUSERCONF}
203168656Smtm	echo "uidstart=$uidstart"       >> ${ADDUSERCONF}
204107543Sscottl}
205107543Sscottl
206107543Sscottl# add_user
207107543Sscottl#	Add a user to the user database. If the user chose to send a welcome
208107543Sscottl#	message or lock the account, do so.
209107543Sscottl#
210107543Sscottladd_user() {
211107543Sscottl
212107543Sscottl	# Is this a configuration run? If so, don't modify user database.
213107543Sscottl	#
214107543Sscottl	if [ -n "$configflag" ]; then
215107543Sscottl		save_config
216107543Sscottl		return
217107543Sscottl	fi
218107543Sscottl
219107543Sscottl	_uid=
220107543Sscottl	_name=
221107543Sscottl	_comment=
222107543Sscottl	_gecos=
223107543Sscottl	_home=
224107543Sscottl	_group=
225107543Sscottl	_grouplist=
226107543Sscottl	_shell=
227107543Sscottl	_class=
228107543Sscottl	_dotdir=
229107543Sscottl	_expire=
230107543Sscottl	_pwexpire=
231107543Sscottl	_passwd=
232107543Sscottl	_upasswd=
233107543Sscottl	_passwdmethod=
234107543Sscottl
235109720Smtm	_name="-n '$username'"
236110595Smtm	[ -n "$uuid" ] && _uid='-u "$uuid"'
237110595Smtm	[ -n "$ulogingroup" ] && _group='-g "$ulogingroup"'
238110595Smtm	[ -n "$ugroups" ] && _grouplist='-G "$ugroups"'
239110595Smtm	[ -n "$ushell" ] && _shell='-s "$ushell"'
240110595Smtm	[ -n "$uclass" ] && _class='-L "$uclass"'
241110595Smtm	[ -n "$ugecos" ] && _comment='-c "$ugecos"'
242110595Smtm	[ -n "$udotdir" ] && _dotdir='-k "$udotdir"'
243110595Smtm	[ -n "$uexpire" ] && _expire='-e "$uexpire"'
244110595Smtm	[ -n "$upwexpire" ] && _pwexpire='-p "$upwexpire"'
245127076Smtm	if [ -z "$Dflag" -a -n "$uhome" ]; then
246127076Smtm		# The /nonexistent home directory is special. It
247127076Smtm		# means the user has no home directory.
248127076Smtm		if [ "$uhome" = "$NOHOME" ]; then
249127076Smtm			_home='-d "$uhome"'
250127076Smtm		else
251175719Smtm			# Use home directory permissions if specified
252175719Smtm			if [ -n "$uhomeperm" ]; then
253175719Smtm				_home='-m -d "$uhome" -M "$uhomeperm"'
254175719Smtm			else
255175719Smtm				_home='-m -d "$uhome"'
256175719Smtm			fi
257127076Smtm		fi
258127076Smtm	elif [ -n "$Dflag" -a -n "$uhome" ]; then
259127076Smtm		_home='-d "$uhome"'
260127076Smtm	fi
261107543Sscottl	case $passwdtype in
262107543Sscottl	no)
263107543Sscottl		_passwdmethod="-w no"
264107543Sscottl		_passwd="-h -"
265107543Sscottl		;;
266107543Sscottl	yes)
267110595Smtm		# Note on processing the password: The outer double quotes
268110595Smtm		# make literal everything except ` and \ and $.
269110595Smtm		# The outer single quotes make literal ` and $.
270110595Smtm		# We can ensure the \ isn't treated specially by specifying
271110595Smtm		# the -r switch to the read command used to obtain the input.
272110595Smtm		#
273107543Sscottl		_passwdmethod="-w yes"
274107543Sscottl		_passwd="-h 0"
275110595Smtm		_upasswd='echo "$upass" |'
276107543Sscottl		;;
277107543Sscottl	none)
278107543Sscottl		_passwdmethod="-w none"
279107543Sscottl		;;
280107543Sscottl	random)
281107543Sscottl		_passwdmethod="-w random"
282107543Sscottl		;;
283107543Sscottl	esac
284107543Sscottl
285107543Sscottl	_pwcmd="$_upasswd ${PWCMD} useradd $_uid $_name $_group $_grouplist $_comment"
286107543Sscottl	_pwcmd="$_pwcmd $_shell $_class $_home $_dotdir $_passwdmethod $_passwd"
287107543Sscottl	_pwcmd="$_pwcmd $_expire $_pwexpire"
288107543Sscottl
289107543Sscottl	if ! _output=`eval $_pwcmd` ; then
290107543Sscottl		err "There was an error adding user ($username)."
291107543Sscottl		return 1
292107543Sscottl	else
293107543Sscottl		info "Successfully added ($username) to the user database."
294107543Sscottl		if [ "random" = "$passwdtype" ]; then
295107543Sscottl			randompass="$_output"
296107543Sscottl			info "Password for ($username) is: $randompass"
297107543Sscottl		fi
298107543Sscottl	fi
299107543Sscottl
300107543Sscottl	if [ -n "$disableflag" ]; then
301107543Sscottl		if ${PWCMD} lock $username ; then
302107543Sscottl			info "Account ($username) is locked."
303107543Sscottl		else
304107543Sscottl			info "Account ($username) could NOT be locked."
305107543Sscottl		fi
306107543Sscottl	fi
307107543Sscottl
308107543Sscottl	_line=
309107543Sscottl	_owner=
310107543Sscottl	_perms=
311107543Sscottl	if [ -n "$msgflag" ]; then
312107543Sscottl		[ -r "$msgfile" ] && {
313107543Sscottl			# We're evaluating the contents of an external file.
314107543Sscottl			# Let's not open ourselves up for attack. _perms will
315107543Sscottl			# be empty if it's writeable only by the owner. _owner
316107543Sscottl			# will *NOT* be empty if the file is owned by root.
317107543Sscottl			#
318107543Sscottl			_dir="`dirname $msgfile`"
319107543Sscottl			_file="`basename $msgfile`"
320107543Sscottl			_perms=`/usr/bin/find $_dir -name $_file -perm +07022 -prune`
321107543Sscottl			_owner=`/usr/bin/find $_dir -name $_file -user 0 -prune`
322107543Sscottl			if [ -z "$_owner" -o -n "$_perms" ]; then
323107543Sscottl				err "The message file ($msgfile) may be writeable only by root."
324107543Sscottl				return 1
325107543Sscottl			fi
326107543Sscottl			cat "$msgfile" |
327107543Sscottl			while read _line ; do
328107543Sscottl				eval echo "$_line"
329107543Sscottl			done | ${MAILCMD} -s"Welcome" ${username}
330107543Sscottl			info "Sent welcome message to ($username)."
331107543Sscottl		}
332107543Sscottl	fi
333107543Sscottl}
334107543Sscottl
335107543Sscottl# get_user
336107543Sscottl#	Reads username of the account from standard input or from a global
337107543Sscottl#	variable containing an account line from a file. The username is
338107543Sscottl#	required. If this is an interactive session it will prompt in
339107543Sscottl#	a loop until a username is entered. If it is batch processing from
340107543Sscottl#	a file it will output an error message and return to the caller.
341107543Sscottl#
342107543Sscottlget_user() {
343107543Sscottl	_input=
344107543Sscottl
345107543Sscottl	# No need to take down user names if this is a configuration saving run.
346107543Sscottl	[ -n "$configflag" ] && return
347107543Sscottl
348107543Sscottl	while : ; do
349107543Sscottl		if [ -z "$fflag" ]; then
350107543Sscottl			echo -n "Username: "
351107543Sscottl			read _input
352107543Sscottl		else
353107543Sscottl			_input="`echo "$fileline" | cut -f1 -d:`"
354107543Sscottl		fi
355107543Sscottl
356167917Sle		# There *must* be a username, and it must not exist. If
357167917Sle		# this is an interactive session give the user an
358167917Sle		# opportunity to retry.
359107543Sscottl		#
360107543Sscottl		if [ -z "$_input" ]; then
361107543Sscottl			err "You must enter a username!"
362107543Sscottl			[ -z "$fflag" ] && continue
363107543Sscottl		fi
364167917Sle		${PWCMD} usershow $_input > /dev/null 2>&1
365167917Sle		if [ "$?" -eq 0 ]; then
366167917Sle			err "User exists!"
367167917Sle			[ -z "$fflag" ] && continue
368167917Sle		fi
369107543Sscottl		break
370107543Sscottl	done
371107543Sscottl	username="$_input"
372107543Sscottl}
373107543Sscottl
374107543Sscottl# get_gecos
375107543Sscottl#	Reads extra information about the user. Can be used both in interactive
376107543Sscottl#	and batch (from file) mode.
377107543Sscottl#
378107543Sscottlget_gecos() {
379107543Sscottl	_input=
380107543Sscottl
381107543Sscottl	# No need to take down additional user information for a configuration run.
382107543Sscottl	[ -n "$configflag" ] && return
383107543Sscottl
384107543Sscottl	if [ -z "$fflag" ]; then
385107543Sscottl		echo -n "Full name: "
386107543Sscottl		read _input
387107543Sscottl	else
388107543Sscottl		_input="`echo "$fileline" | cut -f7 -d:`"
389107543Sscottl	fi
390107543Sscottl	ugecos="$_input"
391107543Sscottl}
392107543Sscottl
393107543Sscottl# get_shell
394107543Sscottl#	Get the account's shell. Works in interactive and batch mode. It
395130160Smtm#	accepts either the base name of the shell or the full path.
396107543Sscottl#	If an invalid shell is entered it will simply use the default shell.
397107543Sscottl#
398107543Sscottlget_shell() {
399107543Sscottl	_input=
400107543Sscottl	_fullpath=
401107543Sscottl	ushell="$defaultshell"
402107543Sscottl
403107543Sscottl	# Make sure the current value of the shell is a valid one
404127076Smtm	if [ -z "$Sflag" ]; then
405127076Smtm		if ! shell_exists $ushell ; then
406127076Smtm			info "Using default shell ${defaultshell}."
407127076Smtm			ushell="$defaultshell"
408127076Smtm		fi
409116627Smtm	fi
410107543Sscottl
411107543Sscottl	if [ -z "$fflag" ]; then
412107543Sscottl		echo -n "Shell ($shells) [`basename $ushell`]: "
413107543Sscottl		read _input
414107543Sscottl	else
415107543Sscottl		_input="`echo "$fileline" | cut -f9 -d:`"
416107543Sscottl	fi
417107543Sscottl	if [ -n "$_input" ]; then
418127076Smtm		if [ -n "$Sflag" ]; then
419127076Smtm			ushell="$_input"
420107543Sscottl		else
421127076Smtm			_fullpath=`fullpath_from_shell $_input`
422127076Smtm			if [ -n "$_fullpath" ]; then
423127076Smtm				ushell="$_fullpath"
424127076Smtm			else
425127076Smtm				err "Invalid shell ($_input) for user $username."
426127076Smtm				info "Using default shell ${defaultshell}."
427127076Smtm				ushell="$defaultshell"
428127076Smtm			fi
429107543Sscottl		fi
430107543Sscottl	fi
431107543Sscottl}
432107543Sscottl
433107543Sscottl# get_homedir
434107543Sscottl#	Reads the account's home directory. Used both with interactive input
435107543Sscottl#	and batch input.
436107543Sscottl#
437107543Sscottlget_homedir() {
438107543Sscottl	_input=
439107543Sscottl	if [ -z "$fflag" ]; then
440107543Sscottl		echo -n "Home directory [${homeprefix}/${username}]: "
441107543Sscottl		read _input
442107543Sscottl	else
443107543Sscottl		_input="`echo "$fileline" | cut -f8 -d:`"
444107543Sscottl	fi
445107543Sscottl
446107543Sscottl	if [ -n "$_input" ]; then
447107543Sscottl		uhome="$_input"
448107543Sscottl		# if this is a configuration run, then user input is the home
449107543Sscottl		# directory prefix. Otherwise it is understood to
450107543Sscottl		# be $prefix/$user
451107543Sscottl		#
452107543Sscottl		[ -z "$configflag" ] && homeprefix="`dirname $uhome`" || homeprefix="$uhome"
453107543Sscottl	else
454107543Sscottl		uhome="${homeprefix}/${username}"
455107543Sscottl	fi
456107543Sscottl}
457107543Sscottl
458175719Smtm# get_homeperm
459175719Smtm#	Reads the account's home directory permissions.
460175719Smtm#
461175719Smtmget_homeperm() {
462175719Smtm	uhomeperm=$defaultHomePerm
463175719Smtm	_input=
464175719Smtm	_prompt=
465175719Smtm
466175719Smtm	if [ -n "$uhomeperm" ]; then
467175719Smtm		_prompt="Home directory permissions [${uhomeperm}]: "
468175719Smtm	else
469175719Smtm		_prompt="Home directory permissions (Leave empty for default): "
470175719Smtm	fi
471175719Smtm	if [ -z "$fflag" ]; then
472175719Smtm		echo -n "$_prompt"
473175719Smtm		read _input
474175719Smtm	fi
475175719Smtm
476175719Smtm	if [ -n "$_input" ]; then
477175719Smtm		uhomeperm="$_input"
478175719Smtm	fi
479175719Smtm}
480175719Smtm
481107543Sscottl# get_uid
482107543Sscottl#	Reads a numeric userid in an interactive or batch session. Automatically
483107543Sscottl#	allocates one if it is not specified.
484107543Sscottl#
485107543Sscottlget_uid() {
486168656Smtm	uuid=${uidstart}
487107543Sscottl	_input=
488107543Sscottl	_prompt=
489107543Sscottl
490107543Sscottl	if [ -n "$uuid" ]; then
491232146Sdeischen		uuid=`get_nextuid $uuid`
492107543Sscottl		_prompt="Uid [$uuid]: "
493107543Sscottl	else
494107543Sscottl		_prompt="Uid (Leave empty for default): "
495107543Sscottl	fi
496107543Sscottl	if [ -z "$fflag" ]; then
497109573Sfjoe		echo -n "$_prompt"
498107543Sscottl		read _input
499107543Sscottl	else
500107543Sscottl		_input="`echo "$fileline" | cut -f2 -d:`"
501107543Sscottl	fi
502107543Sscottl
503107543Sscottl	[ -n "$_input" ] && uuid=$_input
504107543Sscottl	uuid=`get_nextuid $uuid`
505107543Sscottl	uidstart=$uuid
506107543Sscottl}
507107543Sscottl
508107543Sscottl# get_class
509107543Sscottl#	Reads login class of account. Can be used in interactive or batch mode.
510107543Sscottl#
511107543Sscottlget_class() {
512107543Sscottl	uclass="$defaultclass"
513107543Sscottl	_input=
514107543Sscottl	_class=${uclass:-"default"}
515107543Sscottl
516107543Sscottl	if [ -z "$fflag" ]; then
517107543Sscottl		echo -n "Login class [$_class]: "
518107543Sscottl		read _input
519107543Sscottl	else
520107543Sscottl		_input="`echo "$fileline" | cut -f4 -d:`"
521107543Sscottl	fi
522107543Sscottl
523107543Sscottl	[ -n "$_input" ] && uclass="$_input"
524107543Sscottl}
525107543Sscottl
526107543Sscottl# get_logingroup
527107543Sscottl#	Reads user's login group. Can be used in both interactive and batch
528107543Sscottl#	modes. The specified value can be a group name or its numeric id.
529112433Smtm#	This routine leaves the field blank if nothing is provided and
530112433Smtm#	a default login group has not been set. The pw(8) command
531112433Smtm#	will then provide a login group with the same name as the username.
532107543Sscottl#
533107543Sscottlget_logingroup() {
534112433Smtm	ulogingroup="$defaultLgroup"
535107543Sscottl	_input=
536107543Sscottl
537107543Sscottl	if [ -z "$fflag" ]; then
538112433Smtm		echo -n "Login group [${ulogingroup:-$username}]: "
539107543Sscottl		read _input
540107543Sscottl	else
541107543Sscottl		_input="`echo "$fileline" | cut -f3 -d:`"
542107543Sscottl	fi
543107543Sscottl
544107543Sscottl	# Pw(8) will use the username as login group if it's left empty
545112433Smtm	[ -n "$_input" ] && ulogingroup="$_input"
546107543Sscottl}
547107543Sscottl
548107543Sscottl# get_groups
549107543Sscottl#	Read additional groups for the user. It can be used in both interactive
550107543Sscottl#	and batch modes.
551107543Sscottl#
552107543Sscottlget_groups() {
553107543Sscottl	ugroups="$defaultgroups"
554107543Sscottl	_input=
555107543Sscottl	_group=${ulogingroup:-"${username}"}
556107543Sscottl
557107543Sscottl	if [ -z "$configflag" ]; then
558107543Sscottl		[ -z "$fflag" ] && echo -n "Login group is $_group. Invite $username"
559107543Sscottl		[ -z "$fflag" ] && echo -n " into other groups? [$ugroups]: "
560107543Sscottl	else
561107543Sscottl		[ -z "$fflag" ] && echo -n "Enter additional groups [$ugroups]: "
562107543Sscottl	fi
563107543Sscottl	read _input
564107543Sscottl
565107543Sscottl	[ -n "$_input" ] && ugroups="$_input"
566107543Sscottl}
567107543Sscottl
568107543Sscottl# get_expire_dates
569107543Sscottl#	Read expiry information for the account and also for the password. This
570107543Sscottl#	routine is used only from batch processing mode.
571107543Sscottl#
572107543Sscottlget_expire_dates() {
573107543Sscottl	upwexpire="`echo "$fileline" | cut -f5 -d:`"
574107543Sscottl	uexpire="`echo "$fileline" | cut -f6 -d:`"
575107543Sscottl}
576107543Sscottl
577107543Sscottl# get_password
578107543Sscottl#	Read the password in batch processing mode. The password field matters
579107543Sscottl#	only when the password type is "yes" or "random". If the field is empty and the
580107543Sscottl#	password type is "yes", then it assumes the account has an empty passsword
581107543Sscottl#	and changes the password type accordingly. If the password type is "random"
582107543Sscottl#	and the password field is NOT empty, then it assumes the account will NOT
583107543Sscottl#	have a random password and set passwdtype to "yes."
584107543Sscottl#
585107543Sscottlget_password() {
586107543Sscottl	# We may temporarily change a password type. Make sure it's changed
587107543Sscottl	# back to whatever it was before we process the next account.
588107543Sscottl	#
589107543Sscottl	[ -n "$savedpwtype" ] && {
590107543Sscottl		passwdtype=$savedpwtype
591107543Sscottl		savedpwtype=
592107543Sscottl	}
593107543Sscottl
594107543Sscottl	# There may be a ':' in the password
595107543Sscottl	upass=${fileline#*:*:*:*:*:*:*:*:*:}
596107543Sscottl
597107543Sscottl	if [ -z "$upass" ]; then
598107543Sscottl		case $passwdtype in
599107543Sscottl		yes)
600107543Sscottl			# if it's empty, assume an empty password
601107543Sscottl			passwdtype=none
602107543Sscottl			savedpwtype=yes
603107543Sscottl			;;
604107543Sscottl		esac
605107543Sscottl	else
606107543Sscottl		case $passwdtype in
607107543Sscottl		random)
608107543Sscottl			passwdtype=yes
609107543Sscottl			savedpwtype=random
610107543Sscottl			;;
611107543Sscottl		esac
612107543Sscottl	fi
613107543Sscottl}
614107543Sscottl
615107543Sscottl# input_from_file
616107543Sscottl#	Reads a line of account information from standard input and
617107543Sscottl#	adds it to the user database.
618107543Sscottl#
619107543Sscottlinput_from_file() {
620107543Sscottl	_field=
621107543Sscottl
622110595Smtm	while read -r fileline ; do
623107543Sscottl		case "$fileline" in
624107543Sscottl		\#*|'')
625107543Sscottl			;;
626168651Smtm		*)
627168651Smtm			get_user || continue
628168651Smtm			get_gecos
629168651Smtm			get_uid
630168651Smtm			get_logingroup
631168651Smtm			get_class
632168651Smtm			get_shell
633168651Smtm			get_homedir
634175719Smtm			get_homeperm
635168651Smtm			get_password
636168651Smtm			get_expire_dates
637172823Smtm			ugroups="$defaultgroups"
638168651Smtm
639168651Smtm			add_user
640168651Smtm			;;
641107543Sscottl		esac
642107543Sscottl	done
643107543Sscottl}
644107543Sscottl
645107543Sscottl# input_interactive
646107543Sscottl#	Prompts for user information interactively, and commits to
647107543Sscottl#	the user database.
648107543Sscottl#
649107543Sscottlinput_interactive() {
650107543Sscottl	_disable=
651107543Sscottl	_pass=
652107543Sscottl	_passconfirm=
653107543Sscottl	_random="no"
654107543Sscottl	_emptypass="no"
655107543Sscottl	_usepass="yes"
656112401Smtm	_logingroup_ok="no"
657112401Smtm	_groups_ok="no"
658107543Sscottl	case $passwdtype in
659107543Sscottl	none)
660107543Sscottl		_emptypass="yes"
661107543Sscottl		_usepass="yes"
662107543Sscottl		;;
663107543Sscottl	no)
664107543Sscottl		_usepass="no"
665107543Sscottl		;;
666107543Sscottl	random)
667107543Sscottl		_random="yes"
668107543Sscottl		;;
669107543Sscottl	esac
670107543Sscottl
671107543Sscottl	get_user
672107543Sscottl	get_gecos
673107543Sscottl	get_uid
674110537Sadrian
675110537Sadrian	# The case where group = user is handled elsewhere, so
676110537Sadrian	# validate any other groups the user is invited to.
677110537Sadrian	until [ "$_logingroup_ok" = yes ]; do
678110537Sadrian		get_logingroup
679110537Sadrian		_logingroup_ok=yes
680110537Sadrian		if [ -n "$ulogingroup" -a "$username" != "$ulogingroup" ]; then
681110537Sadrian			if ! ${PWCMD} show group $ulogingroup > /dev/null 2>&1; then
682110537Sadrian				echo "Group $ulogingroup does not exist!"
683110537Sadrian				_logingroup_ok=no
684110537Sadrian			fi
685110537Sadrian		fi
686110537Sadrian	done
687110537Sadrian	until [ "$_groups_ok" = yes ]; do
688110537Sadrian		get_groups
689110537Sadrian		_groups_ok=yes
690110537Sadrian		for i in $ugroups; do
691110537Sadrian			if [ "$username" != "$i" ]; then
692110537Sadrian				if ! ${PWCMD} show group $i > /dev/null 2>&1; then
693110537Sadrian					echo "Group $i does not exist!"
694110537Sadrian					_groups_ok=no
695110537Sadrian				fi
696110537Sadrian			fi
697110537Sadrian		done
698110537Sadrian	done
699110537Sadrian
700107543Sscottl	get_class
701107543Sscottl	get_shell
702107543Sscottl	get_homedir
703175719Smtm	get_homeperm
704107543Sscottl
705107543Sscottl	while : ; do
706107543Sscottl		echo -n "Use password-based authentication? [$_usepass]: "
707107543Sscottl		read _input
708107543Sscottl		[ -z "$_input" ] && _input=$_usepass
709107543Sscottl		case $_input in
710107543Sscottl		[Nn][Oo]|[Nn])
711107543Sscottl			passwdtype="no"
712107543Sscottl			;;
713107543Sscottl		[Yy][Ee][Ss]|[Yy][Ee]|[Yy])
714107543Sscottl			while : ; do
715107543Sscottl				echo -n "Use an empty password? (yes/no) [$_emptypass]: "
716107543Sscottl				read _input
717107543Sscottl				[ -n "$_input" ] && _emptypass=$_input
718107543Sscottl				case $_emptypass in
719107543Sscottl				[Nn][Oo]|[Nn])
720107543Sscottl					echo -n "Use a random password? (yes/no) [$_random]: "
721107543Sscottl					read _input
722107543Sscottl					[ -n "$_input" ] && _random="$_input"
723107543Sscottl					case $_random in
724107543Sscottl					[Yy][Ee][Ss]|[Yy][Ee]|[Yy])
725107543Sscottl						passwdtype="random"
726107543Sscottl						break
727107543Sscottl						;;
728107543Sscottl					esac
729107543Sscottl					passwdtype="yes"
730112401Smtm					[ -n "$configflag" ] && break
731107543Sscottl					trap 'stty echo; exit' 0 1 2 3 15
732107543Sscottl					stty -echo
733107543Sscottl					echo -n "Enter password: "
734110595Smtm					read -r upass
735107543Sscottl					echo''
736107543Sscottl					echo -n "Enter password again: "
737116623Smtm					read -r _passconfirm
738107543Sscottl					echo ''
739107543Sscottl					stty echo
740107543Sscottl					# if user entered a blank password
741107543Sscottl					# explicitly ask again.
742107543Sscottl					[ -z "$upass" -a -z "$_passconfirm" ] \
743107543Sscottl					    && continue
744107543Sscottl					;;
745107543Sscottl				[Yy][Ee][Ss]|[Yy][Ee]|[Yy])
746107543Sscottl					passwdtype="none"
747107543Sscottl					break;
748107543Sscottl					;;
749107543Sscottl				*)
750107543Sscottl					# invalid answer; repeat the loop
751107543Sscottl					continue
752107543Sscottl					;;
753107543Sscottl				esac
754107543Sscottl				if [ "$upass" != "$_passconfirm" ]; then
755107543Sscottl					echo "Passwords did not match!"
756107543Sscottl					continue
757107543Sscottl				fi
758107543Sscottl				break
759107543Sscottl			done
760107543Sscottl			;;
761107543Sscottl		*)
762107543Sscottl			# invalid answer; repeat loop
763107543Sscottl			continue
764107543Sscottl			;;
765107543Sscottl		esac
766107543Sscottl		break;
767107543Sscottl	done
768107543Sscottl	_disable=${disableflag:-"no"}
769107543Sscottl	while : ; do
770107543Sscottl		echo -n "Lock out the account after creation? [$_disable]: "
771107543Sscottl		read _input
772107543Sscottl		[ -z "$_input" ] && _input=$_disable
773107543Sscottl		case $_input in
774107543Sscottl		[Nn][Oo]|[Nn])
775107543Sscottl			disableflag=
776107543Sscottl			;;
777107543Sscottl		[Yy][Ee][Ss]|[Yy][Ee]|[Yy])
778107543Sscottl			disableflag=yes
779107543Sscottl			;;
780107543Sscottl		*)
781107543Sscottl			# invalid answer; repeat loop
782107543Sscottl			continue
783107543Sscottl			;;
784107543Sscottl		esac
785107543Sscottl		break
786107543Sscottl	done
787107543Sscottl	
788107543Sscottl	# Display the information we have so far and prompt to
789107543Sscottl	# commit it.
790107543Sscottl	#
791107543Sscottl	_disable=${disableflag:-"no"}
792107543Sscottl	[ -z "$configflag" ] && printf "%-10s : %s\n" Username $username
793107543Sscottl	case $passwdtype in
794107543Sscottl	yes)
795107543Sscottl		_pass='*****'
796107543Sscottl		;;
797107543Sscottl	no)
798107543Sscottl		_pass='<disabled>'
799107543Sscottl		;;
800107543Sscottl	none)
801107543Sscottl		_pass='<blank>'
802107543Sscottl		;;
803107543Sscottl	random)
804107543Sscottl		_pass='<random>'
805107543Sscottl		;;
806107543Sscottl	esac
807109751Sfjoe	[ -z "$configflag" ] && printf "%-10s : %s\n" "Password" "$_pass"
808109751Sfjoe	[ -n "$configflag" ] && printf "%-10s : %s\n" "Pass Type" "$passwdtype"
809107543Sscottl	[ -z "$configflag" ] && printf "%-10s : %s\n" "Full Name" "$ugecos"
810107543Sscottl	[ -z "$configflag" ] && printf "%-10s : %s\n" "Uid" "$uuid"
811107543Sscottl	printf "%-10s : %s\n" "Class" "$uclass"
812112433Smtm	printf "%-10s : %s %s\n" "Groups" "${ulogingroup:-$username}" "$ugroups"
813107543Sscottl	printf "%-10s : %s\n" "Home" "$uhome"
814175719Smtm	printf "%-10s : %s\n" "Home Mode" "$uhomeperm"
815107543Sscottl	printf "%-10s : %s\n" "Shell" "$ushell"
816107543Sscottl	printf "%-10s : %s\n" "Locked" "$_disable"
817107543Sscottl	while : ; do
818107543Sscottl		echo -n "OK? (yes/no): "
819107543Sscottl		read _input
820107543Sscottl		case $_input in
821107543Sscottl		[Nn][Oo]|[Nn])
822107543Sscottl			return 1
823107543Sscottl			;;
824107543Sscottl		[Yy][Ee][Ss]|[Yy][Ee]|[Yy])
825107543Sscottl			add_user
826107543Sscottl			;;
827107543Sscottl		*)
828107543Sscottl			continue
829107543Sscottl			;;
830107543Sscottl		esac
831107543Sscottl		break
832107543Sscottl	done
833107543Sscottl	return 0
834107543Sscottl}
835107543Sscottl
836145618Srobert#### END SUBROUTINE DEFINITION ####
837107543Sscottl
838107543SscottlTHISCMD=`/usr/bin/basename $0`
839107543SscottlDEFAULTSHELL=/bin/sh
840107543SscottlADDUSERCONF="${ADDUSERCONF:-/etc/adduser.conf}"
841107543SscottlPWCMD="${PWCMD:-/usr/sbin/pw}"
842107543SscottlMAILCMD="${MAILCMD:-mail}"
843107543SscottlETCSHELLS="${ETCSHELLS:-/etc/shells}"
844127076SmtmNOHOME="/nonexistent"
845116627SmtmNOLOGIN="nologin"
846127635ScpercivaNOLOGIN_PATH="/usr/sbin/nologin"
847109751SfjoeGREPCMD="/usr/bin/grep"
848109751SfjoeDATECMD="/bin/date"
849107543Sscottl
850107543Sscottl# Set default values
851107543Sscottl#
852107543Sscottlusername=
853107543Sscottluuid=
854107543Sscottluidstart=
855107543Sscottlugecos=
856107543Sscottlulogingroup=
857107543Sscottluclass=
858107543Sscottluhome=
859175719Smtmuhomeperm=
860107543Sscottlupass=
861107543Sscottlushell=
862107543Sscottludotdir=/usr/share/skel
863107543Sscottlugroups=
864107543Sscottluexpire=
865107543Sscottlupwexpire=
866107543Sscottlshells="`valid_shells`"
867107543Sscottlpasswdtype="yes"
868107543Sscottlmsgfile=/etc/adduser.msg
869107543Sscottlmsgflag=
870107543Sscottlquietflag=
871107543Sscottlconfigflag=
872107543Sscottlfflag=
873107543Sscottlinfile=
874107543Sscottldisableflag=
875127076SmtmDflag=
876127076SmtmSflag=
877107543Sscottlreadconfig="yes"
878107543Sscottlhomeprefix="/home"
879107543Sscottlrandompass=
880107543Sscottlfileline=
881107543Sscottlsavedpwtype=
882107543Sscottldefaultclass=
883112433SmtmdefaultLgroup=
884116784Smtmdefaultgroups=
885107543Sscottldefaultshell="${DEFAULTSHELL}"
886175719SmtmdefaultHomePerm=
887107543Sscottl
888107543Sscottl# Make sure the user running this program is root. This isn't a security
889202312Sjoel# measure as much as it is a useful method of reminding the user to
890107543Sscottl# 'su -' before he/she wastes time entering data that won't be saved.
891107543Sscottl#
892107543Sscottlprocowner=${procowner:-`/usr/bin/id -u`}
893107543Sscottlif [ "$procowner" != "0" ]; then
894107543Sscottl	err 'you must be the super-user (uid 0) to use this utility.'
895107543Sscottl	exit 1
896107543Sscottlfi
897107543Sscottl
898228990Suqs# Override from our conf file
899107543Sscottl# Quickly go through the commandline line to see if we should read
900107543Sscottl# from our configuration file. The actual parsing of the commandline
901107543Sscottl# arguments happens after we read in our configuration file (commandline
902107543Sscottl# should override configuration file).
903107543Sscottl#
904107543Sscottlfor _i in $* ; do
905107543Sscottl	if [ "$_i" = "-N" ]; then
906107543Sscottl		readconfig=
907107543Sscottl		break;
908107543Sscottl	fi
909107543Sscottldone
910107543Sscottlif [ -n "$readconfig" ]; then
911107543Sscottl	# On a long-lived system, the first time this script is run it
912107543Sscottl	# will barf upon reading the configuration file for its perl predecessor.
913107543Sscottl	if ( . ${ADDUSERCONF} > /dev/null 2>&1 ); then
914107543Sscottl		[ -r ${ADDUSERCONF} ] && . ${ADDUSERCONF} > /dev/null 2>&1
915107543Sscottl	fi
916107543Sscottlfi 
917107543Sscottl
918228990Suqs# Process command-line options
919107543Sscottl#
920107543Sscottlfor _switch ; do
921107543Sscottl	case $_switch in
922107543Sscottl	-L)
923107543Sscottl		defaultclass="$2"
924107543Sscottl		shift; shift
925107543Sscottl		;;
926107543Sscottl	-C)
927107543Sscottl		configflag=yes
928107543Sscottl		shift
929107543Sscottl		;;
930127076Smtm	-D)
931127076Smtm		Dflag=yes
932127076Smtm		shift
933127076Smtm		;;
934107543Sscottl	-E)
935107543Sscottl		disableflag=yes
936107543Sscottl		shift
937107543Sscottl		;;
938107543Sscottl	-k)
939107543Sscottl		udotdir="$2"
940107543Sscottl		shift; shift
941107543Sscottl		;;
942107543Sscottl	-f)
943107543Sscottl		[ "$2" != "-" ] && infile="$2"
944107543Sscottl		fflag=yes
945107543Sscottl		shift; shift
946107543Sscottl		;;
947112433Smtm	-g)
948112433Smtm		defaultLgroup="$2"
949112433Smtm		shift; shift
950112433Smtm		;;
951107543Sscottl	-G)
952107543Sscottl		defaultgroups="$2"
953107543Sscottl		shift; shift
954107543Sscottl		;;
955107543Sscottl	-h)
956107543Sscottl		show_usage
957107543Sscottl		exit 0
958107543Sscottl		;;
959107543Sscottl	-d)
960107543Sscottl		homeprefix="$2"
961107543Sscottl		shift; shift
962107543Sscottl		;;
963107543Sscottl	-m)
964107543Sscottl		case "$2" in
965107543Sscottl		[Nn][Oo])
966107543Sscottl			msgflag=
967107543Sscottl			;;
968107543Sscottl		*)
969107543Sscottl			msgflag=yes
970107543Sscottl			msgfile="$2"
971107543Sscottl			;;
972107543Sscottl		esac
973107543Sscottl		shift; shift
974107543Sscottl		;;
975175719Smtm	-M)
976175719Smtm		defaultHomePerm=$2
977175719Smtm		shift; shift
978175719Smtm		;;
979107543Sscottl	-N)
980107543Sscottl		readconfig=
981107543Sscottl		shift
982107543Sscottl		;;
983107543Sscottl	-w)
984107543Sscottl		case "$2" in
985107543Sscottl		no|none|random|yes)
986107543Sscottl			passwdtype=$2
987107543Sscottl			;;
988107543Sscottl		*)
989107543Sscottl			show_usage
990107543Sscottl			exit 1
991107543Sscottl			;;
992107543Sscottl		esac
993107543Sscottl		shift; shift
994107543Sscottl		;;
995107543Sscottl	-q)
996107543Sscottl		quietflag=yes
997107543Sscottl		shift
998107543Sscottl		;;
999107543Sscottl	-s)
1000107543Sscottl		defaultshell="`fullpath_from_shell $2`"
1001107543Sscottl		shift; shift
1002107543Sscottl		;;
1003127076Smtm	-S)
1004127076Smtm		Sflag=yes
1005127076Smtm		shift
1006127076Smtm		;;
1007107543Sscottl	-u)
1008107543Sscottl		uidstart=$2
1009107543Sscottl		shift; shift
1010107543Sscottl		;;
1011107543Sscottl	esac
1012107543Sscottldone
1013107543Sscottl
1014107543Sscottl# If the -f switch was used, get input from a file. Otherwise,
1015107543Sscottl# this is an interactive session.
1016107543Sscottl#
1017107543Sscottlif [ -n "$fflag" ]; then
1018107543Sscottl	if [ -z "$infile" ]; then
1019107543Sscottl		input_from_file
1020107543Sscottl	elif [ -n "$infile" ]; then
1021107543Sscottl		if [ -r "$infile" ]; then
1022107543Sscottl			input_from_file < $infile
1023107543Sscottl		else
1024107543Sscottl			err "File ($infile) is unreadable or does not exist."
1025107543Sscottl		fi
1026107543Sscottl	fi
1027107543Sscottlelse
1028107543Sscottl	input_interactive
1029109768Smtm	while : ; do
1030112401Smtm		if [ -z "$configflag" ]; then
1031112401Smtm			echo -n "Add another user? (yes/no): "
1032112401Smtm		else
1033112401Smtm			echo -n "Re-edit the default configuration? (yes/no): "
1034112401Smtm		fi
1035109768Smtm		read _input
1036109768Smtm		case $_input in
1037109768Smtm		[Yy][Ee][Ss]|[Yy][Ee]|[Yy])
1038109768Smtm			uidstart=`get_nextuid $uidstart`
1039109768Smtm			input_interactive
1040109768Smtm			continue
1041109768Smtm			;;
1042109768Smtm		[Nn][Oo]|[Nn])
1043109768Smtm			echo "Goodbye!"
1044109768Smtm			;;
1045109768Smtm		*)
1046109768Smtm			continue
1047109768Smtm			;;
1048109768Smtm		esac
1049109768Smtm		break
1050109768Smtm	done
1051107543Sscottlfi
1052