2002-12-03 05:41:09 +00:00
|
|
|
#!/bin/sh
|
|
|
|
#
|
2004-03-16 13:46:29 +00:00
|
|
|
# Copyright (c) 2002-2004 Michael Telahun Makonnen. All rights reserved.
|
2002-12-03 05:41:09 +00:00
|
|
|
#
|
|
|
|
# Redistribution and use in source and binary forms, with or without
|
|
|
|
# modification, are permitted provided that the following conditions
|
|
|
|
# are met:
|
|
|
|
# 1. Redistributions of source code must retain the above copyright
|
|
|
|
# notice, this list of conditions and the following disclaimer.
|
|
|
|
# 2. Redistributions in binary form must reproduce the above copyright
|
|
|
|
# notice, this list of conditions and the following disclaimer in the
|
|
|
|
# documentation and/or other materials provided with the distribution.
|
|
|
|
#
|
|
|
|
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
|
|
|
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
|
|
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
|
|
# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
|
|
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
|
|
# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
|
|
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
|
|
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
|
|
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
|
|
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
#
|
2003-06-20 16:53:36 +00:00
|
|
|
# Email: Mike Makonnen <mtm@FreeBSD.Org>
|
2002-12-03 05:41:09 +00:00
|
|
|
#
|
|
|
|
# $FreeBSD$
|
|
|
|
#
|
|
|
|
|
|
|
|
# err msg
|
|
|
|
# Display $msg on stderr, unless we're being quiet.
|
|
|
|
#
|
|
|
|
err() {
|
|
|
|
if [ -z "$quietflag" ]; then
|
|
|
|
echo 1>&2 ${THISCMD}: ERROR: $*
|
|
|
|
fi
|
|
|
|
}
|
|
|
|
|
|
|
|
# info msg
|
|
|
|
# Display $msg on stdout, unless we're being quiet.
|
|
|
|
#
|
|
|
|
info() {
|
|
|
|
if [ -z "$quietflag" ]; then
|
|
|
|
echo ${THISCMD}: INFO: $*
|
|
|
|
fi
|
|
|
|
}
|
|
|
|
|
|
|
|
# get_nextuid
|
|
|
|
# Output the value of $_uid if it is available for use. If it
|
|
|
|
# is not, output the value of the next higher uid that is available.
|
|
|
|
# If a uid is not specified, output the first available uid, as indicated
|
|
|
|
# by pw(8).
|
|
|
|
#
|
|
|
|
get_nextuid () {
|
|
|
|
_uid=$1
|
|
|
|
_nextuid=
|
|
|
|
|
|
|
|
if [ -z "$_uid" ]; then
|
|
|
|
_nextuid="`${PWCMD} usernext | cut -f1 -d:`"
|
|
|
|
else
|
|
|
|
while : ; do
|
|
|
|
${PWCMD} usershow $_uid > /dev/null 2>&1
|
|
|
|
if [ ! "$?" -eq 0 ]; then
|
|
|
|
_nextuid=$_uid
|
|
|
|
break
|
|
|
|
fi
|
|
|
|
_uid=$(($_uid + 1))
|
|
|
|
done
|
|
|
|
fi
|
|
|
|
echo $_nextuid
|
|
|
|
}
|
|
|
|
|
|
|
|
# show_usage
|
|
|
|
# Display usage information for this utility.
|
|
|
|
#
|
|
|
|
show_usage() {
|
|
|
|
echo "usage: ${THISCMD} [options]"
|
|
|
|
echo " options may include:"
|
|
|
|
echo " -C save to the configuration file only"
|
2004-03-16 13:46:29 +00:00
|
|
|
echo " -D do not attempt to create the home directory"
|
2002-12-03 05:41:09 +00:00
|
|
|
echo " -E disable this account after creation"
|
|
|
|
echo " -G additional groups to add accounts to"
|
|
|
|
echo " -L login class of the user"
|
|
|
|
echo " -N do not read configuration file"
|
2004-03-16 13:46:29 +00:00
|
|
|
echo " -S a nonexistent shell is not an error"
|
2002-12-03 05:41:09 +00:00
|
|
|
echo " -d home directory"
|
|
|
|
echo " -f file from which input will be received"
|
2003-03-23 23:06:44 +00:00
|
|
|
echo " -g default login group"
|
2002-12-03 05:41:09 +00:00
|
|
|
echo " -h display this usage message"
|
|
|
|
echo " -k path to skeleton home directory"
|
|
|
|
echo " -m user welcome message file"
|
|
|
|
echo " -q absolute minimal user feedback"
|
|
|
|
echo " -s shell"
|
|
|
|
echo " -u uid to start at"
|
|
|
|
echo " -w password type: no, none, yes or random"
|
|
|
|
}
|
|
|
|
|
|
|
|
# valid_shells
|
|
|
|
# Outputs a list of valid shells from /etc/shells. Only the
|
|
|
|
# basename of the shell is output.
|
|
|
|
#
|
|
|
|
valid_shells() {
|
|
|
|
_prefix=
|
|
|
|
cat ${ETCSHELLS} |
|
|
|
|
while read _path _junk ; do
|
|
|
|
case $_path in
|
|
|
|
\#*|'')
|
|
|
|
;;
|
|
|
|
*)
|
|
|
|
echo -n "${_prefix}`basename $_path`"
|
|
|
|
_prefix=' '
|
|
|
|
;;
|
|
|
|
esac
|
|
|
|
done
|
2003-06-20 19:23:55 +00:00
|
|
|
|
2004-03-30 19:24:56 +00:00
|
|
|
# /usr/sbin/nologin is a special case
|
2003-06-20 19:23:55 +00:00
|
|
|
[ -x "${NOLOGIN_PATH}" ] && echo -n " ${NOLOGIN}"
|
2002-12-03 05:41:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
# fullpath_from_shell shell
|
2004-06-06 17:55:55 +00:00
|
|
|
# Given $shell, which is either the full path to a shell or
|
|
|
|
# the basename component of a valid shell, get the
|
2002-12-03 05:41:09 +00:00
|
|
|
# full path to the shell from the /etc/shells file.
|
|
|
|
#
|
|
|
|
fullpath_from_shell() {
|
|
|
|
_shell=$1
|
|
|
|
[ -z "$_shell" ] && return 1
|
|
|
|
|
2004-09-23 13:09:42 +00:00
|
|
|
# /usr/sbin/nologin is a special case; it needs to be handled
|
|
|
|
# before the cat | while loop, since a 'return' from within
|
|
|
|
# a subshell will not terminate the function's execution, and
|
|
|
|
# the path to the nologin shell might be printed out twice.
|
|
|
|
#
|
|
|
|
if [ "$_shell" = "${NOLOGIN}" -o \
|
|
|
|
"$_shell" = "${NOLOGIN_PATH}" ]; then
|
|
|
|
echo ${NOLOGIN_PATH}
|
|
|
|
return 0;
|
|
|
|
fi
|
|
|
|
|
2002-12-03 05:41:09 +00:00
|
|
|
cat ${ETCSHELLS} |
|
|
|
|
while read _path _junk ; do
|
|
|
|
case "$_path" in
|
|
|
|
\#*|'')
|
|
|
|
;;
|
|
|
|
*)
|
2004-06-06 17:55:55 +00:00
|
|
|
if [ "$_path" = "$_shell" -o \
|
|
|
|
"`basename $_path`" = "$_shell" ]; then
|
2002-12-03 05:41:09 +00:00
|
|
|
echo $_path
|
|
|
|
return 0
|
|
|
|
fi
|
|
|
|
;;
|
|
|
|
esac
|
|
|
|
done
|
2003-06-20 19:23:55 +00:00
|
|
|
|
2002-12-03 05:41:09 +00:00
|
|
|
return 1
|
|
|
|
}
|
|
|
|
|
2003-06-20 19:23:55 +00:00
|
|
|
# shell_exists shell
|
|
|
|
# If the given shell is listed in ${ETCSHELLS} or it is
|
|
|
|
# the nologin shell this function will return 0.
|
|
|
|
# Otherwise, it will return 1. If shell is valid but
|
|
|
|
# the path is invalid or it is not executable it
|
|
|
|
# will emit an informational message saying so.
|
|
|
|
#
|
|
|
|
shell_exists()
|
|
|
|
{
|
|
|
|
_sh="$1"
|
|
|
|
_shellchk="${GREPCMD} '^$_sh$' ${ETCSHELLS} > /dev/null 2>&1"
|
|
|
|
|
|
|
|
if ! eval $_shellchk; then
|
|
|
|
# The nologin shell is not listed in /etc/shells.
|
|
|
|
if [ "$_sh" != "${NOLOGIN_PATH}" ]; then
|
|
|
|
err "Invalid shell ($_sh) for user $username."
|
|
|
|
return 1
|
|
|
|
fi
|
|
|
|
fi
|
|
|
|
! [ -x "$_sh" ] &&
|
2003-06-20 19:36:56 +00:00
|
|
|
info "The shell ($_sh) does not exist or is not executable."
|
2003-06-20 19:23:55 +00:00
|
|
|
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
2002-12-03 05:41:09 +00:00
|
|
|
# save_config
|
|
|
|
# Save some variables to a configuration file.
|
|
|
|
# Note: not all script variables are saved, only those that
|
|
|
|
# it makes sense to save.
|
|
|
|
#
|
|
|
|
save_config() {
|
|
|
|
echo "# Configuration file for adduser(8)." > ${ADDUSERCONF}
|
|
|
|
echo "# NOTE: only *some* variables are saved." >> ${ADDUSERCONF}
|
2003-01-23 20:07:40 +00:00
|
|
|
echo "# Last Modified on `${DATECMD}`." >> ${ADDUSERCONF}
|
2002-12-03 05:41:09 +00:00
|
|
|
echo '' >> ${ADDUSERCONF}
|
2003-03-20 16:36:03 +00:00
|
|
|
echo "defaultLgroup=$ulogingroup" >> ${ADDUSERCONF}
|
2002-12-03 05:41:09 +00:00
|
|
|
echo "defaultclass=$uclass" >> ${ADDUSERCONF}
|
|
|
|
echo "defaultgroups=$ugroups" >> ${ADDUSERCONF}
|
|
|
|
echo "passwdtype=$passwdtype" >> ${ADDUSERCONF}
|
|
|
|
echo "homeprefix=$homeprefix" >> ${ADDUSERCONF}
|
|
|
|
echo "defaultshell=$ushell" >> ${ADDUSERCONF}
|
|
|
|
echo "udotdir=$udotdir" >> ${ADDUSERCONF}
|
|
|
|
echo "msgfile=$msgfile" >> ${ADDUSERCONF}
|
|
|
|
echo "disableflag=$disableflag" >> ${ADDUSERCONF}
|
2007-04-12 08:17:56 +00:00
|
|
|
echo "uidstart=$uidstart" >> ${ADDUSERCONF}
|
2002-12-03 05:41:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
# add_user
|
|
|
|
# Add a user to the user database. If the user chose to send a welcome
|
|
|
|
# message or lock the account, do so.
|
|
|
|
#
|
|
|
|
add_user() {
|
|
|
|
|
|
|
|
# Is this a configuration run? If so, don't modify user database.
|
|
|
|
#
|
|
|
|
if [ -n "$configflag" ]; then
|
|
|
|
save_config
|
|
|
|
return
|
|
|
|
fi
|
|
|
|
|
|
|
|
_uid=
|
|
|
|
_name=
|
|
|
|
_comment=
|
|
|
|
_gecos=
|
|
|
|
_home=
|
|
|
|
_group=
|
|
|
|
_grouplist=
|
|
|
|
_shell=
|
|
|
|
_class=
|
|
|
|
_dotdir=
|
|
|
|
_expire=
|
|
|
|
_pwexpire=
|
|
|
|
_passwd=
|
|
|
|
_upasswd=
|
|
|
|
_passwdmethod=
|
|
|
|
|
2003-01-23 01:57:30 +00:00
|
|
|
_name="-n '$username'"
|
2003-02-09 18:29:09 +00:00
|
|
|
[ -n "$uuid" ] && _uid='-u "$uuid"'
|
|
|
|
[ -n "$ulogingroup" ] && _group='-g "$ulogingroup"'
|
|
|
|
[ -n "$ugroups" ] && _grouplist='-G "$ugroups"'
|
|
|
|
[ -n "$ushell" ] && _shell='-s "$ushell"'
|
|
|
|
[ -n "$uclass" ] && _class='-L "$uclass"'
|
|
|
|
[ -n "$ugecos" ] && _comment='-c "$ugecos"'
|
|
|
|
[ -n "$udotdir" ] && _dotdir='-k "$udotdir"'
|
|
|
|
[ -n "$uexpire" ] && _expire='-e "$uexpire"'
|
|
|
|
[ -n "$upwexpire" ] && _pwexpire='-p "$upwexpire"'
|
2004-03-16 13:46:29 +00:00
|
|
|
if [ -z "$Dflag" -a -n "$uhome" ]; then
|
|
|
|
# The /nonexistent home directory is special. It
|
|
|
|
# means the user has no home directory.
|
|
|
|
if [ "$uhome" = "$NOHOME" ]; then
|
|
|
|
_home='-d "$uhome"'
|
|
|
|
else
|
|
|
|
_home='-m -d "$uhome"'
|
|
|
|
fi
|
|
|
|
elif [ -n "$Dflag" -a -n "$uhome" ]; then
|
|
|
|
_home='-d "$uhome"'
|
|
|
|
fi
|
2002-12-03 05:41:09 +00:00
|
|
|
case $passwdtype in
|
|
|
|
no)
|
|
|
|
_passwdmethod="-w no"
|
|
|
|
_passwd="-h -"
|
|
|
|
;;
|
|
|
|
yes)
|
2003-02-09 18:29:09 +00:00
|
|
|
# Note on processing the password: The outer double quotes
|
|
|
|
# make literal everything except ` and \ and $.
|
|
|
|
# The outer single quotes make literal ` and $.
|
|
|
|
# We can ensure the \ isn't treated specially by specifying
|
|
|
|
# the -r switch to the read command used to obtain the input.
|
|
|
|
#
|
2002-12-03 05:41:09 +00:00
|
|
|
_passwdmethod="-w yes"
|
|
|
|
_passwd="-h 0"
|
2003-02-09 18:29:09 +00:00
|
|
|
_upasswd='echo "$upass" |'
|
2002-12-03 05:41:09 +00:00
|
|
|
;;
|
|
|
|
none)
|
|
|
|
_passwdmethod="-w none"
|
|
|
|
;;
|
|
|
|
random)
|
|
|
|
_passwdmethod="-w random"
|
|
|
|
;;
|
|
|
|
esac
|
|
|
|
|
|
|
|
_pwcmd="$_upasswd ${PWCMD} useradd $_uid $_name $_group $_grouplist $_comment"
|
|
|
|
_pwcmd="$_pwcmd $_shell $_class $_home $_dotdir $_passwdmethod $_passwd"
|
|
|
|
_pwcmd="$_pwcmd $_expire $_pwexpire"
|
|
|
|
|
|
|
|
if ! _output=`eval $_pwcmd` ; then
|
|
|
|
err "There was an error adding user ($username)."
|
|
|
|
return 1
|
|
|
|
else
|
|
|
|
info "Successfully added ($username) to the user database."
|
|
|
|
if [ "random" = "$passwdtype" ]; then
|
|
|
|
randompass="$_output"
|
|
|
|
info "Password for ($username) is: $randompass"
|
|
|
|
fi
|
|
|
|
fi
|
|
|
|
|
|
|
|
if [ -n "$disableflag" ]; then
|
|
|
|
if ${PWCMD} lock $username ; then
|
|
|
|
info "Account ($username) is locked."
|
|
|
|
else
|
|
|
|
info "Account ($username) could NOT be locked."
|
|
|
|
fi
|
|
|
|
fi
|
|
|
|
|
|
|
|
_line=
|
|
|
|
_owner=
|
|
|
|
_perms=
|
|
|
|
if [ -n "$msgflag" ]; then
|
|
|
|
[ -r "$msgfile" ] && {
|
|
|
|
# We're evaluating the contents of an external file.
|
|
|
|
# Let's not open ourselves up for attack. _perms will
|
|
|
|
# be empty if it's writeable only by the owner. _owner
|
|
|
|
# will *NOT* be empty if the file is owned by root.
|
|
|
|
#
|
|
|
|
_dir="`dirname $msgfile`"
|
|
|
|
_file="`basename $msgfile`"
|
|
|
|
_perms=`/usr/bin/find $_dir -name $_file -perm +07022 -prune`
|
|
|
|
_owner=`/usr/bin/find $_dir -name $_file -user 0 -prune`
|
|
|
|
if [ -z "$_owner" -o -n "$_perms" ]; then
|
|
|
|
err "The message file ($msgfile) may be writeable only by root."
|
|
|
|
return 1
|
|
|
|
fi
|
|
|
|
cat "$msgfile" |
|
|
|
|
while read _line ; do
|
|
|
|
eval echo "$_line"
|
|
|
|
done | ${MAILCMD} -s"Welcome" ${username}
|
|
|
|
info "Sent welcome message to ($username)."
|
|
|
|
}
|
|
|
|
fi
|
|
|
|
}
|
|
|
|
|
|
|
|
# get_user
|
|
|
|
# Reads username of the account from standard input or from a global
|
|
|
|
# variable containing an account line from a file. The username is
|
|
|
|
# required. If this is an interactive session it will prompt in
|
|
|
|
# a loop until a username is entered. If it is batch processing from
|
|
|
|
# a file it will output an error message and return to the caller.
|
|
|
|
#
|
|
|
|
get_user() {
|
|
|
|
_input=
|
|
|
|
|
|
|
|
# No need to take down user names if this is a configuration saving run.
|
|
|
|
[ -n "$configflag" ] && return
|
|
|
|
|
|
|
|
while : ; do
|
|
|
|
if [ -z "$fflag" ]; then
|
|
|
|
echo -n "Username: "
|
|
|
|
read _input
|
|
|
|
else
|
|
|
|
_input="`echo "$fileline" | cut -f1 -d:`"
|
|
|
|
fi
|
|
|
|
|
2007-03-26 22:22:10 +00:00
|
|
|
# There *must* be a username, and it must not exist. If
|
|
|
|
# this is an interactive session give the user an
|
|
|
|
# opportunity to retry.
|
2002-12-03 05:41:09 +00:00
|
|
|
#
|
|
|
|
if [ -z "$_input" ]; then
|
|
|
|
err "You must enter a username!"
|
|
|
|
[ -z "$fflag" ] && continue
|
|
|
|
fi
|
2007-03-26 22:22:10 +00:00
|
|
|
${PWCMD} usershow $_input > /dev/null 2>&1
|
|
|
|
if [ "$?" -eq 0 ]; then
|
|
|
|
err "User exists!"
|
|
|
|
[ -z "$fflag" ] && continue
|
|
|
|
fi
|
2002-12-03 05:41:09 +00:00
|
|
|
break
|
|
|
|
done
|
|
|
|
username="$_input"
|
|
|
|
}
|
|
|
|
|
|
|
|
# get_gecos
|
|
|
|
# Reads extra information about the user. Can be used both in interactive
|
|
|
|
# and batch (from file) mode.
|
|
|
|
#
|
|
|
|
get_gecos() {
|
|
|
|
_input=
|
|
|
|
|
|
|
|
# No need to take down additional user information for a configuration run.
|
|
|
|
[ -n "$configflag" ] && return
|
|
|
|
|
|
|
|
if [ -z "$fflag" ]; then
|
|
|
|
echo -n "Full name: "
|
|
|
|
read _input
|
|
|
|
else
|
|
|
|
_input="`echo "$fileline" | cut -f7 -d:`"
|
|
|
|
fi
|
|
|
|
ugecos="$_input"
|
|
|
|
}
|
|
|
|
|
|
|
|
# get_shell
|
|
|
|
# Get the account's shell. Works in interactive and batch mode. It
|
2004-06-06 17:55:55 +00:00
|
|
|
# accepts either the base name of the shell or the full path.
|
2002-12-03 05:41:09 +00:00
|
|
|
# If an invalid shell is entered it will simply use the default shell.
|
|
|
|
#
|
|
|
|
get_shell() {
|
|
|
|
_input=
|
|
|
|
_fullpath=
|
|
|
|
ushell="$defaultshell"
|
|
|
|
|
|
|
|
# Make sure the current value of the shell is a valid one
|
2004-03-16 13:46:29 +00:00
|
|
|
if [ -z "$Sflag" ]; then
|
|
|
|
if ! shell_exists $ushell ; then
|
|
|
|
info "Using default shell ${defaultshell}."
|
|
|
|
ushell="$defaultshell"
|
|
|
|
fi
|
2003-06-20 19:23:55 +00:00
|
|
|
fi
|
2002-12-03 05:41:09 +00:00
|
|
|
|
|
|
|
if [ -z "$fflag" ]; then
|
|
|
|
echo -n "Shell ($shells) [`basename $ushell`]: "
|
|
|
|
read _input
|
|
|
|
else
|
|
|
|
_input="`echo "$fileline" | cut -f9 -d:`"
|
|
|
|
fi
|
|
|
|
if [ -n "$_input" ]; then
|
2004-03-16 13:46:29 +00:00
|
|
|
if [ -n "$Sflag" ]; then
|
|
|
|
ushell="$_input"
|
2002-12-03 05:41:09 +00:00
|
|
|
else
|
2004-03-16 13:46:29 +00:00
|
|
|
_fullpath=`fullpath_from_shell $_input`
|
|
|
|
if [ -n "$_fullpath" ]; then
|
|
|
|
ushell="$_fullpath"
|
|
|
|
else
|
|
|
|
err "Invalid shell ($_input) for user $username."
|
|
|
|
info "Using default shell ${defaultshell}."
|
|
|
|
ushell="$defaultshell"
|
|
|
|
fi
|
2002-12-03 05:41:09 +00:00
|
|
|
fi
|
|
|
|
fi
|
|
|
|
}
|
|
|
|
|
|
|
|
# get_homedir
|
|
|
|
# Reads the account's home directory. Used both with interactive input
|
|
|
|
# and batch input.
|
|
|
|
#
|
|
|
|
get_homedir() {
|
|
|
|
_input=
|
|
|
|
if [ -z "$fflag" ]; then
|
|
|
|
echo -n "Home directory [${homeprefix}/${username}]: "
|
|
|
|
read _input
|
|
|
|
else
|
|
|
|
_input="`echo "$fileline" | cut -f8 -d:`"
|
|
|
|
fi
|
|
|
|
|
|
|
|
if [ -n "$_input" ]; then
|
|
|
|
uhome="$_input"
|
|
|
|
# if this is a configuration run, then user input is the home
|
|
|
|
# directory prefix. Otherwise it is understood to
|
|
|
|
# be $prefix/$user
|
|
|
|
#
|
|
|
|
[ -z "$configflag" ] && homeprefix="`dirname $uhome`" || homeprefix="$uhome"
|
|
|
|
else
|
|
|
|
uhome="${homeprefix}/${username}"
|
|
|
|
fi
|
|
|
|
}
|
|
|
|
|
|
|
|
# get_uid
|
|
|
|
# Reads a numeric userid in an interactive or batch session. Automatically
|
|
|
|
# allocates one if it is not specified.
|
|
|
|
#
|
|
|
|
get_uid() {
|
2007-04-12 08:17:56 +00:00
|
|
|
uuid=${uidstart}
|
2002-12-03 05:41:09 +00:00
|
|
|
_input=
|
|
|
|
_prompt=
|
|
|
|
|
|
|
|
if [ -n "$uuid" ]; then
|
|
|
|
_prompt="Uid [$uuid]: "
|
|
|
|
else
|
|
|
|
_prompt="Uid (Leave empty for default): "
|
|
|
|
fi
|
|
|
|
if [ -z "$fflag" ]; then
|
2003-01-20 17:55:31 +00:00
|
|
|
echo -n "$_prompt"
|
2002-12-03 05:41:09 +00:00
|
|
|
read _input
|
|
|
|
else
|
|
|
|
_input="`echo "$fileline" | cut -f2 -d:`"
|
|
|
|
fi
|
|
|
|
|
|
|
|
[ -n "$_input" ] && uuid=$_input
|
|
|
|
uuid=`get_nextuid $uuid`
|
|
|
|
uidstart=$uuid
|
|
|
|
}
|
|
|
|
|
|
|
|
# get_class
|
|
|
|
# Reads login class of account. Can be used in interactive or batch mode.
|
|
|
|
#
|
|
|
|
get_class() {
|
|
|
|
uclass="$defaultclass"
|
|
|
|
_input=
|
|
|
|
_class=${uclass:-"default"}
|
|
|
|
|
|
|
|
if [ -z "$fflag" ]; then
|
|
|
|
echo -n "Login class [$_class]: "
|
|
|
|
read _input
|
|
|
|
else
|
|
|
|
_input="`echo "$fileline" | cut -f4 -d:`"
|
|
|
|
fi
|
|
|
|
|
|
|
|
[ -n "$_input" ] && uclass="$_input"
|
|
|
|
}
|
|
|
|
|
|
|
|
# get_logingroup
|
|
|
|
# Reads user's login group. Can be used in both interactive and batch
|
|
|
|
# modes. The specified value can be a group name or its numeric id.
|
2003-03-20 16:36:03 +00:00
|
|
|
# This routine leaves the field blank if nothing is provided and
|
|
|
|
# a default login group has not been set. The pw(8) command
|
|
|
|
# will then provide a login group with the same name as the username.
|
2002-12-03 05:41:09 +00:00
|
|
|
#
|
|
|
|
get_logingroup() {
|
2003-03-20 16:36:03 +00:00
|
|
|
ulogingroup="$defaultLgroup"
|
2002-12-03 05:41:09 +00:00
|
|
|
_input=
|
|
|
|
|
|
|
|
if [ -z "$fflag" ]; then
|
2003-03-20 16:36:03 +00:00
|
|
|
echo -n "Login group [${ulogingroup:-$username}]: "
|
2002-12-03 05:41:09 +00:00
|
|
|
read _input
|
|
|
|
else
|
|
|
|
_input="`echo "$fileline" | cut -f3 -d:`"
|
|
|
|
fi
|
|
|
|
|
|
|
|
# Pw(8) will use the username as login group if it's left empty
|
2003-03-20 16:36:03 +00:00
|
|
|
[ -n "$_input" ] && ulogingroup="$_input"
|
2002-12-03 05:41:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
# get_groups
|
|
|
|
# Read additional groups for the user. It can be used in both interactive
|
|
|
|
# and batch modes.
|
|
|
|
#
|
|
|
|
get_groups() {
|
|
|
|
ugroups="$defaultgroups"
|
|
|
|
_input=
|
|
|
|
_group=${ulogingroup:-"${username}"}
|
|
|
|
|
|
|
|
if [ -z "$configflag" ]; then
|
|
|
|
[ -z "$fflag" ] && echo -n "Login group is $_group. Invite $username"
|
|
|
|
[ -z "$fflag" ] && echo -n " into other groups? [$ugroups]: "
|
|
|
|
else
|
|
|
|
[ -z "$fflag" ] && echo -n "Enter additional groups [$ugroups]: "
|
|
|
|
fi
|
|
|
|
read _input
|
|
|
|
|
|
|
|
[ -n "$_input" ] && ugroups="$_input"
|
|
|
|
}
|
|
|
|
|
|
|
|
# get_expire_dates
|
|
|
|
# Read expiry information for the account and also for the password. This
|
|
|
|
# routine is used only from batch processing mode.
|
|
|
|
#
|
|
|
|
get_expire_dates() {
|
|
|
|
upwexpire="`echo "$fileline" | cut -f5 -d:`"
|
|
|
|
uexpire="`echo "$fileline" | cut -f6 -d:`"
|
|
|
|
}
|
|
|
|
|
|
|
|
# get_password
|
|
|
|
# Read the password in batch processing mode. The password field matters
|
|
|
|
# only when the password type is "yes" or "random". If the field is empty and the
|
|
|
|
# password type is "yes", then it assumes the account has an empty passsword
|
|
|
|
# and changes the password type accordingly. If the password type is "random"
|
|
|
|
# and the password field is NOT empty, then it assumes the account will NOT
|
|
|
|
# have a random password and set passwdtype to "yes."
|
|
|
|
#
|
|
|
|
get_password() {
|
|
|
|
# We may temporarily change a password type. Make sure it's changed
|
|
|
|
# back to whatever it was before we process the next account.
|
|
|
|
#
|
|
|
|
[ -n "$savedpwtype" ] && {
|
|
|
|
passwdtype=$savedpwtype
|
|
|
|
savedpwtype=
|
|
|
|
}
|
|
|
|
|
|
|
|
# There may be a ':' in the password
|
|
|
|
upass=${fileline#*:*:*:*:*:*:*:*:*:}
|
|
|
|
|
|
|
|
if [ -z "$upass" ]; then
|
|
|
|
case $passwdtype in
|
|
|
|
yes)
|
|
|
|
# if it's empty, assume an empty password
|
|
|
|
passwdtype=none
|
|
|
|
savedpwtype=yes
|
|
|
|
;;
|
|
|
|
esac
|
|
|
|
else
|
|
|
|
case $passwdtype in
|
|
|
|
random)
|
|
|
|
passwdtype=yes
|
|
|
|
savedpwtype=random
|
|
|
|
;;
|
|
|
|
esac
|
|
|
|
fi
|
|
|
|
}
|
|
|
|
|
|
|
|
# input_from_file
|
|
|
|
# Reads a line of account information from standard input and
|
|
|
|
# adds it to the user database.
|
|
|
|
#
|
|
|
|
input_from_file() {
|
|
|
|
_field=
|
|
|
|
|
2003-02-09 18:29:09 +00:00
|
|
|
while read -r fileline ; do
|
2002-12-03 05:41:09 +00:00
|
|
|
case "$fileline" in
|
|
|
|
\#*|'')
|
|
|
|
;;
|
2007-04-12 07:38:26 +00:00
|
|
|
*)
|
|
|
|
get_user || continue
|
|
|
|
get_gecos
|
|
|
|
get_uid
|
|
|
|
get_logingroup
|
|
|
|
get_class
|
|
|
|
get_shell
|
|
|
|
get_homedir
|
|
|
|
get_password
|
|
|
|
get_expire_dates
|
2007-10-20 00:45:31 +00:00
|
|
|
ugroups="$defaultgroups"
|
2007-04-12 07:38:26 +00:00
|
|
|
|
|
|
|
add_user
|
|
|
|
;;
|
2002-12-03 05:41:09 +00:00
|
|
|
esac
|
|
|
|
done
|
|
|
|
}
|
|
|
|
|
|
|
|
# input_interactive
|
|
|
|
# Prompts for user information interactively, and commits to
|
|
|
|
# the user database.
|
|
|
|
#
|
|
|
|
input_interactive() {
|
|
|
|
|
|
|
|
_disable=
|
|
|
|
_pass=
|
|
|
|
_passconfirm=
|
|
|
|
_random="no"
|
|
|
|
_emptypass="no"
|
|
|
|
_usepass="yes"
|
2003-03-19 08:07:15 +00:00
|
|
|
_logingroup_ok="no"
|
|
|
|
_groups_ok="no"
|
2002-12-03 05:41:09 +00:00
|
|
|
case $passwdtype in
|
|
|
|
none)
|
|
|
|
_emptypass="yes"
|
|
|
|
_usepass="yes"
|
|
|
|
;;
|
|
|
|
no)
|
|
|
|
_usepass="no"
|
|
|
|
;;
|
|
|
|
random)
|
|
|
|
_random="yes"
|
|
|
|
;;
|
|
|
|
esac
|
|
|
|
|
|
|
|
get_user
|
|
|
|
get_gecos
|
|
|
|
get_uid
|
2003-02-08 08:04:53 +00:00
|
|
|
|
|
|
|
# The case where group = user is handled elsewhere, so
|
|
|
|
# validate any other groups the user is invited to.
|
|
|
|
until [ "$_logingroup_ok" = yes ]; do
|
|
|
|
get_logingroup
|
|
|
|
_logingroup_ok=yes
|
|
|
|
if [ -n "$ulogingroup" -a "$username" != "$ulogingroup" ]; then
|
|
|
|
if ! ${PWCMD} show group $ulogingroup > /dev/null 2>&1; then
|
|
|
|
echo "Group $ulogingroup does not exist!"
|
|
|
|
_logingroup_ok=no
|
|
|
|
fi
|
|
|
|
fi
|
|
|
|
done
|
|
|
|
until [ "$_groups_ok" = yes ]; do
|
|
|
|
get_groups
|
|
|
|
_groups_ok=yes
|
|
|
|
for i in $ugroups; do
|
|
|
|
if [ "$username" != "$i" ]; then
|
|
|
|
if ! ${PWCMD} show group $i > /dev/null 2>&1; then
|
|
|
|
echo "Group $i does not exist!"
|
|
|
|
_groups_ok=no
|
|
|
|
fi
|
|
|
|
fi
|
|
|
|
done
|
|
|
|
done
|
|
|
|
|
2002-12-03 05:41:09 +00:00
|
|
|
get_class
|
|
|
|
get_shell
|
|
|
|
get_homedir
|
|
|
|
|
|
|
|
while : ; do
|
|
|
|
echo -n "Use password-based authentication? [$_usepass]: "
|
|
|
|
read _input
|
|
|
|
[ -z "$_input" ] && _input=$_usepass
|
|
|
|
case $_input in
|
|
|
|
[Nn][Oo]|[Nn])
|
|
|
|
passwdtype="no"
|
|
|
|
;;
|
|
|
|
[Yy][Ee][Ss]|[Yy][Ee]|[Yy])
|
|
|
|
while : ; do
|
|
|
|
echo -n "Use an empty password? (yes/no) [$_emptypass]: "
|
|
|
|
read _input
|
|
|
|
[ -n "$_input" ] && _emptypass=$_input
|
|
|
|
case $_emptypass in
|
|
|
|
[Nn][Oo]|[Nn])
|
|
|
|
echo -n "Use a random password? (yes/no) [$_random]: "
|
|
|
|
read _input
|
|
|
|
[ -n "$_input" ] && _random="$_input"
|
|
|
|
case $_random in
|
|
|
|
[Yy][Ee][Ss]|[Yy][Ee]|[Yy])
|
|
|
|
passwdtype="random"
|
|
|
|
break
|
|
|
|
;;
|
|
|
|
esac
|
|
|
|
passwdtype="yes"
|
2003-03-19 08:07:15 +00:00
|
|
|
[ -n "$configflag" ] && break
|
2002-12-03 05:41:09 +00:00
|
|
|
trap 'stty echo; exit' 0 1 2 3 15
|
|
|
|
stty -echo
|
|
|
|
echo -n "Enter password: "
|
2003-02-09 18:29:09 +00:00
|
|
|
read -r upass
|
2002-12-03 05:41:09 +00:00
|
|
|
echo''
|
|
|
|
echo -n "Enter password again: "
|
2003-06-20 16:52:06 +00:00
|
|
|
read -r _passconfirm
|
2002-12-03 05:41:09 +00:00
|
|
|
echo ''
|
|
|
|
stty echo
|
|
|
|
# if user entered a blank password
|
|
|
|
# explicitly ask again.
|
|
|
|
[ -z "$upass" -a -z "$_passconfirm" ] \
|
|
|
|
&& continue
|
|
|
|
;;
|
|
|
|
[Yy][Ee][Ss]|[Yy][Ee]|[Yy])
|
|
|
|
passwdtype="none"
|
|
|
|
break;
|
|
|
|
;;
|
|
|
|
*)
|
|
|
|
# invalid answer; repeat the loop
|
|
|
|
continue
|
|
|
|
;;
|
|
|
|
esac
|
|
|
|
if [ "$upass" != "$_passconfirm" ]; then
|
|
|
|
echo "Passwords did not match!"
|
|
|
|
continue
|
|
|
|
fi
|
|
|
|
break
|
|
|
|
done
|
|
|
|
;;
|
|
|
|
*)
|
|
|
|
# invalid answer; repeat loop
|
|
|
|
continue
|
|
|
|
;;
|
|
|
|
esac
|
|
|
|
break;
|
|
|
|
done
|
|
|
|
_disable=${disableflag:-"no"}
|
|
|
|
while : ; do
|
|
|
|
echo -n "Lock out the account after creation? [$_disable]: "
|
|
|
|
read _input
|
|
|
|
[ -z "$_input" ] && _input=$_disable
|
|
|
|
case $_input in
|
|
|
|
[Nn][Oo]|[Nn])
|
|
|
|
disableflag=
|
|
|
|
;;
|
|
|
|
[Yy][Ee][Ss]|[Yy][Ee]|[Yy])
|
|
|
|
disableflag=yes
|
|
|
|
;;
|
|
|
|
*)
|
|
|
|
# invalid answer; repeat loop
|
|
|
|
continue
|
|
|
|
;;
|
|
|
|
esac
|
|
|
|
break
|
|
|
|
done
|
|
|
|
|
|
|
|
# Display the information we have so far and prompt to
|
|
|
|
# commit it.
|
|
|
|
#
|
|
|
|
_disable=${disableflag:-"no"}
|
|
|
|
[ -z "$configflag" ] && printf "%-10s : %s\n" Username $username
|
|
|
|
case $passwdtype in
|
|
|
|
yes)
|
|
|
|
_pass='*****'
|
|
|
|
;;
|
|
|
|
no)
|
|
|
|
_pass='<disabled>'
|
|
|
|
;;
|
|
|
|
none)
|
|
|
|
_pass='<blank>'
|
|
|
|
;;
|
|
|
|
random)
|
|
|
|
_pass='<random>'
|
|
|
|
;;
|
|
|
|
esac
|
2003-01-23 20:07:40 +00:00
|
|
|
[ -z "$configflag" ] && printf "%-10s : %s\n" "Password" "$_pass"
|
|
|
|
[ -n "$configflag" ] && printf "%-10s : %s\n" "Pass Type" "$passwdtype"
|
2002-12-03 05:41:09 +00:00
|
|
|
[ -z "$configflag" ] && printf "%-10s : %s\n" "Full Name" "$ugecos"
|
|
|
|
[ -z "$configflag" ] && printf "%-10s : %s\n" "Uid" "$uuid"
|
|
|
|
printf "%-10s : %s\n" "Class" "$uclass"
|
2003-03-20 16:36:03 +00:00
|
|
|
printf "%-10s : %s %s\n" "Groups" "${ulogingroup:-$username}" "$ugroups"
|
2002-12-03 05:41:09 +00:00
|
|
|
printf "%-10s : %s\n" "Home" "$uhome"
|
|
|
|
printf "%-10s : %s\n" "Shell" "$ushell"
|
|
|
|
printf "%-10s : %s\n" "Locked" "$_disable"
|
|
|
|
while : ; do
|
|
|
|
echo -n "OK? (yes/no): "
|
|
|
|
read _input
|
|
|
|
case $_input in
|
|
|
|
[Nn][Oo]|[Nn])
|
|
|
|
return 1
|
|
|
|
;;
|
|
|
|
[Yy][Ee][Ss]|[Yy][Ee]|[Yy])
|
|
|
|
add_user
|
|
|
|
;;
|
|
|
|
*)
|
|
|
|
continue
|
|
|
|
;;
|
|
|
|
esac
|
|
|
|
break
|
|
|
|
done
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
2005-04-28 12:44:02 +00:00
|
|
|
#### END SUBROUTINE DEFINITION ####
|
2002-12-03 05:41:09 +00:00
|
|
|
|
|
|
|
THISCMD=`/usr/bin/basename $0`
|
|
|
|
DEFAULTSHELL=/bin/sh
|
|
|
|
ADDUSERCONF="${ADDUSERCONF:-/etc/adduser.conf}"
|
|
|
|
PWCMD="${PWCMD:-/usr/sbin/pw}"
|
|
|
|
MAILCMD="${MAILCMD:-mail}"
|
|
|
|
ETCSHELLS="${ETCSHELLS:-/etc/shells}"
|
2004-03-16 13:46:29 +00:00
|
|
|
NOHOME="/nonexistent"
|
2003-06-20 19:23:55 +00:00
|
|
|
NOLOGIN="nologin"
|
2004-03-30 19:24:56 +00:00
|
|
|
NOLOGIN_PATH="/usr/sbin/nologin"
|
2003-01-23 20:07:40 +00:00
|
|
|
GREPCMD="/usr/bin/grep"
|
|
|
|
DATECMD="/bin/date"
|
2002-12-03 05:41:09 +00:00
|
|
|
|
|
|
|
# Set default values
|
|
|
|
#
|
|
|
|
username=
|
|
|
|
uuid=
|
|
|
|
uidstart=
|
|
|
|
ugecos=
|
|
|
|
ulogingroup=
|
|
|
|
uclass=
|
|
|
|
uhome=
|
|
|
|
upass=
|
|
|
|
ushell=
|
|
|
|
udotdir=/usr/share/skel
|
|
|
|
ugroups=
|
|
|
|
uexpire=
|
|
|
|
upwexpire=
|
|
|
|
shells="`valid_shells`"
|
|
|
|
passwdtype="yes"
|
|
|
|
msgfile=/etc/adduser.msg
|
|
|
|
msgflag=
|
|
|
|
quietflag=
|
|
|
|
configflag=
|
|
|
|
fflag=
|
|
|
|
infile=
|
|
|
|
disableflag=
|
2004-03-16 13:46:29 +00:00
|
|
|
Dflag=
|
|
|
|
Sflag=
|
2002-12-03 05:41:09 +00:00
|
|
|
readconfig="yes"
|
|
|
|
homeprefix="/home"
|
|
|
|
randompass=
|
|
|
|
fileline=
|
|
|
|
savedpwtype=
|
|
|
|
defaultclass=
|
2003-03-20 16:36:03 +00:00
|
|
|
defaultLgroup=
|
2003-06-24 15:20:43 +00:00
|
|
|
defaultgroups=
|
2002-12-03 05:41:09 +00:00
|
|
|
defaultshell="${DEFAULTSHELL}"
|
|
|
|
|
|
|
|
# Make sure the user running this program is root. This isn't a security
|
|
|
|
# measure as much as it is a usefull method of reminding the user to
|
|
|
|
# 'su -' before he/she wastes time entering data that won't be saved.
|
|
|
|
#
|
|
|
|
procowner=${procowner:-`/usr/bin/id -u`}
|
|
|
|
if [ "$procowner" != "0" ]; then
|
|
|
|
err 'you must be the super-user (uid 0) to use this utility.'
|
|
|
|
exit 1
|
|
|
|
fi
|
|
|
|
|
|
|
|
# Overide from our conf file
|
|
|
|
# Quickly go through the commandline line to see if we should read
|
|
|
|
# from our configuration file. The actual parsing of the commandline
|
|
|
|
# arguments happens after we read in our configuration file (commandline
|
|
|
|
# should override configuration file).
|
|
|
|
#
|
|
|
|
for _i in $* ; do
|
|
|
|
if [ "$_i" = "-N" ]; then
|
|
|
|
readconfig=
|
|
|
|
break;
|
|
|
|
fi
|
|
|
|
done
|
|
|
|
if [ -n "$readconfig" ]; then
|
|
|
|
# On a long-lived system, the first time this script is run it
|
|
|
|
# will barf upon reading the configuration file for its perl predecessor.
|
|
|
|
if ( . ${ADDUSERCONF} > /dev/null 2>&1 ); then
|
|
|
|
[ -r ${ADDUSERCONF} ] && . ${ADDUSERCONF} > /dev/null 2>&1
|
|
|
|
fi
|
|
|
|
fi
|
|
|
|
|
|
|
|
# Proccess command-line options
|
|
|
|
#
|
|
|
|
for _switch ; do
|
|
|
|
case $_switch in
|
|
|
|
-L)
|
|
|
|
defaultclass="$2"
|
|
|
|
shift; shift
|
|
|
|
;;
|
|
|
|
-C)
|
|
|
|
configflag=yes
|
|
|
|
shift
|
|
|
|
;;
|
2004-03-16 13:46:29 +00:00
|
|
|
-D)
|
|
|
|
Dflag=yes
|
|
|
|
shift
|
|
|
|
;;
|
2002-12-03 05:41:09 +00:00
|
|
|
-E)
|
|
|
|
disableflag=yes
|
|
|
|
shift
|
|
|
|
;;
|
|
|
|
-k)
|
|
|
|
udotdir="$2"
|
|
|
|
shift; shift
|
|
|
|
;;
|
|
|
|
-f)
|
|
|
|
[ "$2" != "-" ] && infile="$2"
|
|
|
|
fflag=yes
|
|
|
|
shift; shift
|
|
|
|
;;
|
2003-03-20 16:36:03 +00:00
|
|
|
-g)
|
|
|
|
defaultLgroup="$2"
|
|
|
|
shift; shift
|
|
|
|
;;
|
2002-12-03 05:41:09 +00:00
|
|
|
-G)
|
|
|
|
defaultgroups="$2"
|
|
|
|
shift; shift
|
|
|
|
;;
|
|
|
|
-h)
|
|
|
|
show_usage
|
|
|
|
exit 0
|
|
|
|
;;
|
|
|
|
-d)
|
|
|
|
homeprefix="$2"
|
|
|
|
shift; shift
|
|
|
|
;;
|
|
|
|
-m)
|
|
|
|
case "$2" in
|
|
|
|
[Nn][Oo])
|
|
|
|
msgflag=
|
|
|
|
;;
|
|
|
|
*)
|
|
|
|
msgflag=yes
|
|
|
|
msgfile="$2"
|
|
|
|
;;
|
|
|
|
esac
|
|
|
|
shift; shift
|
|
|
|
;;
|
|
|
|
-N)
|
|
|
|
readconfig=
|
|
|
|
shift
|
|
|
|
;;
|
|
|
|
-w)
|
|
|
|
case "$2" in
|
|
|
|
no|none|random|yes)
|
|
|
|
passwdtype=$2
|
|
|
|
;;
|
|
|
|
*)
|
|
|
|
show_usage
|
|
|
|
exit 1
|
|
|
|
;;
|
|
|
|
esac
|
|
|
|
shift; shift
|
|
|
|
;;
|
|
|
|
-q)
|
|
|
|
quietflag=yes
|
|
|
|
shift
|
|
|
|
;;
|
|
|
|
-s)
|
|
|
|
defaultshell="`fullpath_from_shell $2`"
|
|
|
|
shift; shift
|
|
|
|
;;
|
2004-03-16 13:46:29 +00:00
|
|
|
-S)
|
|
|
|
Sflag=yes
|
|
|
|
shift
|
|
|
|
;;
|
2002-12-03 05:41:09 +00:00
|
|
|
-u)
|
|
|
|
uidstart=$2
|
|
|
|
shift; shift
|
|
|
|
;;
|
|
|
|
esac
|
|
|
|
done
|
|
|
|
|
|
|
|
# If the -f switch was used, get input from a file. Otherwise,
|
|
|
|
# this is an interactive session.
|
|
|
|
#
|
|
|
|
if [ -n "$fflag" ]; then
|
|
|
|
if [ -z "$infile" ]; then
|
|
|
|
input_from_file
|
|
|
|
elif [ -n "$infile" ]; then
|
|
|
|
if [ -r "$infile" ]; then
|
|
|
|
input_from_file < $infile
|
|
|
|
else
|
|
|
|
err "File ($infile) is unreadable or does not exist."
|
|
|
|
fi
|
|
|
|
fi
|
|
|
|
else
|
|
|
|
input_interactive
|
2003-01-24 00:24:26 +00:00
|
|
|
while : ; do
|
2003-03-19 08:07:15 +00:00
|
|
|
if [ -z "$configflag" ]; then
|
|
|
|
echo -n "Add another user? (yes/no): "
|
|
|
|
else
|
|
|
|
echo -n "Re-edit the default configuration? (yes/no): "
|
|
|
|
fi
|
2003-01-24 00:24:26 +00:00
|
|
|
read _input
|
|
|
|
case $_input in
|
|
|
|
[Yy][Ee][Ss]|[Yy][Ee]|[Yy])
|
|
|
|
uidstart=`get_nextuid $uidstart`
|
|
|
|
input_interactive
|
|
|
|
continue
|
|
|
|
;;
|
|
|
|
[Nn][Oo]|[Nn])
|
|
|
|
echo "Goodbye!"
|
|
|
|
;;
|
|
|
|
*)
|
|
|
|
continue
|
|
|
|
;;
|
|
|
|
esac
|
|
|
|
break
|
|
|
|
done
|
2002-12-03 05:41:09 +00:00
|
|
|
fi
|