freebsd-src/usr.sbin/adduser/adduser.sh
Mike Karels 0b39b2e2dd adduser: create dataset only if home is directly within dataset
Currently, if the prefix of the new home directory is a subdirectory
of a ZFS dataset, adduser will create a new dataset up one or more
levels from the intended destination.  "pw useradd" will then create
a normal directory in the desired location, leaving an unused dataset.
Check for this situation when determining whether to create a dataset,
and let pw create the directory.

Reviewed by:	des
Differential Revision:	https://reviews.freebsd.org/D45229
MFC after:	3 days
2024-05-20 08:43:34 -05:00

1165 lines
27 KiB
Bash

#!/bin/sh
#
# SPDX-License-Identifier: BSD-2-Clause
#
# 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
# 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.
#
# Email: Mike Makonnen <mtm@FreeBSD.Org>
#
#
# 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 () {
local _uid=$1 _nextuid=
if [ -z "$_uid" ]; then
_nextuid="$(${PWCMD} usernext | cut -f1 -d:)"
else
while : ; do
if ! ${PWCMD} usershow $_uid > /dev/null 2>&1; 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"
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 " -Z do not attempt to create ZFS home dataset"
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"
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() {
local _prefix=
${GREPCMD} '^[^#]' ${ETCSHELLS} |
while read _path _junk ; do
echo -n "${_prefix}${_path##*/}"
_prefix=' '
done
# /usr/sbin/nologin is a special case
[ -x "${NOLOGIN_PATH}" ] && echo -n " ${NOLOGIN}"
}
# fullpath_from_shell shell
# 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() {
local _shell=$1 _fullpath=
if [ -z "$_shell" ]; then
return
fi
# /usr/sbin/nologin is a special case; it needs to be handled
# before the grep | 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}" ] ||
[ "$_shell" = "${NOLOGIN_PATH}" ]; then
echo ${NOLOGIN_PATH}
return
fi
${GREPCMD} '^[^#]' ${ETCSHELLS} |
while read _path _junk ; do
if [ "$_path" = "$_shell" ] ||
[ "${_path##*/}" = "$_shell" ]; then
echo "$_path"
break
fi
done
}
# 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() {
local _sh=$1
if [ -z "$(fullpath_from_shell "$_sh")" ] ; then
err "Invalid shell ($_sh) for user $username."
return 1
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
# it makes sense to save.
#
save_config() {
echo "# Configuration file for adduser(8)." > ${ADDUSERCONF}
echo "# NOTE: only *some* variables are saved." >> ${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 "homeprefix=$homeprefix" >> ${ADDUSERCONF}
echo "defaultshell=$ushell" >> ${ADDUSERCONF}
echo "udotdir=$udotdir" >> ${ADDUSERCONF}
echo "msgfile=$msgfile" >> ${ADDUSERCONF}
echo "disableflag=$disableflag" >> ${ADDUSERCONF}
echo "uidstart=$uidstart" >> ${ADDUSERCONF}
}
# 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() {
local _uid= _name= _comment= _gecos= _home= _group= _grouplist=
local _shell= _class= _dotdir= _expire= _pwexpire= _passwd= _upasswd=
local _passwdmethod= _pwcmd=
# Is this a configuration run? If so, don't modify user database.
#
if [ -n "$configflag" ]; then
save_config
return
fi
_name="-n '$username'"
[ -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" ] && [ -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" ] && [ -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" |'
;;
none)
_passwdmethod="-w none"
;;
random)
_passwdmethod="-w random"
;;
esac
# create ZFS dataset before home directory is created with pw
if [ "${Zcreate}" = "yes" ]; then
if [ "${Zencrypt}" = "yes" ]; then
echo "Enter encryption keyphrase for ZFS dataset (${zhome}):"
fi
if [ -n "$BSDINSTALL_CHROOT" ]; then
create_zfs_chrooted_dataset
else
if ! create_zfs_dataset; then
err "There was an error adding user ($username)."
return 1
fi
fi
fi
_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
# give newly created user permissions to their home zfs dataset
if [ "${Zcreate}" = "yes" ]; then
set_zfs_perms
if [ -n "$BSDINSTALL_CHROOT" ]; then
umount_legacy_zfs
fi
fi
local _line= _owner= _perms= _file= _dir=
if [ -n "$msgflag" ]; then
if [ -r "$msgfile" ]; then
# 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" ] || [ -n "$_perms" ]; then
err "The message file ($msgfile) may be writeable only by root."
return 1
fi
while read _line ; do
eval echo "$_line"
done <"$msgfile" | ${MAILCMD} -s"Welcome" ${username}
info "Sent welcome message to ($username)."
fi
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() {
local _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
# 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
if ${PWCMD} usershow "$_input" > /dev/null 2>&1; then
err "User exists!"
[ -z "$fflag" ] && continue
fi
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() {
local _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
# 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() {
local _input= _fullpath=
ushell="$defaultshell"
# Make sure the current value of the shell is a valid one
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) [${ushell##*/}]: "
read _input
else
_input="$(echo "$fileline" | cut -f9 -d:)"
fi
if [ -n "$_input" ]; then
if [ -n "$Sflag" ]; then
ushell="$_input"
else
_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
}
# get_homedir
# Reads the account's home directory. Used both with interactive input
# and batch input.
#
get_homedir() {
local _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_homeperm
# Reads the account's home directory permissions.
#
get_homeperm() {
local _input= _prompt=
uhomeperm=$defaultHomePerm
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_zfs_home
# Determine if homeprefix is located on a ZFS filesystem and if
# so, enable ZFS home dataset creation.
#
get_zfs_home() {
local _prefix= _tmp=
# check if zfs kernel module is loaded before attempting to run zfs to
# prevent loading the kernel module on systems that don't use ZFS
if ! "$KLDSTATCMD" -q -m zfs; then
Zcreate="no"
return
fi
if ! _prefix=$(${ZFSCMD} list -Ho name "${homeprefix}" 2>/dev/null) ||
[ -z "${_prefix}" ]; then
Zcreate="no"
return
fi
# Make sure that _prefix is not a subdirectory within a dataset. If it
# is, the containing dataset will be the same for it and its parent.
_tmp=$(${ZFSCMD} list -Ho name "$(dirname "${homeprefix}")" 2>/dev/null)
if [ "${_tmp}" = "${_prefix}" ]; then
Zcreate="no"
return
fi
zhome="${_prefix}/${username}"
}
# get_uid
# Reads a numeric userid in an interactive or batch session. Automatically
# allocates one if it is not specified.
#
get_uid() {
local _input= _prompt=
uuid=${uidstart}
if [ -n "$uuid" ]; then
uuid=$(get_nextuid "$uuid")
_prompt="Uid [$uuid]: "
else
_prompt="Uid (Leave empty for default): "
fi
if [ -z "$fflag" ]; then
echo -n "$_prompt"
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() {
local _input= _class=
uclass="$defaultclass"
_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.
# 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() {
local _input=
ulogingroup="$defaultLgroup"
if [ -z "$fflag" ]; then
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"
}
# get_groups
# Read additional groups for the user. It can be used in both interactive
# and batch modes.
#
get_groups() {
local _input= _group=
ugroups="$defaultgroups"
_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.
#
if [ -n "$savedpwtype" ]; then
passwdtype=$savedpwtype
savedpwtype=
fi
# 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
}
# get_zfs_encryption
# Ask user if they want to enable encryption on their ZFS home dataset.
#
get_zfs_encryption() {
local _input= _prompt=
_prompt="Enable ZFS encryption? (yes/no) [${Zencrypt}]: "
while : ; do
echo -n "$_prompt"
read _input
[ -z "$_input" ] && _input=$Zencrypt
case $_input in
[Nn][Oo]|[Nn])
Zencrypt="no"
break
;;
[Yy][Ee][Ss]|[Yy][Ee]|[Yy])
Zencrypt="yes"
break
;;
*)
# invalid answer; repeat loop
continue
;;
esac
done
if [ "${Zencrypt}" = "yes" ]; then
zfsopt="-o encryption=on -o keylocation=prompt -o keyformat=passphrase"
fi
}
# create_zfs_chrooted_dataset
# Create ZFS dataset owned by the user that was just added within a bsdinstall chroot
#
create_zfs_chrooted_dataset() {
if ! ${ZFSCMD} create -u ${zfsopt} "${zhome}"; then
err "There was an error creating ZFS dataset (${zhome})."
return 1
fi
${ZFSCMD} set mountpoint=legacy "${zhome}"
${MKDIRCMD} -p "${uhome}"
${MOUNTCMD} -t zfs "${zhome}" "${uhome}"
}
# umount_legacy_zfs
# Unmount ZFS home directory created as a legacy mount and switch inheritance
#
umount_legacy_zfs() {
${UMOUNTCMD} "${uhome}"
${ZFSCMD} inherit mountpoint "${zhome}"
}
# create_zfs_dataset
# Create ZFS dataset owned by the user that was just added.
#
create_zfs_dataset() {
if ! ${ZFSCMD} create ${zfsopt} "${zhome}"; then
err "There was an error creating ZFS dataset (${zhome})."
return 1
else
info "Successfully created ZFS dataset (${zhome})."
fi
}
# set_zfs_perms
# Give new user ownership of newly created zfs dataset.
#
set_zfs_perms() {
if ! ${ZFSCMD} allow "${username}" create,destroy,mount,snapshot "${zhome}"; then
err "There was an error setting permissions on ZFS dataset (${zhome})."
return 1
fi
}
# input_from_file
# Reads a line of account information from standard input and
# adds it to the user database.
#
input_from_file() {
local _field=
while read -r fileline ; do
case "$fileline" in
\#*|'')
;;
*)
get_user || continue
get_gecos
get_uid
get_logingroup
get_class
get_shell
get_homedir
get_zfs_home
get_homeperm
get_password
get_expire_dates
ugroups="$defaultgroups"
add_user
;;
esac
done
}
# input_interactive
# Prompts for user information interactively, and commits to
# the user database.
#
input_interactive() {
local _disable= _pass= _passconfirm= _input=
local _random="no"
local _emptypass="no"
local _usepass="yes"
local _logingroup_ok="no"
local _groups_ok="no"
local _all_ok="yes"
case $passwdtype in
none)
_emptypass="yes"
_usepass="yes"
;;
no)
_usepass="no"
;;
random)
_random="yes"
;;
esac
get_user
get_gecos
get_uid
# 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" ] && [ "$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
get_zfs_home
[ "$Zcreate" = "yes" ] && get_zfs_encryption
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"
[ -n "$configflag" ] && break
trap 'stty echo; exit' 0 1 2 3 15
stty -echo
echo -n "Enter password: "
IFS= read -r upass
echo''
echo -n "Enter password again: "
IFS= read -r _passconfirm
echo ''
stty echo
# if user entered a blank password
# explicitly ask again.
[ -z "$upass$_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 "%-11s : %s\n" Username $username
case $passwdtype in
yes)
_pass='*****'
;;
no)
_pass='<disabled>'
;;
none)
_pass='<blank>'
;;
random)
_pass='<random>'
;;
esac
[ -z "$configflag" ] && printf "%-11s : %s\n" "Password" "$_pass"
[ -n "$configflag" ] && printf "%-11s : %s\n" "Pass Type" "$passwdtype"
[ -z "$configflag" ] && printf "%-11s : %s\n" "Full Name" "$ugecos"
[ -z "$configflag" ] && printf "%-11s : %s\n" "Uid" "$uuid"
[ "$Zcreate" = "yes" ] && [ -z "$configflag" ] &&
printf "%-11s : %s\n" "ZFS dataset" "${zhome}"
[ "$Zencrypt" = "yes" ] && [ -z "$configflag" ] &&
printf "%-11s : %s\n" "Encrypted" "${Zencrypt}"
printf "%-11s : %s\n" "Class" "$uclass"
printf "%-11s : %s %s\n" "Groups" "${ulogingroup:-$username}" "$ugroups"
printf "%-11s : %s\n" "Home" "$uhome"
printf "%-11s : %s\n" "Home Mode" "$uhomeperm"
printf "%-11s : %s\n" "Shell" "$ushell"
printf "%-11s : %s\n" "Locked" "$_disable"
while : ; do
echo -n "OK? (yes/no) [$_all_ok]: "
read _input
if [ -z "$_input" ]; then
_input=$_all_ok
fi
case $_input in
[Nn][Oo]|[Nn])
return 1
;;
[Yy][Ee][Ss]|[Yy][Ee]|[Yy])
add_user
;;
*)
continue
;;
esac
break
done
return 0
}
#### END SUBROUTINE DEFINITION ####
THISCMD=${0##*/}
DEFAULTSHELL=/bin/sh
ADDUSERCONF="${ADDUSERCONF:-/etc/adduser.conf}"
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"
MKDIRCMD="/bin/mkdir"
MOUNTCMD="/sbin/mount"
UMOUNTCMD="/sbin/umount"
ZFSCMD="/sbin/zfs"
KLDSTATCMD="/sbin/kldstat"
# Set default values
#
username=
uuid=
uidstart=
ugecos=
ulogingroup=
uclass=
uhome=
uhomeperm=
upass=
ushell=
udotdir=/usr/share/skel
ugroups=
uexpire=
upwexpire=
shells="$(valid_shells)"
passwdtype="yes"
msgfile=/etc/adduser.msg
msgflag=
quietflag=
configflag=
fflag=
infile=
disableflag=
Dflag=
Sflag=
Zcreate="yes"
readconfig="yes"
homeprefix="/home"
randompass=
fileline=
savedpwtype=
defaultclass=
defaultLgroup=
defaultgroups=
defaultshell="${DEFAULTSHELL}"
defaultHomePerm=
zfsopt=
Zencrypt="no"
# Make sure the user running this program is root. This isn't a security
# 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)}
if [ "$procowner" != "0" ]; then
err 'you must be the super-user (uid 0) to use this utility.'
exit 1
fi
# 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
# should override configuration file).
#
for _i in $* ; do
if [ "$_i" = "-N" ]; then
readconfig=
break;
fi
done
if [ -n "$readconfig" ] && [ -r "${ADDUSERCONF}" ]; then
. "${ADDUSERCONF}"
fi
# Process command-line options
#
for _switch ; do
case $_switch in
-L)
defaultclass="$2"
shift; shift
;;
-C)
configflag=yes
shift
;;
-D)
Dflag=yes
shift
;;
-E)
disableflag=yes
shift
;;
-k)
udotdir="$2"
shift; shift
;;
-f)
[ "$2" != "-" ] && infile="$2"
fflag=yes
shift; shift
;;
-g)
defaultLgroup="$2"
shift; shift
;;
-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
;;
-M)
defaultHomePerm=$2
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
;;
-S)
Sflag=yes
shift
;;
-u)
uidstart=$2
shift; shift
;;
-Z)
Zcreate="no"
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
while : ; do
_another_user="no"
if [ -z "$configflag" ]; then
echo -n "Add another user? (yes/no) [$_another_user]: "
else
echo -n "Re-edit the default configuration? (yes/no) [$_another_user]: "
fi
read _input
if [ -z "$_input" ]; then
_input=$_another_user
fi
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