Skip to content

Commit

Permalink
adduser(8): support creation of ZFS dataset
Browse files Browse the repository at this point in the history
On systems utilizing ZFS, default to creating a ZFS dataset for a new
user's home directory if the parent directory resides on a ZFS dataset.
Add a flag that disables this behavior if the administrator explicitly
does not want it.

If run during installation from within a chroot, set mountpoint to legacy
after dataset creation and mount directly into the chroot.  Then umount
and reset the mountpoint to inherit from parent.

Also support ZFS default encryption on user's home directory.

Feedback by: delphij
Reviewed by: imp, kevans
Pull Request: #881

(cherry picked from commit 215c0a5)
  • Loading branch information
jgrafton authored and dag-erling committed Apr 29, 2024
1 parent 2c24df8 commit 516009c
Show file tree
Hide file tree
Showing 3 changed files with 159 additions and 14 deletions.
10 changes: 8 additions & 2 deletions usr.sbin/adduser/adduser.8
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,15 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
.Dd September 15, 2012
.Dd April 11, 2024
.Dt ADDUSER 8
.Os
.Sh NAME
.Nm adduser
.Nd command for adding new users
.Sh SYNOPSIS
.Nm
.Op Fl CDENShq
.Op Fl CDENSZhq
.Op Fl G Ar groups
.Op Fl L Ar login_class
.Op Fl M Ar mode
Expand All @@ -52,6 +52,10 @@ utility is a shell script, implemented around the
command, for adding new users.
It creates passwd/group entries, a home directory,
copies dotfiles and sends the new user a welcome message.
On systems where the parent of home directory is a ZFS dataset,
.Nm
will create the home directory as a ZFS dataset by default,
unless the system administrator specified otherwise.
It supports two modes of operation.
It may be used interactively
at the command line to add one user at a time, or it may be directed
Expand Down Expand Up @@ -295,6 +299,8 @@ In addition, it will be available for inclusion in the message file in the
.Va randompass
variable.
.El
.It Fl Z
Do not attempt to create ZFS home dataset.
.El
.Sh FORMAT
When the
Expand Down
2 changes: 2 additions & 0 deletions usr.sbin/adduser/adduser.conf.5
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,8 @@ The default information to be held in the GECOS field of
.It Va uidstart
The default user ID setting.
This must be a number above 1000 and fewer than 65534.
.It Va Zflag
Do not attempt to create ZFS home dataset.
.El
.Sh EXAMPLES
The following is an example
Expand Down
161 changes: 149 additions & 12 deletions usr.sbin/adduser/adduser.sh
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ show_usage() {
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"
Expand Down Expand Up @@ -283,6 +284,22 @@ add_user() {
;;
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
create_zfs_dataset
if [ "$?" -ne 0 ]; 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"
Expand All @@ -306,6 +323,14 @@ add_user() {
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

_line=
_owner=
_perms=
Expand Down Expand Up @@ -479,6 +504,26 @@ get_homeperm() {
fi
}

# get_zfs_home
# Determine if homeprefix is located on a ZFS filesystem and if
# so, enable ZFS home dataset creation.
#
get_zfs_home() {
# 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
zfs_homeprefix=`${ZFSCMD} list -Ho name "${homeprefix}" 2>/dev/null`
if [ "$?" -ne 0 ]; then
Zcreate="no"
elif [ -z "${zfs_homeprefix}" ]; then
Zcreate="no"
fi
zhome="${zfs_homeprefix}/${username}"
}

# get_uid
# Reads a numeric userid in an interactive or batch session. Automatically
# allocates one if it is not specified.
Expand Down Expand Up @@ -613,6 +658,81 @@ get_password() {
fi
}

# get_zfs_encryption
# Ask user if they want to enable encryption on their ZFS home dataset.
#
get_zfs_encryption() {
_input=
_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.
Expand All @@ -632,6 +752,7 @@ input_from_file() {
get_class
get_shell
get_homedir
get_zfs_home
get_homeperm
get_password
get_expire_dates
Expand Down Expand Up @@ -704,6 +825,8 @@ input_interactive() {
get_shell
get_homedir
get_homeperm
get_zfs_home
[ "$Zcreate" = "yes" ] && get_zfs_encryption

while : ; do
echo -n "Use password-based authentication? [$_usepass]: "
Expand Down Expand Up @@ -787,12 +910,12 @@ input_interactive() {
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
[ -z "$configflag" ] && printf "%-11s : %s\n" Username $username
case $passwdtype in
yes)
_pass='*****'
Expand All @@ -807,16 +930,18 @@ input_interactive() {
_pass='<random>'
;;
esac
[ -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"
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"
[ -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" -a -z "$configflag" ] && printf "%-11s : %s\n" "ZFS dataset" "${zhome}"
[ "$Zencrypt" = "yes" -a -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
Expand Down Expand Up @@ -852,6 +977,11 @@ 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
#
Expand Down Expand Up @@ -880,6 +1010,7 @@ infile=
disableflag=
Dflag=
Sflag=
Zcreate="yes"
readconfig="yes"
homeprefix="/home"
randompass=
Expand All @@ -890,6 +1021,8 @@ 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
Expand Down Expand Up @@ -1014,6 +1147,10 @@ for _switch ; do
uidstart=$2
shift; shift
;;
-Z)
Zcreate="no"
shift
;;
esac
done

Expand Down

0 comments on commit 516009c

Please sign in to comment.