File: //usr/share/bsdconfig/usermgmt/user.subr
if [ ! "$_USERMGMT_USER_SUBR" ]; then _USERMGMT_USER_SUBR=1
#
# Copyright (c) 2012 Ron McDowell
# Copyright (c) 2012-2015 Devin Teske
# 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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.
#
# $FreeBSD$
#
############################################################ INCLUDES
BSDCFG_SHARE="/usr/share/bsdconfig"
. $BSDCFG_SHARE/common.subr || exit 1
f_dprintf "%s: loading includes..." usermgmt/user.subr
f_include $BSDCFG_SHARE/dialog.subr
f_include $BSDCFG_SHARE/strings.subr
f_include $BSDCFG_SHARE/usermgmt/group_input.subr
f_include $BSDCFG_SHARE/usermgmt/user_input.subr
BSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="070.usermgmt"
f_include_lang $BSDCFG_LIBE/$APP_DIR/include/messages.subr
############################################################ CONFIGURATION
# set some reasonable defaults if /etc/adduser.conf does not exist.
[ -f /etc/adduser.conf ] && f_include /etc/adduser.conf
: ${defaultclass:=""}
: ${defaultshell:="/bin/sh"}
: ${homeprefix:="/home"}
: ${passwdtype:="yes"}
: ${udotdir:="/usr/share/skel"}
: ${uexpire:=""}
# Default account expire time. Format is similar to upwexpire variable.
: ${ugecos:="User &"}
: ${upwexpire:=""}
# The default password expiration time. Format of the date is either a
# UNIX time in decimal, or a date in dd-mmm-yy[yy] format, where dd is
# the day, mmm is the month in either numeric or alphabetic format, and
# yy[yy] is either a two or four digit year. This variable also accepts
# a relative date in the form of n[mhdwoy] where n is a decimal, octal
# (leading 0) or hexadecimal (leading 0x) digit followed by the number
# of Minutes, Hours, Days, Weeks, Months or Years from the current date
# at which the expiration time is to be set.
#
# uexpire and upwexpire from adduser.conf(5) differ only slightly from what
# pw(8) accepts as `date' argument(s); pw(8) requires a leading `+' for the
# relative date syntax (n[mhdwoy]).
#
case "$uexpire" in *[mhdwoy])
f_isinteger "${uexpire%[mhdwoy]}" && uexpire="+$uexpire"
esac
case "$upwexpire" in *[mhdwoy])
f_isinteger "${upwexpire%[mhdwoy]}" && upwexpire="+$upwexpire"
esac
############################################################ FUNCTIONS
# f_user_create_homedir $user
#
# Create home directory for $user.
#
f_user_create_homedir()
{
local funcname=f_user_create_homedir
local user="$1"
[ "$user" ] || return $FAILURE
local user_account_expire user_class user_gecos user_gid user_home_dir
local user_member_groups user_name user_password user_password_expire
local user_shell user_uid # Variables created by f_input_user() below
f_input_user "$user" || return $FAILURE
f_dprintf "Creating home directory \`%s' for user \`%s'" \
"$user_home_dir" "$user"
local _user_gid _user_home_dir _user_uid
f_shell_escape "$user_gid" _user_gid
f_shell_escape "$user_home_dir" _user_home_dir
f_shell_escape "$user_uid" _user_uid
f_eval_catch $funcname mkdir "mkdir -p '%s'" "$_user_home_dir" ||
return $FAILURE
f_eval_catch $funcname chown "chown '%i:%i' '%s'" \
"$_user_uid" "$_user_gid" "$_user_home_dir" || return $FAILURE
}
# f_user_copy_dotfiles $user
#
# Copy `skel' dot-files from $udotdir (global inherited from /etc/adduser.conf)
# to the home-directory of $user. Attempts to create the home-directory first
# if it doesn't exist.
#
f_user_copy_dotfiles()
{
local funcname=f_user_copy_dotfiles
local user="$1"
[ "$udotdir" ] || return $FAILURE
[ "$user" ] || return $FAILURE
local user_account_expire user_class user_gecos user_gid user_home_dir
local user_member_groups user_name user_password user_password_expire
local user_shell user_uid # Variables created by f_input_user() below
f_input_user "$user" || return $FAILURE
f_dprintf "Copying dot-files from \`%s' to \`%s'" \
"$udotdir" "$user_home_dir"
# Attempt to create the home directory if it doesn't exist
[ -d "$user_home_dir" ] ||
f_user_create_homedir "$user" || return $FAILURE
local _user_gid _user_home_dir _user_uid
f_shell_escape "$user_gid" _user_gid
f_shell_escape "$user_home_dir" _user_home_dir
f_shell_escape "$user_uid" _user_uid
local - # Localize `set' to this function
set +f # Enable glob pattern-matching for paths
cd "$udotdir" || return $FAILURE
local _file file retval
for file in dot.*; do
[ -e "$file" ] || continue # no-match
f_shell_escape "$file" "_file"
f_eval_catch $funcname cp "cp -n '%s' '%s'" \
"$_file" "$_user_home_dir/${_file#dot}"
retval=$?
[ $retval -eq $SUCCESS ] || break
f_eval_catch $funcname chown \
"chown -h '%i:%i' '%s'" \
"$_user_uid" "$_user_gid" \
"$_user_home_dir/${_file#dot}"
retval=$?
[ $retval -eq $SUCCESS ] || break
done
cd -
return $retval
}
# f_user_add [$user]
#
# Create a login account. If both $user (as a first argument) and $VAR_USER are
# unset or NULL and we are running interactively, prompt the end-user to enter
# the name of a new login account and (if $VAR_NO_CONFIRM is unset or NULL)
# prompt the end-user to answer some questions about the new account. Variables
# that can be used to script user input:
#
# VAR_USER [Optional if running interactively]
# The login to add. Ignored if given non-NULL first-argument.
# VAR_USER_ACCOUNT_EXPIRE [Optional]
# The account expiration time. Format is similar to
# VAR_USER_PASSWORD_EXPIRE variable below. Default is to never
# expire the account.
# VAR_USER_DOTFILES_CREATE [Optional]
# If non-NULL, populate the user's home directory with the
# template files found in $udotdir (`/usr/share/skel' default).
# VAR_USER_GECOS [Optional]
# Often the full name of the account holder. Default is NULL.
# VAR_USER_GID [Optional]
# Numerical primary-group ID to use. If NULL or unset, the group
# ID is automatically chosen.
# VAR_USER_GROUPS [Optional]
# Comma-separated list of additional groups to which the user is
# a member of. Default is NULL (no additional groups).
# VAR_USER_HOME [Optional]
# The home directory to set. If NULL or unset, the home directory
# is automatically calculated.
# VAR_USER_HOME_CREATE [Optional]
# If non-NULL, create the user's home directory if it doesn't
# already exist.
# VAR_USER_LOGIN_CLASS [Optional]
# Login class to use when creating the login. Default is NULL.
# VAR_USER_PASSWORD [Optional]
# Unencrypted password to use. If unset or NULL, password
# authentication for the login is disabled.
# VAR_USER_PASSWORD_EXPIRE [Optional]
# The password expiration time. Format of the date is either a
# UNIX time in decimal, or a date in dd-mmm-yy[yy] format, where
# dd is the day, mmm is the month in either numeric or alphabetic
# format, and yy[yy] is either a two or four digit year. This
# variable also accepts a relative date in the form of +n[mhdwoy]
# where n is a decimal, octal (leading 0) or hexadecimal (leading
# 0x) digit followed by the number of Minutes, Hours, Days,
# Weeks, Months or Years from the current date at which the
# expiration time is to be set. Default is to never expire the
# account password.
# VAR_USER_SHELL [Optional]
# Path to login shell to use. Default is `/bin/sh'.
# VAR_USER_UID [Optional]
# Numerical user ID to use. If NULL or unset, the user ID is
# automatically chosen.
#
# Returns success if the user account was successfully created.
#
f_user_add()
{
local funcname=f_user_add
local title # Calculated below
local alert=f_show_msg no_confirm=
f_getvar $VAR_NO_CONFIRM no_confirm
[ "$no_confirm" ] && alert=f_show_info
local input
f_getvar 3:-\$$VAR_USER input "$1"
#
# NB: pw(8) has a ``feature'' wherein `-n name' can be taken as UID
# instead of name. Work-around is to also pass `-u UID' at the same
# time (the UID is ignored in this case, so any UID will do).
#
if [ "$input" ] && f_quietly pw usershow -n "$input" -u 1337; then
f_show_err "$msg_login_already_used" "$input"
return $FAILURE
fi
local user_name="$input"
while f_interactive && [ ! "$user_name" ]; do
f_dialog_input_name user_name "$user_name" ||
return $SUCCESS
[ "$user_name" ] ||
f_show_err "$msg_please_enter_a_user_name"
done
if [ ! "$user_name" ]; then
f_show_err "$msg_no_user_specified"
return $FAILURE
fi
local user_account_expire user_class user_gecos user_gid user_home_dir
local user_member_groups user_password user_password_expire user_shell
local user_uid user_dotfiles_create= user_home_create=
f_getvar $VAR_USER_ACCOUNT_EXPIRE-\$uexpire user_account_expire
f_getvar $VAR_USER_DOTFILES_CREATE:+\$msg_yes user_dotfiles_create
f_getvar $VAR_USER_GECOS-\$ugecos user_gecos
f_getvar $VAR_USER_GID user_gid
f_getvar $VAR_USER_GROUPS user_member_groups
f_getvar $VAR_USER_HOME:-\${homeprefix%/}/\$user_name \
user_home_dir
f_getvar $VAR_USER_HOME_CREATE:+\$msg_yes user_home_create
f_getvar $VAR_USER_LOGIN_CLASS-\$defaultclass user_class
f_getvar $VAR_USER_PASSWORD user_password
f_getvar $VAR_USER_PASSWORD_EXPIRE-\$upwexpire user_password_expire
f_getvar $VAR_USER_SHELL-\$defaultshell user_shell
f_getvar $VAR_USER_UID user_uid
# Create home-dir if no script-override and does not exist
f_isset $VAR_USER_HOME_CREATE || [ -d "$user_home_dir" ] ||
user_home_create="$msg_yes"
# Copy dotfiles if home-dir creation is desired, does not yet exist,
# and no script-override has been set
f_isset $VAR_USER_DOTFILES_CREATE ||
[ "$user_home_create" != "$msg_yes" ] ||
[ -d "$user_home_dir" ] || user_dotfiles_create="$msg_yes"
# Create home-dir if copying dotfiles but home-dir does not exist
[ "$user_dotfiles_create" -a ! -d "$user_home_dir" ] &&
user_home_create="$msg_yes"
# Set flags for meaningful NULL values if-provided
local no_account_expire= no_password_expire= null_gecos= null_members=
local user_password_disable=
f_isset $VAR_USER_ACCOUNT_EXPIRE &&
[ ! "$user_account_expire" ] && no_account_expire=1
f_isset $VAR_USER_GECOS &&
[ ! "$user_gecos" ] && null_gecos=1
f_isset $VAR_USER_GROUPS &&
[ ! "$user_member_groups" ] && null_members=1
f_isset $VAR_USER_PASSWORD &&
[ ! "$user_password" ] && user_password_disable=1
f_isset $VAR_USER_PASSWORD_EXPIRE &&
[ ! "$user_password_expire" ] && no_password_expire=1
if f_interactive && [ ! "$no_confirm" ]; then
f_dialog_noyes \
"$msg_use_default_values_for_all_account_details"
retval=$?
if [ $retval -eq $DIALOG_ESC ]; then
return $SUCCESS
elif [ $retval -ne $DIALOG_OK ]; then
#
# Ask series of questions to pre-fill the editor screen
#
# Defaults used in each dialog should allow the user to
# simply hit ENTER to proceed, because cancelling any
# single dialog will cause them to be returned to the
# previous menu.
#
f_dialog_input_gecos user_gecos "$user_gecos" ||
return $FAILURE
if [ "$passwdtype" = "yes" ]; then
f_dialog_input_password user_password \
user_password_disable ||
return $FAILURE
fi
f_dialog_input_uid user_uid "$user_uid" ||
return $FAILURE
f_dialog_input_gid user_gid "$user_gid" ||
return $FAILURE
f_dialog_input_member_groups user_member_groups \
"$user_member_groups" || return $FAILURE
f_dialog_input_class user_class "$user_class" ||
return $FAILURE
f_dialog_input_expire_password user_password_expire \
"$user_password_expire" || return $FAILURE
f_dialog_input_expire_account user_account_expire \
"$user_account_expire" || return $FAILURE
f_dialog_input_home_dir user_home_dir \
"$user_home_dir" || return $FAILURE
if [ ! -d "$user_home_dir" ]; then
f_dialog_input_home_create user_home_create ||
return $FAILURE
if [ "$user_home_create" = "$msg_yes" ]; then
f_dialog_input_dotfiles_create \
user_dotfiles_create ||
return $FAILURE
fi
fi
f_dialog_input_shell user_shell "$user_shell" ||
return $FAILURE
fi
fi
#
# Loop until the user decides to Exit, Cancel, or presses ESC
#
title="$msg_add $msg_user: $user_name"
if f_interactive; then
local mtag retval defaultitem=
while :; do
f_dialog_title "$title"
f_dialog_menu_user_add "$defaultitem"
retval=$?
f_dialog_title_restore
f_dialog_menutag_fetch mtag
f_dprintf "retval=%u mtag=[%s]" $retval "$mtag"
defaultitem="$mtag"
# Return if user either pressed ESC or chose Cancel/No
[ $retval -eq $DIALOG_OK ] || return $FAILURE
case "$mtag" in
X) # Add/Exit
local var
for var in account_expire class gecos gid home_dir \
member_groups name password_expire shell uid \
; do
local _user_$var
eval f_shell_escape \"\$user_$var\" _user_$var
done
local cmd="pw useradd -n '$_user_name'"
[ "$user_gid" ] && cmd="$cmd -g '$_user_gid'"
[ "$user_shell" ] && cmd="$cmd -s '$_user_shell'"
[ "$user_uid" ] && cmd="$cmd -u '$_user_uid'"
[ "$user_account_expire" -o \
"$no_account_expire" ] &&
cmd="$cmd -e '$_user_account_expire'"
[ "$user_class" -o "$null_class" ] &&
cmd="$cmd -L '$_user_class'"
[ "$user_gecos" -o "$null_gecos" ] &&
cmd="$cmd -c '$_user_gecos'"
[ "$user_home_dir" ] &&
cmd="$cmd -d '$_user_home_dir'"
[ "$user_member_groups" ] &&
cmd="$cmd -G '$_user_member_groups'"
[ "$user_password_expire" -o \
"$no_password_expire" ] &&
cmd="$cmd -p '$_user_password_expire'"
# Execute the command
if [ "$user_password_disable" ]; then
f_eval_catch $funcname pw '%s -h -' "$cmd"
elif [ "$user_password" ]; then
echo "$user_password" | f_eval_catch \
$funcname pw '%s -h 0' "$cmd"
else
f_eval_catch $funcname pw '%s' "$cmd"
fi || continue
# Create home directory if desired
[ "${user_home_create:-$msg_no}" != "$msg_no" ] &&
f_user_create_homedir "$user_name"
# Copy dotfiles if desired
[ "${user_dotfiles_create:-$msg_no}" != \
"$msg_no" ] && f_user_copy_dotfiles "$user_name"
break # to success
;;
1) # Login (prompt for new login name)
f_dialog_input_name input "$user_name" ||
continue
if f_quietly pw usershow -n "$input" -u 1337; then
f_show_err "$msg_login_already_used" "$input"
continue
fi
user_name="$input"
title="$msg_add $msg_user: $user_name"
user_home_dir="${homeprefix%/}/$user_name"
;;
2) # Full Name
f_dialog_input_gecos user_gecos "$user_gecos" &&
[ ! "$user_gecos" ] && null_gecos=1 ;;
3) # Password
f_dialog_input_password \
user_password user_password_disable ;;
4) # User ID
f_dialog_input_uid user_uid "$user_uid" ;;
5) # Group ID
f_dialog_input_gid user_gid "$user_gid" ;;
6) # Member of Groups
f_dialog_input_member_groups \
user_member_groups "$user_member_groups" &&
[ ! "$user_member_groups" ] &&
null_members=1 ;;
7) # Login Class
f_dialog_input_class user_class "$user_class" &&
[ ! "$user_class" ] && null_class=1 ;;
8) # Password Expires On
f_dialog_input_expire_password \
user_password_expire "$user_password_expire" &&
[ ! "$user_password_expire" ] &&
no_password_expire=1 ;;
9) # Account Expires On
f_dialog_input_expire_account \
user_account_expire "$user_account_expire" &&
[ ! "$user_account_expire" ] &&
no_account_expire=1 ;;
A) # Home Directory
f_dialog_input_home_dir \
user_home_dir "$user_home_dir" ;;
B) # Shell
f_dialog_input_shell user_shell "$user_shell" ;;
C) # Create Home Directory?
if [ "${user_home_create:-$msg_no}" != "$msg_no" ]
then
user_home_create="$msg_no"
else
user_home_create="$msg_yes"
fi ;;
D) # Create Dotfiles?
if [ "${user_dotfiles_create:-$msg_no}" != \
"$msg_no" ]
then
user_dotfiles_create="$msg_no"
else
user_dotfiles_create="$msg_yes"
fi ;;
esac
done
else
local var
for var in account_expire class gecos gid home_dir \
member_groups name password_expire shell uid \
; do
local _user_$var
eval f_shell_escape \"\$user_$var\" _user_$var
done
# Form the command
local cmd="pw useradd -n '$_user_name'"
[ "$user_gid" ] && cmd="$cmd -g '$_user_gid'"
[ "$user_home_dir" ] && cmd="$cmd -d '$_user_home_dir'"
[ "$user_shell" ] && cmd="$cmd -s '$_user_shell'"
[ "$user_uid" ] && cmd="$cmd -u '$_user_uid'"
[ "$user_account_expire" -o "$no_account_expire" ] &&
cmd="$cmd -e '$_user_account_expire'"
[ "$user_class" -o "$null_class" ] &&
cmd="$cmd -L '$_user_class'"
[ "$user_gecos" -o "$null_gecos" ] &&
cmd="$cmd -c '$_user_gecos'"
[ "$user_member_groups" -o "$null_members" ] &&
cmd="$cmd -G '$_user_member_groups'"
[ "$user_password_expire" -o "$no_password_expire" ] &&
cmd="$cmd -p '$_user_password_expire'"
# Execute the command
local retval err
if [ "$user_password_disable" ]; then
f_eval_catch -k err $funcname pw '%s -h -' "$cmd"
elif [ "$user_password" ]; then
err=$( echo "$user_password" | f_eval_catch -de \
$funcname pw '%s -h 0' "$cmd" 2>&1 )
else
f_eval_catch -k err $funcname pw '%s' "$cmd"
fi
retval=$?
if [ $retval -ne $SUCCESS ]; then
f_show_err "%s" "$err"
return $retval
fi
# Create home directory if desired
[ "${user_home_create:-$msg_no}" != "$msg_no" ] &&
f_user_create_homedir "$user_name"
# Copy dotfiles if desired
[ "${user_dotfiles_create:-$msg_no}" != "$msg_no" ] &&
f_user_copy_dotfiles "$user_name"
fi
f_dialog_title "$title"
$alert "$msg_login_added"
f_dialog_title_restore
[ "$no_confirm" -a "$USE_DIALOG" ] && sleep 1
return $SUCCESS
}
# f_user_delete [$user]
#
# Delete a user. If both $user (as a first argument) and $VAR_USER are unset or
# NULL and we are running interactively, prompt the end-user to select a user
# account from a list of those available. Variables that can be used to script
# user input:
#
# VAR_USER [Optional if running interactively]
# The user to delete. Ignored if given non-NULL first-argument.
#
# Returns success if the user account was successfully deleted.
#
f_user_delete()
{
local funcname=f_user_delete
local title # Calculated below
local alert=f_show_msg no_confirm=
f_getvar $VAR_NO_CONFIRM no_confirm
[ "$no_confirm" ] && alert=f_show_info
local input
f_getvar 3:-\$$VAR_USER input "$1"
if f_interactive && [ ! "$input" ]; then
f_dialog_menu_user_list || return $SUCCESS
f_dialog_menutag_fetch input
[ "$input" = "X $msg_exit" ] && return $SUCCESS
elif [ ! "$input" ]; then
f_show_err "$msg_no_user_specified"
return $FAILURE
fi
local user_account_expire user_class user_gecos user_gid user_home_dir
local user_member_groups user_name user_password user_password_expire
local user_shell user_uid # Variables created by f_input_user() below
if [ "$input" ] && ! f_input_user "$input"; then
f_show_err "$msg_login_not_found" "$input"
return $FAILURE
fi
local user_group_delete= user_home_delete=
f_getvar $VAR_USER_GROUP_DELETE:-\$msg_no user_group_delete
f_getvar $VAR_USER_HOME_DELETE:-\$msg_no user_home_delete
# Attempt to translate user GID into a group name
local user_group
if user_group=$( pw groupshow -g "$user_gid" 2> /dev/null ); then
user_group="${user_group%%:*}"
# Default to delete the primary group if no script-override and
# exists with same name as the user (same logic used by pw(8))
f_isset $VAR_USER_GROUP_DELETE ||
[ "$user_group" != "$user_name" ] ||
user_group_delete="$msg_yes"
fi
#
# Loop until the user decides to Exit, Cancel, or presses ESC
#
title="$msg_delete $msg_user: $user_name"
if f_interactive; then
local mtag retval defaultitem=
while :; do
f_dialog_title "$title"
f_dialog_menu_user_delete "$user_name" "$defaultitem"
retval=$?
f_dialog_title_restore
f_dialog_menutag_fetch mtag
f_dprintf "retval=%u mtag=[%s]" $retval "$mtag"
defaultitem="$mtag"
# Return if user either pressed ESC or chose Cancel/No
[ $retval -eq $DIALOG_OK ] || return $FAILURE
case "$mtag" in
X) # Delete/Exit
f_shell_escape "$user_uid" _user_uid
# Save group information in case pw(8) deletes it
# and we wanted to keep it (to be restored below)
if [ "${user_group_delete:-$msg_no}" = "$msg_no" ]
then
local v vars="gid members name password"
for v in $vars; do local group_$var; done
f_input_group "$user_group"
# Remove user-to-delete from group members
# NB: Otherwise group restoration could fail
local name length=0 _members=
while [ $length -ne ${#group_members} ]; do
name="${group_members%%,*}"
[ "$name" != "$user_name" ] &&
_members="$_members,$name"
length=${#group_members}
group_members="${group_members#*,}"
done
group_members="${_members#,}"
# Create escaped variables for f_eval_catch()
for v in $vars; do
local _group_$v
eval f_shell_escape \
\"\$group_$v\" _group_$v
done
fi
# Delete the user (if asked to delete home directory
# display [X]dialog notification to show activity)
local cmd="pw userdel -u '$_user_uid'"
if [ "$user_home_delete" = "$msg_yes" -a \
"$USE_XDIALOG" ]
then
local err
err=$(
exec 9>&1
f_eval_catch -e $funcname pw \
"%s -r" "$cmd" \
>&$DIALOG_TERMINAL_PASSTHRU_FD 2>&9 |
f_xdialog_info \
"$msg_deleting_home_directory"
)
[ ! "$err" ]
elif [ "$user_home_delete" = "$msg_yes" ]; then
f_dialog_info "$msg_deleting_home_directory"
f_eval_catch $funcname pw '%s -r' "$cmd"
else
f_eval_catch $funcname pw '%s' "$cmd"
fi || continue
#
# pw(8) may conditionally delete the primary group,
# which may not be what is desired.
#
# If we've been asked to delete the group and pw(8)
# chose not to, delete it. Otherwise, if we're told
# to NOT delete the group, we may need to restore it
# since pw(8) doesn't have a flag to tell `userdel'
# to not delete the group.
#
# NB: If primary group and user have different names
# the group may not have been deleted (again, see PR
# 169471 and SVN r263114 for details).
#
if [ "${user_group_delete:-$msg_no}" != "$msg_no" ]
then
f_quietly pw groupshow -g "$user_gid" &&
f_eval_catch $funcname pw \
"pw groupdel -g '%s'" "$_user_gid"
elif ! f_quietly pw groupshow -g "$group_gid" &&
[ "$group_name" -a "$group_gid" ]
then
# Group deleted by pw(8), so restore it
local cmd="pw groupadd -n '$_group_name'"
cmd="$cmd -g '$_group_gid'"
cmd="$cmd -M '$_group_members'"
# Get the group password (pw(8) groupshow does
# NOT provide this (even if running privileged)
local group_password_enc
group_password_enc=$( getent group | awk -F: '
!/^[[:space:]]*(#|$)/ && \
$1 == ENVIRON["group_name"] && \
$3 == ENVIRON["group_gid"] && \
$4 == ENVIRON["group_members"] \
{ print $2; exit }
' )
if [ "$group_password_enc" ]; then
echo "$group_password_enc" |
f_eval_catch $funcname \
pw '%s -H 0' "$cmd"
else
f_eval_catch $funcname \
pw '%s -h -' "$cmd"
fi
fi
break # to success
;;
1) # Login (select different login from list)
f_dialog_menu_user_list "$user_name" || continue
f_dialog_menutag_fetch mtag
[ "$mtag" = "X $msg_exit" ] && continue
if ! f_input_user "$mtag"; then
f_show_err "$msg_login_not_found" "$mtag"
# Attempt to fall back to previous selection
f_input_user "$input" || return $FAILURE
else
input="$mtag"
fi
title="$msg_delete $msg_user: $user_name"
;;
C) # Delete Primary Group?
if [ "${user_group_delete:-$msg_no}" != "$msg_no" ]
then
user_group_delete="$msg_no"
else
user_group_delete="$msg_yes"
fi ;;
D) # Delete Home Directory?
if [ "${user_home_delete:-$msg_no}" != "$msg_no" ]
then
user_home_delete="$msg_no"
else
user_home_delete="$msg_yes"
fi ;;
esac
done
else
f_shell_escape "$user_uid" _user_uid
# Save group information in case pw(8) deletes it
# and we wanted to keep it (to be restored below)
if [ "${user_group_delete:-$msg_no}" = "$msg_no" ]; then
local v vars="gid members name password"
for v in $vars; do local group_$v; done
f_input_group "$user_group"
# Remove user we're about to delete from group members
# NB: Otherwise group restoration could fail
local name length=0 _members=
while [ $length -ne ${#group_members} ]; do
name="${group_members%%,*}"
[ "$name" != "$user_name" ] &&
_members="$_members,$name"
length=${#group_members}
group_members="${group_members#*,}"
done
group_members="${_members#,}"
# Create escaped variables for later f_eval_catch()
for v in $vars; do
local _group_$v
eval f_shell_escape \"\$group_$v\" _group_$v
done
fi
# Delete the user (if asked to delete home directory
# display [X]dialog notification to show activity)
local err cmd="pw userdel -u '$_user_uid'"
if [ "$user_home_delete" = "$msg_yes" -a "$USE_XDIALOG" ]; then
err=$(
exec 9>&1
f_eval_catch -de $funcname pw \
'%s -r' "$cmd" 2>&9 | f_xdialog_info \
"$msg_deleting_home_directory"
)
[ ! "$err" ]
elif [ "$user_home_delete" = "$msg_yes" ]; then
f_dialog_info "$msg_deleting_home_directory"
f_eval_catch -k err $funcname pw '%s -r' "$cmd"
else
f_eval_catch -k err $funcname pw '%s' "$cmd"
fi
local retval=$?
if [ $retval -ne $SUCCESS ]; then
f_show_err "%s" "$err"
return $retval
fi
#
# pw(8) may conditionally delete the primary group, which may
# not be what is desired.
#
# If we've been asked to delete the group and pw(8) chose not
# to, delete it. Otherwise, if we're told to NOT delete the
# group, we may need to restore it since pw(8) doesn't have a
# flag to tell `userdel' to not delete the group.
#
# NB: If primary group and user have different names the group
# may not have been deleted (again, see PR 169471 and SVN
# r263114 for details).
#
if [ "${user_group_delete:-$msg_no}" != "$msg_no" ]
then
f_quietly pw groupshow -g "$user_gid" &&
f_eval_catch $funcname pw \
"pw groupdel -g '%s'" "$_user_gid"
elif ! f_quietly pw groupshow -g "$group_gid" &&
[ "$group_name" -a "$group_gid" ]
then
# Group deleted by pw(8), so restore it
local cmd="pw groupadd -n '$_group_name'"
cmd="$cmd -g '$_group_gid'"
cmd="$cmd -M '$_group_members'"
local group_password_enc
group_password_enc=$( getent group | awk -F: '
!/^[[:space:]]*(#|$)/ && \
$1 == ENVIRON["group_name"] && \
$3 == ENVIRON["group_gid"] && \
$4 == ENVIRON["group_members"] \
{ print $2; exit }
' )
if [ "$group_password_enc" ]; then
echo "$group_password_enc" |
f_eval_catch $funcname \
pw '%s -H 0' "$cmd"
else
f_eval_catch $funcname pw '%s -h -' "$cmd"
fi
fi
fi
f_dialog_title "$title"
$alert "$msg_login_deleted"
f_dialog_title_restore
[ "$no_confirm" -a "$USE_DIALOG" ] && sleep 1
return $SUCCESS
}
# f_user_edit [$user]
#
# Modify a login account. If both $user (as a first argument) and $VAR_USER are
# unset or NULL and we are running interactively, prompt the end-user to select
# a login account from a list of those available. Variables that can be used to
# script user input:
#
# VAR_USER [Optional if running interactively]
# The login to modify. Ignored if given non-NULL first-argument.
# VAR_USER_ACCOUNT_EXPIRE [Optional]
# The account expiration time. Format is similar to
# VAR_USER_PASSWORD_EXPIRE variable below. If unset, account
# expiry is unchanged. If set but NULL, account expiration is
# disabled (same as setting a value of `0').
# VAR_USER_DOTFILES_CREATE [Optional]
# If non-NULL, re-populate the user's home directory with the
# template files found in $udotdir (`/usr/share/skel' default).
# VAR_USER_GECOS [Optional]
# Often the full name of the account holder. If unset, the GECOS
# field is unmodified. If set but NULL, the field is blanked.
# VAR_USER_GID [Optional]
# Numerical primary-group ID to set. If NULL or unset, the group
# ID is unchanged.
# VAR_USER_GROUPS [Optional]
# Comma-separated list of additional groups to which the user is
# a member of. If set but NULL, group memberships are reset (this
# login will not be a member of any additional groups besides the
# primary group). If unset, group membership is unmodified.
# VAR_USER_HOME [Optional]
# The home directory to set. If NULL or unset, the home directory
# is unchanged.
# VAR_USER_HOME_CREATE [Optional]
# If non-NULL, create the user's home directory if it doesn't
# already exist.
# VAR_USER_LOGIN_CLASS [Optional]
# Login class to set. If unset, the login class is unchanged. If
# set but NULL, the field is blanked.
# VAR_USER_PASSWORD [Optional]
# Unencrypted password to set. If unset, the login password is
# unmodified. If set but NULL, password authentication for the
# login is disabled.
# VAR_USER_PASSWORD_EXPIRE [Optional]
# The password expiration time. Format of the date is either a
# UNIX time in decimal, or a date in dd-mmm-yy[yy] format, where
# dd is the day, mmm is the month in either numeric or alphabetic
# format, and yy[yy] is either a two or four digit year. This
# variable also accepts a relative date in the form of +n[mhdwoy]
# where n is a decimal, octal (leading 0) or hexadecimal (leading
# 0x) digit followed by the number of Minutes, Hours, Days,
# Weeks, Months or Years from the current date at which the
# expiration time is to be set. If unset, password expiry is
# unchanged. If set but NULL, password expiration is disabled
# (same as setting a value of `0').
# VAR_USER_SHELL [Optional]
# Path to login shell to set. If NULL or unset, the shell is
# unchanged.
# VAR_USER_UID [Optional]
# Numerical user ID to set. If NULL or unset, the user ID is
# unchanged.
#
# Returns success if the user account was successfully modified.
#
f_user_edit()
{
local funcname=f_user_edit
local title # Calculated below
local alert=f_show_msg no_confirm=
f_getvar $VAR_NO_CONFIRM no_confirm
[ "$no_confirm" ] && alert=f_show_info
local input
f_getvar 3:-\$$VAR_USER input "$1"
#
# NB: pw(8) has a ``feature'' wherein `-n name' can be taken as UID
# instead of name. Work-around is to also pass `-u UID' at the same
# time (the UID is ignored in this case, so any UID will do).
#
if [ "$input" ] && ! f_quietly pw usershow -n "$input" -u 1337; then
f_show_err "$msg_login_not_found" "$input"
return $FAILURE
fi
if f_interactive && [ ! "$input" ]; then
f_dialog_menu_user_list || return $SUCCESS
f_dialog_menutag_fetch input
[ "$input" = "X $msg_exit" ] && return $SUCCESS
elif [ ! "$input" ]; then
f_show_err "$msg_no_user_specified"
return $FAILURE
fi
local user_account_expire user_class user_gecos user_gid user_home_dir
local user_member_groups user_name user_password user_password_expire
local user_shell user_uid # Variables created by f_input_user() below
if ! f_input_user "$input"; then
f_show_err "$msg_login_not_found" "$input"
return $FAILURE
fi
#
# Override values probed by f_input_user() with desired values
#
f_isset $VAR_USER_GID && f_getvar $VAR_USER_GID user_gid
f_isset $VAR_USER_HOME && f_getvar $VAR_USER_HOME user_home_dir
f_isset $VAR_USER_SHELL && f_getvar $VAR_USER_SHELL user_shell
f_isset $VAR_USER_UID && f_getvar $VAR_USER_UID user_uid
local user_dotfiles_create= user_home_create=
f_getvar $VAR_USER_DOTFILES_CREATE:+\$msg_yes user_dotfiles_create
f_getvar $VAR_USER_HOME_CREATE:+\$msg_yes user_home_create
local no_account_expire=
if f_isset $VAR_USER_ACCOUNT_EXPIRE; then
f_getvar $VAR_USER_ACCOUNT_EXPIRE user_account_expire
[ "$user_account_expire" ] || no_account_expire=1
fi
local null_gecos=
if f_isset $VAR_USER_GECOS; then
f_getvar $VAR_USER_GECOS user_gecos
[ "$user_gecos" ] || null_gecos=1
fi
local null_members=
if f_isset $VAR_USER_GROUPS; then
f_getvar $VAR_USER_GROUPS user_member_groups
[ "$user_member_groups" ] || null_members=1
fi
local null_class=
if f_isset $VAR_USER_LOGIN_CLASS; then
f_getvar $VAR_USER_LOGIN_CLASS user_class
[ "$user_class" ] || null_class=1
fi
local user_password_disable=
if f_isset $VAR_USER_PASSWORD; then
f_getvar $VAR_USER_PASSWORD user_password
[ "$user_password" ] || user_password_disable=1
fi
local no_password_expire=
if f_isset $VAR_USER_PASSWORD_EXPIRE; then
f_getvar $VAR_USER_PASSWORD_EXPIRE user_password_expire
[ "$user_password_expire" ] || no_password_expire=1
fi
#
# Loop until the user decides to Exit, Cancel, or presses ESC
#
title="$msg_edit_view $msg_user: $user_name"
if f_interactive; then
local mtag retval defaultitem=
while :; do
f_dialog_title "$title"
f_dialog_menu_user_edit "$defaultitem"
retval=$?
f_dialog_title_restore
f_dialog_menutag_fetch mtag
f_dprintf "retval=%u mtag=[%s]" $retval "$mtag"
defaultitem="$mtag"
# Return if user either pressed ESC or chose Cancel/No
[ $retval -eq $DIALOG_OK ] || return $FAILURE
case "$mtag" in
X) # Save/Exit
local var
for var in account_expire class gecos gid home_dir \
member_groups name password_expire shell uid \
; do
local _user_$var
eval f_shell_escape \"\$user_$var\" _user_$var
done
local cmd="pw usermod -n '$_user_name'"
[ "$user_gid" ] && cmd="$cmd -g '$_user_gid'"
[ "$user_shell" ] && cmd="$cmd -s '$_user_shell'"
[ "$user_uid" ] && cmd="$cmd -u '$_user_uid'"
[ "$user_account_expire" -o \
"$no_account_expire" ] &&
cmd="$cmd -e '$_user_account_expire'"
[ "$user_class" -o "$null_class" ] &&
cmd="$cmd -L '$_user_class'"
[ "$user_gecos" -o "$null_gecos" ] &&
cmd="$cmd -c '$_user_gecos'"
[ "$user_home_dir" ] &&
cmd="$cmd -d '$_user_home_dir'"
[ "$user_member_groups" -o "$null_members" ] &&
cmd="$cmd -G '$_user_member_groups'"
[ "$user_password_expire" -o \
"$no_password_expire" ] &&
cmd="$cmd -p '$_user_password_expire'"
# Execute the command
if [ "$user_password_disable" ]; then
f_eval_catch $funcname pw '%s -h -' "$cmd"
elif [ "$user_password" ]; then
echo "$user_password" | f_eval_catch \
$funcname pw '%s -h 0' "$cmd"
else
f_eval_catch $funcname pw '%s' "$cmd"
fi || continue
# Create home directory if desired
[ "${user_home_create:-$msg_no}" != "$msg_no" ] &&
f_user_create_homedir "$user_name"
# Copy dotfiles if desired
[ "${user_dotfiles_create:-$msg_no}" != \
"$msg_no" ] && f_user_copy_dotfiles "$user_name"
break # to success
;;
1) # Login (select different login from list)
f_dialog_menu_user_list "$user_name" || continue
f_dialog_menutag_fetch mtag
[ "$mtag" = "X $msg_exit" ] && continue
if ! f_input_user "$mtag"; then
f_show_err "$msg_login_not_found" "$mtag"
# Attempt to fall back to previous selection
f_input_user "$input" || return $FAILURE
else
input="$mtag"
fi
title="$msg_edit_view $msg_user: $user_name"
;;
2) # Full Name
f_dialog_input_gecos user_gecos "$user_gecos" &&
[ ! "$user_gecos" ] && null_gecos=1 ;;
3) # Password
f_dialog_input_password \
user_password user_password_disable ;;
4) # User ID
f_dialog_input_uid user_uid "$user_uid" ;;
5) # Group ID
f_dialog_input_gid user_gid "$user_gid" ;;
6) # Member of Groups
f_dialog_input_member_groups \
user_member_groups "$user_member_groups" &&
[ ! "$user_member_groups" ] &&
null_members=1 ;;
7) # Login Class
f_dialog_input_class user_class "$user_class" &&
[ ! "$user_class" ] && null_class=1 ;;
8) # Password Expires On
f_dialog_input_expire_password \
user_password_expire "$user_password_expire" &&
[ ! "$user_password_expire" ] &&
no_password_expire=1 ;;
9) # Account Expires On
f_dialog_input_expire_account \
user_account_expire "$user_account_expire" &&
[ ! "$user_account_expire" ] &&
no_account_expire=1 ;;
A) # Home Directory
f_dialog_input_home_dir \
user_home_dir "$user_home_dir" ;;
B) # Shell
f_dialog_input_shell user_shell "$user_shell" ;;
C) # Create Home Directory?
if [ "${user_home_create:-$msg_no}" != "$msg_no" ]
then
user_home_create="$msg_no"
else
user_home_create="$msg_yes"
fi ;;
D) # Create Dotfiles?
if [ "${user_dotfiles_create:-$msg_no}" != \
"$msg_no" ]
then
user_dotfiles_create="$msg_no"
else
user_dotfiles_create="$msg_yes"
fi ;;
esac
done
else
local var
for var in account_expire class gecos gid home_dir \
member_groups name password_expire shell uid \
; do
local _user_$var
eval f_shell_escape \"\$user_$var\" _user_$var
done
# Form the command
local cmd="pw usermod -n '$_user_name'"
[ "$user_gid" ] && cmd="$cmd -g '$_user_gid'"
[ "$user_home_dir" ] && cmd="$cmd -d '$_user_home_dir'"
[ "$user_shell" ] && cmd="$cmd -s '$_user_shell'"
[ "$user_uid" ] && cmd="$cmd -u '$_user_uid'"
[ "$user_account_expire" -o "$no_account_expire" ] &&
cmd="$cmd -e '$_user_account_expire'"
[ "$user_class" -o "$null_class" ] &&
cmd="$cmd -L '$_user_class'"
[ "$user_gecos" -o "$null_gecos" ] &&
cmd="$cmd -c '$_user_gecos'"
[ "$user_member_groups" -o "$null_members" ] &&
cmd="$cmd -G '$_user_member_groups'"
[ "$user_password_expire" -o "$no_password_expire" ] &&
cmd="$cmd -p '$_user_password_expire'"
# Execute the command
local retval err
if [ "$user_password_disable" ]; then
f_eval_catch -k err $funcname pw '%s -h -' "$cmd"
elif [ "$user_password" ]; then
err=$( echo "$user_password" | f_eval_catch -de \
$funcname pw '%s -h 0' "$cmd" 2>&1 )
else
f_eval_catch -k err $funcname pw '%s' "$cmd"
fi
retval=$?
if [ $retval -ne $SUCCESS ]; then
f_show_err "%s" "$err"
return $retval
fi
# Create home directory if desired
[ "${user_home_create:-$msg_no}" != "$msg_no" ] &&
f_user_create_homedir "$user_name"
# Copy dotfiles if desired
[ "${user_dotfiles_create:-$msg_no}" != "$msg_no" ] &&
f_user_copy_dotfiles "$user_name"
fi
f_dialog_title "$title"
$alert "$msg_login_updated"
f_dialog_title_restore
[ "$no_confirm" -a "$USE_DIALOG" ] && sleep 1
return $SUCCESS
}
############################################################ MAIN
f_dprintf "%s: Successfully loaded." usermgmt/user.subr
fi # ! $_USERMGMT_USER_SUBR