#!/bin/sh
#
-# Copyright (c) 2002 Michael Telahun Makonnen. All rights reserved.
+# Copyright (c) 2002-2004 Michael Telahun Makonnen. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# 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.
-# 3. The name of the author may not be used to endorse or promote products
-# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
-# Email: Mike Makonnen <mtm@identd.net>
+# Email: Mike Makonnen <mtm@FreeBSD.Org>
#
# $FreeBSD$
#
# err msg
-# Display $msg on stderr, unless we're being quiet.
-#
+# Display $msg on stderr, unless we're being quiet.
+#
err() {
if [ -z "$quietflag" ]; then
- echo 1>&2 ${THISCMD}: ERROR: $*
+ echo 1>&2 ${THISCMD}: ERROR: $*
fi
}
# info msg
-# Display $msg on stdout, unless we're being quiet.
-#
+# Display $msg on stdout, unless we're being quiet.
+#
info() {
if [ -z "$quietflag" ]; then
- echo ${THISCMD}: INFO: $*
+ echo ${THISCMD}: INFO: $*
fi
}
# 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=
echo "usage: ${THISCMD} [options]"
echo " options may include:"
echo " -C save to the configuration file only"
+ echo " -D do not attempt to create the home directory"
echo " -E disable this account after creation"
echo " -G additional groups to add accounts to"
echo " -L login class of the user"
+ echo " -M file permission for home directory"
echo " -N do not read configuration file"
+ echo " -S a nonexistent shell is not an error"
echo " -d home directory"
echo " -f file from which input will be received"
+ echo " -g default login group"
echo " -h display this usage message"
echo " -k path to skeleton home directory"
echo " -m user welcome message file"
;;
esac
done
+
+ # /usr/sbin/nologin is a special case
+ [ -x "${NOLOGIN_PATH}" ] && echo -n " ${NOLOGIN}"
}
# fullpath_from_shell shell
-# Given $shell, the basename component of a valid shell, get the
+# Given $shell, which is either the full path to a shell or
+# the basename component of a valid shell, get the
# full path to the shell from the /etc/shells file.
#
fullpath_from_shell() {
_shell=$1
[ -z "$_shell" ] && return 1
+ # /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
+
cat ${ETCSHELLS} |
while read _path _junk ; do
case "$_path" in
\#*|'')
;;
*)
- if [ "`basename $_path`" = "$_shell" ]; then
+ if [ "$_path" = "$_shell" -o \
+ "`basename $_path`" = "$_shell" ]; then
echo $_path
return 0
fi
;;
esac
done
+
return 1
}
+# 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" ] &&
+ info "The shell ($_sh) does not exist or is not executable."
+
+ return 0
+}
+
# save_config
# Save some variables to a configuration file.
# Note: not all script variables are saved, only those that
save_config() {
echo "# Configuration file for adduser(8)." > ${ADDUSERCONF}
echo "# NOTE: only *some* variables are saved." >> ${ADDUSERCONF}
- echo "# Last Modified on `date`." >> ${ADDUSERCONF}
+ echo "# Last Modified on `${DATECMD}`." >> ${ADDUSERCONF}
echo '' >> ${ADDUSERCONF}
+ echo "defaultHomePerm=$uhomeperm" >> ${ADDUSERCONF}
+ echo "defaultLgroup=$ulogingroup" >> ${ADDUSERCONF}
echo "defaultclass=$uclass" >> ${ADDUSERCONF}
echo "defaultgroups=$ugroups" >> ${ADDUSERCONF}
echo "passwdtype=$passwdtype" >> ${ADDUSERCONF}
echo "udotdir=$udotdir" >> ${ADDUSERCONF}
echo "msgfile=$msgfile" >> ${ADDUSERCONF}
echo "disableflag=$disableflag" >> ${ADDUSERCONF}
+ echo "uidstart=$uidstart" >> ${ADDUSERCONF}
}
# add_user
_passwdmethod=
_name="-n '$username'"
- [ -n "$uuid" ] && _uid="-u '$uuid'"
- [ -n "$ulogingroup" ] && _group="-g '$ulogingroup'"
- [ -n "$ugroups" ] && _grouplist="-G '$ugroups'"
- [ -n "$ushell" ] && _shell="-s '$ushell'"
- [ -n "$uhome" ] && _home="-m -d '$uhome'"
- [ -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'"
+ [ -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"'
+ 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
+ # Use home directory permissions if specified
+ if [ -n "$uhomeperm" ]; then
+ _home='-m -d "$uhome" -M "$uhomeperm"'
+ else
+ _home='-m -d "$uhome"'
+ fi
+ fi
+ elif [ -n "$Dflag" -a -n "$uhome" ]; then
+ _home='-d "$uhome"'
+ fi
case $passwdtype in
no)
_passwdmethod="-w no"
_passwd="-h -"
;;
yes)
+ # 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.
+ #
_passwdmethod="-w yes"
_passwd="-h 0"
- _upasswd="echo '$upass' |"
+ _upasswd='echo "$upass" |'
;;
none)
_passwdmethod="-w none"
_input="`echo "$fileline" | cut -f1 -d:`"
fi
- # There *must* be a username. If this is an interactive
- # session give the user an opportunity to retry.
+ # There *must* be a username, and it must not exist. If
+ # this is an interactive session give the user an
+ # opportunity to retry.
#
if [ -z "$_input" ]; then
err "You must enter a username!"
[ -z "$fflag" ] && continue
fi
+ ${PWCMD} usershow $_input > /dev/null 2>&1
+ if [ "$?" -eq 0 ]; then
+ err "User exists!"
+ [ -z "$fflag" ] && continue
+ fi
break
done
username="$_input"
# get_shell
# Get the account's shell. Works in interactive and batch mode. It
-# accepts only the base name of the shell, NOT the full path.
+# accepts either the base name of the shell or the full path.
# If an invalid shell is entered it will simply use the default shell.
#
get_shell() {
ushell="$defaultshell"
# Make sure the current value of the shell is a valid one
- _shellchk="grep '^$ushell$' ${ETCSHELLS} > /dev/null 2>&1"
- eval $_shellchk || {
- err "Invalid shell ($ushell). Using default shell ${defaultshell}."
- ushell="$defaultshell"
- }
+ if [ -z "$Sflag" ]; then
+ if ! shell_exists $ushell ; then
+ info "Using default shell ${defaultshell}."
+ ushell="$defaultshell"
+ fi
+ fi
if [ -z "$fflag" ]; then
echo -n "Shell ($shells) [`basename $ushell`]: "
_input="`echo "$fileline" | cut -f9 -d:`"
fi
if [ -n "$_input" ]; then
- _fullpath=`fullpath_from_shell $_input`
- if [ -n "$_fullpath" ]; then
- ushell="$_fullpath"
+ if [ -n "$Sflag" ]; then
+ ushell="$_input"
else
- err "Invalid shell selection. Using default shell ${defaultshell}."
- ushell="$defaultshell"
+ _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
fi
fi
}
fi
}
+# get_homeperm
+# Reads the account's home directory permissions.
+#
+get_homeperm() {
+ uhomeperm=$defaultHomePerm
+ _input=
+ _prompt=
+
+ if [ -n "$uhomeperm" ]; then
+ _prompt="Home directory permissions [${uhomeperm}]: "
+ else
+ _prompt="Home directory permissions (Leave empty for default): "
+ fi
+ if [ -z "$fflag" ]; then
+ echo -n "$_prompt"
+ read _input
+ fi
+
+ if [ -n "$_input" ]; then
+ uhomeperm="$_input"
+ fi
+}
+
# get_uid
# Reads a numeric userid in an interactive or batch session. Automatically
# allocates one if it is not specified.
_input=
_prompt=
- # No need to take down uids for a configuration saving run.
- [ -n "$configflag" ] && return
-
if [ -n "$uuid" ]; then
_prompt="Uid [$uuid]: "
else
# 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.
-# This routine leaves the field blank if nothing is provided. The pw(8)
-# command will then provide a login group with the same name as the username.
+# 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.
#
get_logingroup() {
- ulogingroup=
+ ulogingroup="$defaultLgroup"
_input=
- # No need to take down a login group for a configuration saving run.
- [ -n "$configflag" ] && return
-
if [ -z "$fflag" ]; then
- echo -n "Login group [$username]: "
+ echo -n "Login group [${ulogingroup:-$username}]: "
read _input
else
_input="`echo "$fileline" | cut -f3 -d:`"
fi
# Pw(8) will use the username as login group if it's left empty
- [ -n "$_input" ] && ulogingroup="$_input" || ulogingroup=
+ [ -n "$_input" ] && ulogingroup="$_input"
}
# get_groups
input_from_file() {
_field=
- while read fileline ; do
+ while read -r fileline ; do
case "$fileline" in
\#*|'')
- return 0
;;
- esac
-
- get_user || continue
- get_gecos
- get_uid
- get_logingroup
- get_class
- get_shell
- get_homedir
- get_password
- get_expire_dates
+ *)
+ get_user || continue
+ get_gecos
+ get_uid
+ get_logingroup
+ get_class
+ get_shell
+ get_homedir
+ get_homeperm
+ get_password
+ get_expire_dates
+ ugroups="$defaultgroups"
- add_user
+ add_user
+ ;;
+ esac
done
}
# the user database.
#
input_interactive() {
-
_disable=
_pass=
_passconfirm=
_random="no"
_emptypass="no"
_usepass="yes"
+ _logingroup_ok="no"
+ _groups_ok="no"
case $passwdtype in
none)
_emptypass="yes"
get_user
get_gecos
get_uid
- get_logingroup
- get_groups
+
+ # 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
+
get_class
get_shell
get_homedir
+ get_homeperm
while : ; do
echo -n "Use password-based authentication? [$_usepass]: "
;;
esac
passwdtype="yes"
+ [ -n "$configflag" ] && break
trap 'stty echo; exit' 0 1 2 3 15
stty -echo
echo -n "Enter password: "
- read upass
+ read -r upass
echo''
echo -n "Enter password again: "
- read _passconfirm
+ read -r _passconfirm
echo ''
stty echo
# if user entered a blank password
_pass='<random>'
;;
esac
- printf "%-10s : %s\n" "Password" "$_pass"
+ [ -z "$configflag" ] && printf "%-10s : %s\n" "Password" "$_pass"
+ [ -n "$configflag" ] && printf "%-10s : %s\n" "Pass Type" "$passwdtype"
[ -z "$configflag" ] && printf "%-10s : %s\n" "Full Name" "$ugecos"
[ -z "$configflag" ] && printf "%-10s : %s\n" "Uid" "$uuid"
printf "%-10s : %s\n" "Class" "$uclass"
- [ -z "$configflag" ] && printf "%-10s : %s %s\n" "Groups" "${ulogingroup:-$username}" "$ugroups"
+ printf "%-10s : %s %s\n" "Groups" "${ulogingroup:-$username}" "$ugroups"
printf "%-10s : %s\n" "Home" "$uhome"
+ printf "%-10s : %s\n" "Home Mode" "$uhomeperm"
printf "%-10s : %s\n" "Shell" "$ushell"
printf "%-10s : %s\n" "Locked" "$_disable"
while : ; do
return 0
}
-#### END SUBROUTINE DEFENITION ####
+#### END SUBROUTINE DEFINITION ####
THISCMD=`/usr/bin/basename $0`
DEFAULTSHELL=/bin/sh
PWCMD="${PWCMD:-/usr/sbin/pw}"
MAILCMD="${MAILCMD:-mail}"
ETCSHELLS="${ETCSHELLS:-/etc/shells}"
+NOHOME="/nonexistent"
+NOLOGIN="nologin"
+NOLOGIN_PATH="/usr/sbin/nologin"
+GREPCMD="/usr/bin/grep"
+DATECMD="/bin/date"
# Set default values
#
ulogingroup=
uclass=
uhome=
+uhomeperm=
upass=
ushell=
udotdir=/usr/share/skel
fflag=
infile=
disableflag=
+Dflag=
+Sflag=
readconfig="yes"
homeprefix="/home"
randompass=
fileline=
savedpwtype=
defaultclass=
-defaultgoups=
+defaultLgroup=
+defaultgroups=
defaultshell="${DEFAULTSHELL}"
+defaultHomePerm=
# 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
+# measure as much as it is a useful 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`}
exit 1
fi
-# Overide from our conf file
+# Override 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
fi
fi
-# Proccess command-line options
+# Process command-line options
#
for _switch ; do
case $_switch in
configflag=yes
shift
;;
+ -D)
+ Dflag=yes
+ shift
+ ;;
-E)
disableflag=yes
shift
fflag=yes
shift; shift
;;
+ -g)
+ defaultLgroup="$2"
+ shift; shift
+ ;;
-G)
defaultgroups="$2"
shift; shift
esac
shift; shift
;;
+ -M)
+ defaultHomePerm=$2
+ shift; shift
+ ;;
-N)
readconfig=
shift
defaultshell="`fullpath_from_shell $2`"
shift; shift
;;
+ -S)
+ Sflag=yes
+ shift
+ ;;
-u)
uidstart=$2
shift; shift
fi
else
input_interactive
+ while : ; do
+ if [ -z "$configflag" ]; then
+ echo -n "Add another user? (yes/no): "
+ else
+ echo -n "Re-edit the default configuration? (yes/no): "
+ fi
+ 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
fi