user.subr revision 279615
153494Sdillonif [ ! "$_USERMGMT_USER_SUBR" ]; then _USERMGMT_USER_SUBR=1
253494Sdillon#
353494Sdillon# Copyright (c) 2012 Ron McDowell
453494Sdillon# Copyright (c) 2012-2014 Devin Teske
553494Sdillon# All rights reserved.
653494Sdillon#
753494Sdillon# Redistribution and use in source and binary forms, with or without
853494Sdillon# modification, are permitted provided that the following conditions
953494Sdillon# are met:
1053494Sdillon# 1. Redistributions of source code must retain the above copyright
1153494Sdillon#    notice, this list of conditions and the following disclaimer.
1253494Sdillon# 2. Redistributions in binary form must reproduce the above copyright
1353494Sdillon#    notice, this list of conditions and the following disclaimer in the
1453494Sdillon#    documentation and/or other materials provided with the distribution.
1553494Sdillon#
1653494Sdillon# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
1753494Sdillon# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
1853494Sdillon# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
1953494Sdillon# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
2053494Sdillon# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2153494Sdillon# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2253494Sdillon# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2353494Sdillon# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2453494Sdillon# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2553494Sdillon# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
2653494Sdillon# SUCH DAMAGE.
2753494Sdillon#
2853494Sdillon# $FreeBSD: stable/10/usr.sbin/bsdconfig/usermgmt/share/user.subr 279615 2015-03-05 00:41:03Z dteske $
2953494Sdillon#
3053494Sdillon############################################################ INCLUDES
3153494Sdillon
3253494SdillonBSDCFG_SHARE="/usr/share/bsdconfig"
33107788Sru. $BSDCFG_SHARE/common.subr || exit 1
3453494Sdillonf_dprintf "%s: loading includes..." usermgmt/user.subr
3568965Sruf_include $BSDCFG_SHARE/dialog.subr
3653494Sdillonf_include $BSDCFG_SHARE/strings.subr
3753494Sdillonf_include $BSDCFG_SHARE/usermgmt/group_input.subr
3853494Sdillonf_include $BSDCFG_SHARE/usermgmt/user_input.subr
3953494Sdillon
4053494SdillonBSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="070.usermgmt"
4153494Sdillonf_include_lang $BSDCFG_LIBE/$APP_DIR/include/messages.subr
4299968Scharnier
4353494Sdillon############################################################ CONFIGURATION
4499968Scharnier
4553494Sdillon# set some reasonable defaults if /etc/adduser.conf does not exist.
4653494Sdillon[ -f /etc/adduser.conf ] && f_include /etc/adduser.conf
4755459Sphantom: ${defaultclass:=""}
4855459Sphantom: ${defaultshell:="/bin/sh"}
4955459Sphantom: ${homeprefix:="/home"}
5055459Sphantom: ${passwdtype:="yes"}
5155459Sphantom: ${udotdir:="/usr/share/skel"}
5253494Sdillon: ${uexpire:=""}
5357673Ssheldonh	# Default account expire time. Format is similar to upwexpire variable.
5457673Ssheldonh: ${ugecos:="User &"}
5553494Sdillon: ${upwexpire:=""}
5657673Ssheldonh	# The default password expiration time. Format of the date is either a
5757673Ssheldonh	# UNIX time in decimal, or a date in dd-mmm-yy[yy] format, where dd is
5853494Sdillon	# the day, mmm is the month in either numeric or alphabetic format, and
5957673Ssheldonh	# yy[yy] is either a two or four digit year. This variable also accepts
6057673Ssheldonh	# a relative date in the form of n[mhdwoy] where n is a decimal, octal
6153494Sdillon	# (leading 0) or hexadecimal (leading 0x) digit followed by the number
6299968Scharnier	# of Minutes, Hours, Days, Weeks, Months or Years from the current date
6353494Sdillon	# at which the expiration time is to be set.
6453494Sdillon
6553494Sdillon#
6655459Sphantom# uexpire and upwexpire from adduser.conf(5) differ only slightly from what
6753494Sdillon# pw(8) accepts as `date' argument(s); pw(8) requires a leading `+' for the
6853494Sdillon# relative date syntax (n[mhdwoy]).
6957673Ssheldonh#
7057673Ssheldonhcase "$uexpire" in *[mhdwoy])
7153494Sdillon	f_isinteger "${uexpire%[mhdwoy]}" && uexpire="+$uexpire"
7299968Scharnieresac
7353494Sdilloncase "$upwexpire" in *[mhdwoy])
7453494Sdillon	f_isinteger "${upwexpire%[mhdwoy]}" && upwexpire="+$upwexpire"
7553494Sdillonesac
7653494Sdillon
7755459Sphantom############################################################ FUNCTIONS
7853494Sdillon
7953494Sdillon# f_user_create_homedir $user
8053494Sdillon#
8155459Sphantom# Create home directory for $user.
8255459Sphantom#
8355459Sphantomf_user_create_homedir()
8457673Ssheldonh{
8557673Ssheldonh	local funcname=f_user_create_homedir
8656038Sgreen	local user="$1"
8753494Sdillon
8857673Ssheldonh	[ "$user" ] || return $FAILURE
8957673Ssheldonh
9053494Sdillon	local user_account_expire user_class user_gecos user_gid user_home_dir
91107788Sru	local user_member_groups user_name user_password user_password_expire
92107788Sru	local user_shell user_uid # Variables created by f_input_user() below
9353494Sdillon	f_input_user "$user" || return $FAILURE
9457782Ssheldonh
9557782Ssheldonh	f_dprintf "Creating home directory \`%s' for user \`%s'" \
9657673Ssheldonh	          "$user_home_dir" "$user"
97107788Sru
98141846Sru	local _user_gid _user_home_dir _user_uid
9957673Ssheldonh	f_shell_escape "$user_gid"      _user_gid
10053494Sdillon	f_shell_escape "$user_home_dir" _user_home_dir
10153494Sdillon	f_shell_escape "$user_uid"      _user_uid
10257673Ssheldonh	f_eval_catch $funcname mkdir "mkdir -p '%s'" "$_user_home_dir" ||
10357673Ssheldonh		return $FAILURE
10453494Sdillon	f_eval_catch $funcname chown "chown '%i:%i' '%s'" \
10553494Sdillon		"$_user_uid" "$_user_gid" "$_user_home_dir" || return $FAILURE
10653494Sdillon}
10753494Sdillon
10853494Sdillon# f_user_copy_dotfiles $user
10953494Sdillon#
11053494Sdillon# Copy `skel' dot-files from $udotdir (global inherited from /etc/adduser.conf)
11153494Sdillon# to the home-directory of $user. Attempts to create the home-directory first
11253494Sdillon# if it doesn't exist.
11353494Sdillon#
114107788Sruf_user_copy_dotfiles()
11553494Sdillon{
116140442Sru	local funcname=f_user_copy_dotfiles
117140442Sru	local user="$1"
118140442Sru
119140442Sru	[ "$udotdir" ] || return $FAILURE
12053494Sdillon	[ "$user"    ] || return $FAILURE
12153494Sdillon
12253494Sdillon	local user_account_expire user_class user_gecos user_gid user_home_dir
12399968Scharnier	local user_member_groups user_name user_password user_password_expire
12453494Sdillon	local user_shell user_uid # Variables created by f_input_user() below
12553494Sdillon	f_input_user "$user" || return $FAILURE
12653494Sdillon
127	f_dprintf "Copying dot-files from \`%s' to \`%s'" \
128	          "$udotdir" "$user_home_dir"
129
130	# Attempt to create the home directory if it doesn't exist
131	[ -d "$user_home_dir" ] ||
132		f_user_create_homedir "$user" || return $FAILURE
133
134	local _user_gid _user_home_dir _user_uid
135	f_shell_escape "$user_gid"      _user_gid
136	f_shell_escape "$user_home_dir" _user_home_dir
137	f_shell_escape "$user_uid"      _user_uid
138
139	local - # Localize `set' to this function
140	set +f # Enable glob pattern-matching for paths
141	cd "$udotdir" || return $FAILURE
142
143	local _file file retval
144	for file in dot.*; do
145		[ -e "$file" ] || continue # no-match
146
147		f_shell_escape "$file" "_file"
148		f_eval_catch $funcname cp "cp -n '%s' '%s'" \
149			"$_file" "$_user_home_dir/${_file#dot}"
150		retval=$?
151		[ $retval -eq $SUCCESS ] || break
152		f_eval_catch $funcname chown \
153			"chown -h '%i:%i' '%s'" \
154			"$_user_uid" "$_user_gid" \
155			"$_user_home_dir/${_file#dot}"
156		retval=$?
157		[ $retval -eq $SUCCESS ] || break
158	done
159
160	cd -
161	return $retval
162}
163
164# f_user_add [$user]
165#
166# Create a login account. If both $user (as a first argument) and $VAR_USER are
167# unset or NULL and we are running interactively, prompt the end-user to enter
168# the name of a new login account and (if $VAR_NO_CONFIRM is unset or NULL)
169# prompt the end-user to answer some questions about the new account. Variables
170# that can be used to script user input:
171#
172# 	VAR_USER [Optional if running interactively]
173# 		The login to add. Ignored if given non-NULL first-argument.
174# 	VAR_USER_ACCOUNT_EXPIRE [Optional]
175# 		The account expiration time. Format is similar to
176# 		VAR_USER_PASSWORD_EXPIRE variable below. Default is to never
177# 		expire the account.
178# 	VAR_USER_DOTFILES_CREATE [Optional]
179# 		If non-NULL, populate the user's home directory with the
180# 		template files found in $udotdir (`/usr/share/skel' default).
181# 	VAR_USER_GECOS [Optional]
182# 		Often the full name of the account holder. Default is NULL.
183# 	VAR_USER_GID [Optional]
184# 		Numerical primary-group ID to use. If NULL or unset, the group
185# 		ID is automatically chosen.
186# 	VAR_USER_GROUPS [Optional]
187# 		Comma-separated list of additional groups to which the user is
188# 		a member of. Default is NULL (no additional groups).
189# 	VAR_USER_HOME [Optional]
190# 		The home directory to set. If NULL or unset, the home directory
191# 		is automatically calculated.
192# 	VAR_USER_HOME_CREATE [Optional]
193# 		If non-NULL, create the user's home directory if it doesn't
194# 		already exist.
195# 	VAR_USER_LOGIN_CLASS [Optional]
196# 		Login class to use when creating the login. Default is NULL.
197# 	VAR_USER_PASSWORD [Optional]
198# 		Unencrypted password to use. If unset or NULL, password
199# 		authentication for the login is disabled.
200# 	VAR_USER_PASSWORD_EXPIRE [Optional]
201# 		The password expiration time. Format of the date is either a
202# 		UNIX time in decimal, or a date in dd-mmm-yy[yy] format, where
203# 		dd is the day, mmm is the month in either numeric or alphabetic
204# 		format, and yy[yy] is either a two or four digit year. This
205# 		variable also accepts a relative date in the form of +n[mhdwoy]
206# 		where n is a decimal, octal (leading 0) or hexadecimal (leading
207# 		0x) digit followed by the number of Minutes, Hours, Days,
208# 		Weeks, Months or Years from the current date at which the
209# 		expiration time is to be set. Default is to never expire the
210# 		account password.
211# 	VAR_USER_SHELL [Optional]
212# 		Path to login shell to use. Default is `/bin/sh'.
213# 	VAR_USER_UID [Optional]
214# 		Numerical user ID to use. If NULL or unset, the user ID is
215# 		automatically chosen.
216#
217# Returns success if the user account was successfully created.
218#
219f_user_add()
220{
221	local funcname=f_user_add
222	local title # Calculated below
223	local alert=f_show_msg no_confirm=
224
225	f_getvar $VAR_NO_CONFIRM no_confirm
226	[ "$no_confirm" ] && alert=f_show_info
227
228	local input
229	f_getvar 3:-\$$VAR_USER input "$1"
230
231	#
232	# NB: pw(8) has a ``feature'' wherein `-n name' can be taken as UID
233	# instead of name. Work-around is to also pass `-u UID' at the same
234	# time (any UID will do; but `-1' is appropriate for this context).
235	#
236	if [ "$input" ] && f_quietly pw usershow -n "$input" -u -1; then
237		f_show_err "$msg_login_already_used" "$input"
238		return $FAILURE
239	fi
240
241	local user_name="$input"
242	while f_interactive && [ ! "$user_name" ]; do
243		f_dialog_input_name user_name "$user_name" ||
244			return $SUCCESS
245		[ "$user_name" ] ||
246			f_show_err "$msg_please_enter_a_user_name"
247	done
248	if [ ! "$user_name" ]; then
249		f_show_err "$msg_no_user_specified"
250		return $FAILURE
251	fi
252
253	local user_account_expire user_class user_gecos user_gid user_home_dir
254	local user_member_groups user_password user_password_expire user_shell
255	local user_uid user_dotfiles_create= user_home_create=
256	f_getvar $VAR_USER_ACCOUNT_EXPIRE-\$uexpire    user_account_expire
257	f_getvar $VAR_USER_DOTFILES_CREATE:+\$msg_yes  user_dotfiles_create
258	f_getvar $VAR_USER_GECOS-\$ugecos              user_gecos
259	f_getvar $VAR_USER_GID                         user_gid
260	f_getvar $VAR_USER_GROUPS                      user_member_groups
261	f_getvar $VAR_USER_HOME:-\${homeprefix%/}/\$user_name \
262	                                               user_home_dir
263	f_getvar $VAR_USER_HOME_CREATE:+\$msg_yes      user_home_create
264	f_getvar $VAR_USER_LOGIN_CLASS-\$defaultclass  user_class
265	f_getvar $VAR_USER_PASSWORD                    user_password
266	f_getvar $VAR_USER_PASSWORD_EXPIRE-\$upwexpire user_password_expire
267	f_getvar $VAR_USER_SHELL-\$defaultshell        user_shell
268	f_getvar $VAR_USER_UID                         user_uid
269
270	# Create home-dir if no script-override and does not exist
271	f_isset $VAR_USER_HOME_CREATE || [ -d "$user_home_dir" ] ||
272		user_home_create="$msg_yes"
273	# Copy dotfiles if home-dir creation is desired, does not yet exist,
274	# and no script-override has been set
275	f_isset $VAR_USER_DOTFILES_CREATE ||
276		[ "$user_home_create" != "$msg_yes" ] ||
277		[ -d "$user_home_dir" ] || user_dotfiles_create="$msg_yes"
278	# Create home-dir if copying dotfiles but home-dir does not exist
279	[ "$user_dotfiles_create" -a ! -d "$user_home_dir" ] &&
280		user_home_create="$msg_yes"
281
282	# Set flags for meaningful NULL values if-provided
283	local no_account_expire= no_password_expire= null_gecos= null_members=
284	local user_password_disable=
285	f_isset $VAR_USER_ACCOUNT_EXPIRE &&
286		[ ! "$user_account_expire"  ] && no_account_expire=1
287	f_isset $VAR_USER_GECOS &&
288		[ ! "$user_gecos"           ] && null_gecos=1
289	f_isset $VAR_USER_GROUPS &&
290		[ ! "$user_member_groups"   ] && null_members=1
291	f_isset $VAR_USER_PASSWORD &&
292		[ ! "$user_password"        ] && user_password_disable=1
293	f_isset $VAR_USER_PASSWORD_EXPIRE &&
294		[ ! "$user_password_expire" ] && no_password_expire=1
295
296	if f_interactive && [ ! "$no_confirm" ]; then
297		f_dialog_noyes \
298			"$msg_use_default_values_for_all_account_details"
299		retval=$?
300		if [ $retval -eq $DIALOG_ESC ]; then
301			return $SUCCESS
302		elif [ $retval -ne $DIALOG_OK ]; then
303			#
304			# Ask series of questions to pre-fill the editor screen
305			#
306			# Defaults used in each dialog should allow the user to
307			# simply hit ENTER to proceed, because cancelling any
308			# single dialog will cause them to be returned to the
309			# previous menu.
310			#
311
312			f_dialog_input_gecos user_gecos "$user_gecos" ||
313				return $FAILURE
314			if [ "$passwdtype" = "yes" ]; then
315				f_dialog_input_password user_password \
316					user_password_disable ||
317					return $FAILURE
318			fi
319			f_dialog_input_uid user_uid "$user_uid" ||
320				return $FAILURE
321			f_dialog_input_gid user_gid "$user_gid" ||
322				return $FAILURE
323			f_dialog_input_member_groups user_member_groups \
324				"$user_member_groups" || return $FAILURE
325			f_dialog_input_class user_class "$user_class" ||
326				return $FAILURE
327			f_dialog_input_expire_password user_password_expire \
328				"$user_password_expire" || return $FAILURE
329			f_dialog_input_expire_account user_account_expire \
330				"$user_account_expire" || return $FAILURE
331			f_dialog_input_home_dir user_home_dir \
332				"$user_home_dir" || return $FAILURE
333			if [ ! -d "$user_home_dir" ]; then
334				f_dialog_input_home_create user_home_create ||
335					return $FAILURE
336				if [ "$user_home_create" = "$msg_yes" ]; then
337					f_dialog_input_dotfiles_create \
338						user_dotfiles_create ||
339						return $FAILURE
340				fi
341			fi
342			f_dialog_input_shell user_shell "$user_shell" ||
343				return $FAILURE
344		fi
345	fi
346
347	#
348	# Loop until the user decides to Exit, Cancel, or presses ESC
349	#
350	title="$msg_add $msg_user: $user_name"
351	if f_interactive; then
352		local mtag retval defaultitem=
353		while :; do
354			f_dialog_title "$title"
355			f_dialog_menu_user_add "$defaultitem"
356			retval=$?
357			f_dialog_title_restore
358			f_dialog_menutag_fetch mtag
359			f_dprintf "retval=%u mtag=[%s]" $retval "$mtag"
360			defaultitem="$mtag"
361
362			# Return if user either pressed ESC or chose Cancel/No
363			[ $retval -eq $DIALOG_OK ] || return $FAILURE
364
365			case "$mtag" in
366			X) # Add/Exit
367			   local var
368			   for var in account_expire class gecos gid home_dir \
369			   	member_groups name password_expire shell uid \
370			   ; do
371			   	local _user_$var
372			   	eval f_shell_escape \"\$user_$var\" _user_$var
373			   done
374
375			   local cmd="pw useradd -n '$_user_name'"
376			   [ "$user_gid"   ] && cmd="$cmd -g '$_user_gid'"
377			   [ "$user_shell" ] && cmd="$cmd -s '$_user_shell'"
378			   [ "$user_uid"   ] && cmd="$cmd -u '$_user_uid'"
379			   [ "$user_account_expire" -o \
380			     "$no_account_expire" ] &&
381			   	cmd="$cmd -e '$_user_account_expire'"
382			   [ "$user_class" -o "$null_class" ] &&
383			   	cmd="$cmd -L '$_user_class'"
384			   [ "$user_gecos" -o "$null_gecos" ] &&
385			   	cmd="$cmd -c '$_user_gecos'"
386			   [ "$user_home_dir" ] &&
387			   	cmd="$cmd -d '$_user_home_dir'"
388			   [ "$user_member_groups" ] &&
389			   	cmd="$cmd -G '$_user_member_groups'"
390			   [ "$user_password_expire" -o \
391			     "$no_password_expire" ] &&
392			   	cmd="$cmd -p '$_user_password_expire'"
393
394			   # Execute the command
395			   if [ "$user_password_disable" ]; then
396			   	f_eval_catch $funcname pw '%s -h -' "$cmd"
397			   elif [ "$user_password" ]; then
398			   	echo "$user_password" | f_eval_catch \
399			   		$funcname pw '%s -h 0' "$cmd"
400			   else
401			   	f_eval_catch $funcname pw '%s' "$cmd"
402			   fi || continue
403
404			   # Create home directory if desired
405			   [ "${user_home_create:-$msg_no}" != "$msg_no" ] &&
406			   	f_user_create_homedir "$user_name"
407
408			   # Copy dotfiles if desired
409			   [ "${user_dotfiles_create:-$msg_no}" != \
410			     "$msg_no" ] && f_user_copy_dotfiles "$user_name"
411
412			   break # to success
413			   ;;
414			1) # Login (prompt for new login name)
415			   f_dialog_input_name input "$user_name" ||
416			   	continue
417			   if f_quietly pw usershow -n "$input" -u -1; then
418			   	f_show_err "$msg_login_already_used" "$input"
419			   	continue
420			   fi
421			   user_name="$input"
422			   title="$msg_add $msg_user: $user_name"
423			   user_home_dir="${homeprefix%/}/$user_name"
424			   ;;
425			2) # Full Name
426			   f_dialog_input_gecos user_gecos "$user_gecos" &&
427			   	[ ! "$user_gecos" ] && null_gecos=1 ;;
428			3) # Password
429			   f_dialog_input_password \
430			   	user_password user_password_disable ;;
431			4) # User ID
432			   f_dialog_input_uid user_uid "$user_uid" ;;
433			5) # Group ID
434			   f_dialog_input_gid user_gid "$user_gid" ;;
435			6) # Member of Groups
436			   f_dialog_input_member_groups \
437			   	user_member_groups "$user_member_groups" &&
438			   	[ ! "$user_member_groups" ] &&
439			   	null_members=1 ;;
440			7) # Login Class
441			   f_dialog_input_class user_class "$user_class" &&
442			   	[ ! "$user_class" ] && null_class=1 ;;
443			8) # Password Expires On
444			   f_dialog_input_expire_password \
445			   	user_password_expire "$user_password_expire" &&
446			   	[ ! "$user_password_expire" ] &&
447			   	no_password_expire=1 ;;
448			9) # Account Expires On
449			   f_dialog_input_expire_account \
450			   	user_account_expire "$user_account_expire" &&
451			   	[ ! "$user_account_expire" ] &&
452			   	no_account_expire=1 ;;
453			A) # Home Directory
454			   f_dialog_input_home_dir \
455			   	user_home_dir "$user_home_dir" ;;
456			B) # Shell
457			   f_dialog_input_shell user_shell "$user_shell" ;;
458			C) # Create Home Directory?
459			   if [ "${user_home_create:-$msg_no}" != "$msg_no" ]
460			   then
461			   	user_home_create="$msg_no"
462			   else
463			   	user_home_create="$msg_yes"
464			   fi ;;
465			D) # Create Dotfiles?
466			   if [ "${user_dotfiles_create:-$msg_no}" != \
467			        "$msg_no" ]
468			   then
469			   	user_dotfiles_create="$msg_no"
470			   else
471			   	user_dotfiles_create="$msg_yes"
472			   fi ;;
473			esac
474		done
475	else
476		local var
477		for var in account_expire class gecos gid home_dir \
478			member_groups name password_expire shell uid \
479		; do
480			local _user_$var
481			eval f_shell_escape \"\$user_$var\" _user_$var
482		done
483
484		# Form the command
485		local cmd="pw useradd -n '$_user_name'"
486		[ "$user_gid"      ] && cmd="$cmd -g '$_user_gid'"
487		[ "$user_home_dir" ] && cmd="$cmd -d '$_user_home_dir'"
488		[ "$user_shell"    ] && cmd="$cmd -s '$_user_shell'"
489		[ "$user_uid"      ] && cmd="$cmd -u '$_user_uid'"
490		[ "$user_account_expire" -o "$no_account_expire" ] &&
491			cmd="$cmd -e '$_user_account_expire'"
492		[ "$user_class" -o "$null_class" ] &&
493			cmd="$cmd -L '$_user_class'"
494		[ "$user_gecos" -o "$null_gecos" ] &&
495			cmd="$cmd -c '$_user_gecos'"
496		[ "$user_member_groups" -o "$null_members" ] &&
497			cmd="$cmd -G '$_user_member_groups'"
498		[ "$user_password_expire" -o "$no_password_expire" ] &&
499			cmd="$cmd -p '$_user_password_expire'"
500
501		# Execute the command
502		local retval err
503		if [ "$user_password_disable" ]; then
504			f_eval_catch -k err $funcname pw '%s -h -' "$cmd"
505		elif [ "$user_password" ]; then
506			err=$( echo "$user_password" | f_eval_catch -de \
507				$funcname pw '%s -h 0' "$cmd" 2>&1 )
508		else
509			f_eval_catch -k err $funcname pw '%s' "$cmd"
510		fi
511		retval=$?
512		if [ $retval -ne $SUCCESS ]; then
513			f_show_err "%s" "$err"
514			return $retval
515		fi
516
517		# Create home directory if desired
518		[ "${user_home_create:-$msg_no}" != "$msg_no" ] &&
519			f_user_create_homedir "$user_name"
520
521		# Copy dotfiles if desired
522		[ "${user_dotfiles_create:-$msg_no}" != "$msg_no" ] &&
523			f_user_copy_dotfiles "$user_name"
524	fi
525
526	f_dialog_title "$title"
527	$alert "$msg_login_added"
528	f_dialog_title_restore
529	[ "$no_confirm" -a "$USE_DIALOG" ] && sleep 1
530
531	return $SUCCESS
532}
533
534# f_user_delete [$user]
535#
536# Delete a user. If both $user (as a first argument) and $VAR_USER are unset or
537# NULL and we are running interactively, prompt the end-user to select a user
538# account from a list of those available. Variables that can be used to script
539# user input:
540#
541# 	VAR_USER [Optional if running interactively]
542# 		The user to delete. Ignored if given non-NULL first-argument.
543#
544# Returns success if the user account was successfully deleted.
545#
546f_user_delete()
547{
548	local funcname=f_user_delete
549	local title # Calculated below
550	local alert=f_show_msg no_confirm=
551
552	f_getvar $VAR_NO_CONFIRM no_confirm
553	[ "$no_confirm" ] && alert=f_show_info
554
555	local input
556	f_getvar 3:-\$$VAR_USER input "$1"
557
558	if f_interactive && [ ! "$input" ]; then
559		f_dialog_menu_user_list || return $SUCCESS
560		f_dialog_menutag_fetch input
561		[ "$input" = "X $msg_exit" ] && return $SUCCESS
562	elif [ ! "$input" ]; then
563		f_show_err "$msg_no_user_specified"
564		return $FAILURE
565	fi
566
567	local user_account_expire user_class user_gecos user_gid user_home_dir
568	local user_member_groups user_name user_password user_password_expire
569	local user_shell user_uid # Variables created by f_input_user() below
570	if [ "$input" ] && ! f_input_user "$input"; then
571		f_show_err "$msg_login_not_found" "$input"
572		return $FAILURE
573	fi
574
575	local user_group_delete= user_home_delete=
576	f_getvar $VAR_USER_GROUP_DELETE:-\$msg_no user_group_delete
577	f_getvar $VAR_USER_HOME_DELETE:-\$msg_no  user_home_delete
578
579	# Attempt to translate user GID into a group name
580	local user_group
581	if user_group=$( pw groupshow -g "$user_gid" 2> /dev/null ); then
582		user_group="${user_group%%:*}"
583		# Default to delete the primary group if no script-override and
584		# exists with same name as the user (same logic used by pw(8))
585		f_isset $VAR_USER_GROUP_DELETE ||
586			[ "$user_group" != "$user_name" ] ||
587			user_group_delete="$msg_yes"
588	fi
589
590	#
591	# Loop until the user decides to Exit, Cancel, or presses ESC
592	#
593	title="$msg_delete $msg_user: $user_name"
594	if f_interactive; then
595		local mtag retval defaultitem=
596		while :; do
597			f_dialog_title "$title"
598			f_dialog_menu_user_delete "$user_name" "$defaultitem"
599			retval=$?
600			f_dialog_title_restore
601			f_dialog_menutag_fetch mtag
602			f_dprintf "retval=%u mtag=[%s]" $retval "$mtag"
603			defaultitem="$mtag"
604
605			# Return if user either pressed ESC or chose Cancel/No
606			[ $retval -eq $DIALOG_OK ] || return $FAILURE
607
608			case "$mtag" in
609			X) # Delete/Exit
610			   f_shell_escape "$user_uid" _user_uid
611
612			   # Save group information in case pw(8) deletes it
613			   # and we wanted to keep it (to be restored below)
614			   if [ "${user_group_delete:-$msg_no}" = "$msg_no" ]
615			   then
616			   	local v vars="gid members name password"
617			   	for v in $vars; do local group_$var; done
618			   	f_input_group "$user_group"
619
620			   	# Remove user-to-delete from group members
621			   	# NB: Otherwise group restoration could fail
622			   	local name length=0 _members=
623			  	while [ $length -ne ${#group_members} ]; do
624			   		name="${group_members%%,*}"
625			   		[ "$name" != "$user_name" ] &&
626			   			_members="$_members,$name"
627			   		length=${#group_members}
628			   		group_members="${group_members#*,}"
629			   	done
630			   	group_members="${_members#,}"
631
632			   	# Create escaped variables for f_eval_catch()
633			   	for v in $vars; do
634			   		local _group_$v
635			   		eval f_shell_escape \
636			   			\"\$group_$v\" _group_$v
637			   	done
638			   fi
639
640			   # Delete the user (if asked to delete home directory
641			   # display [X]dialog notification to show activity)
642			   local cmd="pw userdel -u '$_user_uid'"
643			   if [ "$user_home_delete" = "$msg_yes" -a \
644			        "$USE_XDIALOG" ]
645			   then
646			   	local err
647			   	err=$(
648			   		exec 9>&1
649			   		f_eval_catch -e $funcname pw \
650			   		  "%s -r" "$cmd" \
651			   		  >&$DIALOG_TERMINAL_PASSTHRU_FD 2>&9 |
652			   		  f_xdialog_info \
653			   		  	"$msg_deleting_home_directory"
654			   	)
655			   	[ ! "$err" ]
656			   elif [ "$user_home_delete" = "$msg_yes" ]; then
657			   	f_dialog_info "$msg_deleting_home_directory"
658			   	f_eval_catch $funcname pw '%s -r' "$cmd"
659			   else
660			   	f_eval_catch $funcname pw '%s' "$cmd"
661			   fi || continue
662
663			   #
664			   # pw(8) may conditionally delete the primary group,
665			   # which may not be what is desired.
666			   #
667			   # If we've been asked to delete the group and pw(8)
668			   # chose not to, delete it. Otherwise, if we're told
669			   # to NOT delete the group, we may need to restore it
670			   # since pw(8) doesn't have a flag to tell `userdel'
671			   # to not delete the group.
672			   # 
673			   # NB: If primary group and user have different names
674			   # the group may not have been deleted (again, see PR
675			   # 169471 and SVN r263114 for details).
676			   #
677			   if [ "${user_group_delete:-$msg_no}" != "$msg_no" ]
678			   then
679			   	f_quietly pw groupshow -g "$user_gid" &&
680			   	f_eval_catch $funcname pw \
681			   		"pw groupdel -g '%s'" "$_user_gid"
682			   elif ! f_quietly pw groupshow -g "$group_gid" &&
683			        [ "$group_name" -a "$group_gid" ]
684			   then
685			   	# Group deleted by pw(8), so restore it
686			   	local cmd="pw groupadd -n '$_group_name'"
687			   	cmd="$cmd -g '$_group_gid'"
688			   	cmd="$cmd -M '$_group_members'"
689
690			   	# Get the group password (pw(8) groupshow does
691			  	# NOT provide this (even if running privileged)
692			   	local group_password_enc
693			   	group_password_enc=$( getent group | awk -F: '
694			   		!/^[[:space:]]*(#|$)/ && \
695			   		    $1 == ENVIRON["group_name"] && \
696			   		    $3 == ENVIRON["group_gid"] && \
697			   		    $4 == ENVIRON["group_members"] \
698			   		{ print $2; exit }
699			   	' )
700			   	if [ "$group_password_enc" ]; then
701			   		echo "$group_password_enc" |
702			   			f_eval_catch $funcname \
703			   				pw '%s -H 0' "$cmd"
704			   	else
705			   		f_eval_catch $funcname \
706			   			pw '%s -h -' "$cmd"
707			   	fi
708			   fi
709
710			   break # to success
711			   ;;
712			1) # Login (select different login from list)
713			   f_dialog_menu_user_list "$user_name" || continue
714			   f_dialog_menutag_fetch mtag
715
716			   [ "$mtag" = "X $msg_exit" ] && continue
717
718			   if ! f_input_user "$mtag"; then
719			   	f_show_err "$msg_login_not_found" "$mtag"
720			   	# Attempt to fall back to previous selection
721			   	f_input_user "$input" || return $FAILURE
722			   else
723			   	input="$mtag"
724			   fi
725			   title="$msg_delete $msg_user: $user_name"
726			   ;;
727			C) # Delete Primary Group?
728			   if [ "${user_group_delete:-$msg_no}" != "$msg_no" ]
729			   then
730			   	user_group_delete="$msg_no"
731			   else
732			   	user_group_delete="$msg_yes"
733			   fi ;;
734			D) # Delete Home Directory?
735			   if [ "${user_home_delete:-$msg_no}" != "$msg_no" ]
736			   then
737			   	user_home_delete="$msg_no"
738			   else
739			   	user_home_delete="$msg_yes"
740			   fi ;;
741			esac
742		done
743	else
744		f_shell_escape "$user_uid" _user_uid
745
746		# Save group information in case pw(8) deletes it
747		# and we wanted to keep it (to be restored below)
748		if [ "${user_group_delete:-$msg_no}" = "$msg_no" ]; then
749			local v vars="gid members name password"
750			for v in $vars; do local group_$v; done
751			f_input_group "$user_group"
752
753			# Remove user we're about to delete from group members
754			# NB: Otherwise group restoration could fail
755			local name length=0 _members=
756			while [ $length -ne ${#group_members} ]; do
757				name="${group_members%%,*}"
758				[ "$name" != "$user_name" ] &&
759					_members="$_members,$name"
760				length=${#group_members}
761				group_members="${group_members#*,}"
762			done
763			group_members="${_members#,}"
764
765			# Create escaped variables for later f_eval_catch()
766			for v in $vars; do
767				local _group_$v
768				eval f_shell_escape \"\$group_$v\" _group_$v
769			done
770		fi
771
772		# Delete the user (if asked to delete home directory
773		# display [X]dialog notification to show activity)
774		local err cmd="pw userdel -u '$_user_uid'"
775		if [ "$user_home_delete" = "$msg_yes" -a "$USE_XDIALOG" ]; then
776			err=$(
777				exec 9>&1
778				f_eval_catch -de $funcname pw \
779					'%s -r' "$cmd" 2>&9 | f_xdialog_info \
780					"$msg_deleting_home_directory"
781			)
782			[ ! "$err" ]
783		elif [ "$user_home_delete" = "$msg_yes" ]; then
784			f_dialog_info "$msg_deleting_home_directory"
785			f_eval_catch -k err $funcname pw '%s -r' "$cmd"
786		else
787			f_eval_catch -k err $funcname pw '%s' "$cmd"
788		fi
789		local retval=$?
790		if [ $retval -ne $SUCCESS ]; then
791			f_show_err "%s" "$err"
792			return $retval
793		fi
794
795		#
796		# pw(8) may conditionally delete the primary group, which may
797		# not be what is desired.
798		#
799		# If we've been asked to delete the group and pw(8) chose not
800		# to, delete it. Otherwise, if we're told to NOT delete the
801		# group, we may need to restore it since pw(8) doesn't have a
802		# flag to tell `userdel' to not delete the group.
803		# 
804		# NB: If primary group and user have different names the group
805		# may not have been deleted (again, see PR 169471 and SVN
806		# r263114 for details).
807		#
808		if [ "${user_group_delete:-$msg_no}" != "$msg_no" ]
809		then
810			f_quietly pw groupshow -g "$user_gid" &&
811			f_eval_catch $funcname pw \
812				"pw groupdel -g '%s'" "$_user_gid"
813		elif ! f_quietly pw groupshow -g "$group_gid" &&
814		     [ "$group_name" -a "$group_gid" ]
815		then
816			# Group deleted by pw(8), so restore it
817			local cmd="pw groupadd -n '$_group_name'"
818			cmd="$cmd -g '$_group_gid'"
819			cmd="$cmd -M '$_group_members'"
820			local group_password_enc
821			group_password_enc=$( getent group | awk -F: '
822				!/^[[:space:]]*(#|$)/ && \
823				    $1 == ENVIRON["group_name"] && \
824				    $3 == ENVIRON["group_gid"] && \
825				    $4 == ENVIRON["group_members"] \
826				{ print $2; exit }
827			' )
828			if [ "$group_password_enc" ]; then
829				echo "$group_password_enc" |
830					f_eval_catch $funcname \
831						pw '%s -H 0' "$cmd"
832			else
833				f_eval_catch $funcname pw '%s -h -' "$cmd"
834			fi
835		fi
836	fi
837
838	f_dialog_title "$title"
839	$alert "$msg_login_deleted"
840	f_dialog_title_restore
841	[ "$no_confirm" -a "$USE_DIALOG" ] && sleep 1
842
843	return $SUCCESS
844}
845
846# f_user_edit [$user]
847#
848# Modify a login account. If both $user (as a first argument) and $VAR_USER are
849# unset or NULL and we are running interactively, prompt the end-user to select
850# a login account from a list of those available. Variables that can be used to
851# script user input:
852#
853# 	VAR_USER [Optional if running interactively]
854# 		The login to modify. Ignored if given non-NULL first-argument.
855# 	VAR_USER_ACCOUNT_EXPIRE [Optional]
856# 		The account expiration time. Format is similar to
857# 		VAR_USER_PASSWORD_EXPIRE variable below. If unset, account
858# 		expiry is unchanged. If set but NULL, account expiration is
859# 		disabled (same as setting a value of `0').
860# 	VAR_USER_DOTFILES_CREATE [Optional]
861# 		If non-NULL, re-populate the user's home directory with the
862# 		template files found in $udotdir (`/usr/share/skel' default).
863# 	VAR_USER_GECOS [Optional]
864# 		Often the full name of the account holder. If unset, the GECOS
865# 		field is unmodified. If set but NULL, the field is blanked.
866# 	VAR_USER_GID [Optional]
867# 		Numerical primary-group ID to set. If NULL or unset, the group
868# 		ID is unchanged.
869# 	VAR_USER_GROUPS [Optional]
870# 		Comma-separated list of additional groups to which the user is
871# 		a member of. If set but NULL, group memberships are reset (this
872# 		login will not be a member of any additional groups besides the
873# 		primary group). If unset, group membership is unmodified.
874# 	VAR_USER_HOME [Optional]
875# 		The home directory to set. If NULL or unset, the home directory
876# 		is unchanged.
877# 	VAR_USER_HOME_CREATE [Optional]
878# 		If non-NULL, create the user's home directory if it doesn't
879# 		already exist.
880# 	VAR_USER_LOGIN_CLASS [Optional]
881# 		Login class to set. If unset, the login class is unchanged. If
882# 		set but NULL, the field is blanked.
883# 	VAR_USER_PASSWORD [Optional]
884# 		Unencrypted password to set. If unset, the login password is
885# 		unmodified. If set but NULL, password authentication for the
886# 		login is disabled.
887# 	VAR_USER_PASSWORD_EXPIRE [Optional]
888# 		The password expiration time. Format of the date is either a
889# 		UNIX time in decimal, or a date in dd-mmm-yy[yy] format, where
890# 		dd is the day, mmm is the month in either numeric or alphabetic
891# 		format, and yy[yy] is either a two or four digit year. This
892# 		variable also accepts a relative date in the form of +n[mhdwoy]
893# 		where n is a decimal, octal (leading 0) or hexadecimal (leading
894# 		0x) digit followed by the number of Minutes, Hours, Days,
895# 		Weeks, Months or Years from the current date at which the
896# 		expiration time is to be set. If unset, password expiry is
897# 		unchanged. If set but NULL, password expiration is disabled
898# 		(same as setting a value of `0').
899# 	VAR_USER_SHELL [Optional]
900# 		Path to login shell to set. If NULL or unset, the shell is
901# 		unchanged.
902# 	VAR_USER_UID [Optional]
903# 		Numerical user ID to set. If NULL or unset, the user ID is
904# 		unchanged.
905#
906# Returns success if the user account was successfully modified.
907#
908f_user_edit()
909{
910	local funcname=f_user_edit
911	local title # Calculated below
912	local alert=f_show_msg no_confirm=
913
914	f_getvar $VAR_NO_CONFIRM no_confirm
915	[ "$no_confirm" ] && alert=f_show_info
916
917	local input
918	f_getvar 3:-\$$VAR_USER input "$1"
919
920	#
921	# NB: pw(8) has a ``feature'' wherein `-n name' can be taken as UID
922	# instead of name. Work-around is to also pass `-u UID' at the same
923	# time (any UID will do; but `-1' is appropriate for this context).
924	#
925	if [ "$input" ] && ! f_quietly pw usershow -n "$input" -u -1; then
926		f_show_err "$msg_login_not_found" "$input"
927		return $FAILURE
928	fi
929
930	if f_interactive && [ ! "$input" ]; then
931		f_dialog_menu_user_list || return $SUCCESS
932		f_dialog_menutag_fetch input
933		[ "$input" = "X $msg_exit" ] && return $SUCCESS
934	elif [ ! "$input" ]; then
935		f_show_err "$msg_no_user_specified"
936		return $FAILURE
937	fi
938
939	local user_account_expire user_class user_gecos user_gid user_home_dir
940	local user_member_groups user_name user_password user_password_expire
941	local user_shell user_uid # Variables created by f_input_user() below
942	if ! f_input_user "$input"; then
943		f_show_err "$msg_login_not_found" "$input"
944		return $FAILURE
945	fi
946
947	#
948	# Override values probed by f_input_user() with desired values
949	#
950	f_isset $VAR_USER_GID   && f_getvar $VAR_USER_GID   user_gid
951	f_isset $VAR_USER_HOME  && f_getvar $VAR_USER_HOME  user_home_dir
952	f_isset $VAR_USER_SHELL && f_getvar $VAR_USER_SHELL user_shell
953	f_isset $VAR_USER_UID   && f_getvar $VAR_USER_UID   user_uid
954	local user_dotfiles_create= user_home_create=
955	f_getvar $VAR_USER_DOTFILES_CREATE:+\$msg_yes user_dotfiles_create
956	f_getvar $VAR_USER_HOME_CREATE:+\$msg_yes     user_home_create
957	local no_account_expire=
958	if f_isset $VAR_USER_ACCOUNT_EXPIRE; then
959		f_getvar $VAR_USER_ACCOUNT_EXPIRE user_account_expire
960		[ "$user_account_expire" ] || no_account_expire=1
961	fi
962	local null_gecos=
963	if f_isset $VAR_USER_GECOS; then
964		f_getvar $VAR_USER_GECOS user_gecos
965		[ "$user_gecos" ] || null_gecos=1
966	fi
967	local null_members=
968	if f_isset $VAR_USER_GROUPS; then
969		f_getvar $VAR_USER_GROUPS user_member_groups
970		[ "$user_member_groups" ] || null_members=1
971	fi
972	local null_class=
973	if f_isset $VAR_USER_LOGIN_CLASS; then
974		f_getvar $VAR_USER_LOGIN_CLASS user_class
975		[ "$user_class" ] || null_class=1
976	fi
977	local user_password_disable=
978	if f_isset $VAR_USER_PASSWORD; then
979		f_getvar $VAR_USER_PASSWORD user_password
980		[ "$user_password" ] || user_password_disable=1
981	fi
982	local no_password_expire=
983	if f_isset $VAR_USER_PASSWORD_EXPIRE; then
984		f_getvar $VAR_USER_PASSWORD_EXPIRE user_password_expire
985		[ "$user_password_expire" ] || no_password_expire=1
986	fi
987
988	#
989	# Loop until the user decides to Exit, Cancel, or presses ESC
990	#
991	title="$msg_edit_view $msg_user: $user_name"
992	if f_interactive; then
993		local mtag retval defaultitem=
994		while :; do
995			f_dialog_title "$title"
996			f_dialog_menu_user_edit "$defaultitem"
997			retval=$?
998			f_dialog_title_restore
999			f_dialog_menutag_fetch mtag
1000			f_dprintf "retval=%u mtag=[%s]" $retval "$mtag"
1001			defaultitem="$mtag"
1002
1003			# Return if user either pressed ESC or chose Cancel/No
1004			[ $retval -eq $DIALOG_OK ] || return $FAILURE
1005
1006			case "$mtag" in
1007			X) # Save/Exit
1008			   local var
1009			   for var in account_expire class gecos gid home_dir \
1010			   	member_groups name password_expire shell uid \
1011			   ; do
1012			   	local _user_$var
1013			   	eval f_shell_escape \"\$user_$var\" _user_$var
1014			   done
1015
1016			   local cmd="pw usermod -n '$_user_name'"
1017			   [ "$user_gid"   ] && cmd="$cmd -g '$_user_gid'"
1018			   [ "$user_shell" ] && cmd="$cmd -s '$_user_shell'"
1019			   [ "$user_uid"   ] && cmd="$cmd -u '$_user_uid'"
1020			   [ "$user_account_expire" -o \
1021			     "$no_account_expire" ] &&
1022			   	cmd="$cmd -e '$_user_account_expire'"
1023			   [ "$user_class" -o "$null_class" ] &&
1024			   	cmd="$cmd -L '$_user_class'"
1025			   [ "$user_gecos" -o "$null_gecos" ] &&
1026			   	cmd="$cmd -c '$_user_gecos'"
1027			   [ "$user_home_dir"  ] &&
1028			   	cmd="$cmd -d '$_user_home_dir'"
1029			   [ "$user_member_groups" -o "$null_members" ] &&
1030			   	cmd="$cmd -G '$_user_member_groups'"
1031			   [ "$user_password_expire" -o \
1032			     "$no_password_expire" ] &&
1033			   	cmd="$cmd -p '$_user_password_expire'"
1034
1035			   # Execute the command
1036			   if [ "$user_password_disable" ]; then
1037			   	f_eval_catch $funcname pw '%s -h -' "$cmd"
1038			   elif [ "$user_password" ]; then
1039			   	echo "$user_password" | f_eval_catch \
1040			   		$funcname pw '%s -h 0' "$cmd"
1041			   else
1042			   	f_eval_catch $funcname pw '%s' "$cmd"
1043			   fi || continue
1044
1045			   # Create home directory if desired
1046			   [ "${user_home_create:-$msg_no}" != "$msg_no" ] &&
1047			   	f_user_create_homedir "$user_name"
1048
1049			   # Copy dotfiles if desired
1050			   [ "${user_dotfiles_create:-$msg_no}" != \
1051			     "$msg_no" ] && f_user_copy_dotfiles "$user_name"
1052
1053			   break # to success
1054			   ;;
1055			1) # Login (select different login from list)
1056			   f_dialog_menu_user_list "$user_name" || continue
1057			   f_dialog_menutag_fetch mtag
1058
1059			   [ "$mtag" = "X $msg_exit" ] && continue
1060
1061			   if ! f_input_user "$mtag"; then
1062			   	f_show_err "$msg_login_not_found" "$mtag"
1063			   	# Attempt to fall back to previous selection
1064			   	f_input_user "$input" || return $FAILURE
1065			   else
1066			   	input="$mtag"
1067			   fi
1068			   title="$msg_edit_view $msg_user: $user_name"
1069			   ;;
1070			2) # Full Name
1071			   f_dialog_input_gecos user_gecos "$user_gecos" &&
1072			   	[ ! "$user_gecos" ] && null_gecos=1 ;;
1073			3) # Password
1074			   f_dialog_input_password \
1075			   	user_password user_password_disable ;;
1076			4) # User ID
1077			   f_dialog_input_uid user_uid "$user_uid" ;;
1078			5) # Group ID
1079			   f_dialog_input_gid user_gid "$user_gid" ;;
1080			6) # Member of Groups
1081			   f_dialog_input_member_groups \
1082			   	user_member_groups "$user_member_groups" &&
1083			   	[ ! "$user_member_groups" ] &&
1084			   	null_members=1 ;;
1085			7) # Login Class
1086			   f_dialog_input_class user_class "$user_class" &&
1087			   	[ ! "$user_class" ] && null_class=1 ;;
1088			8) # Password Expires On
1089			   f_dialog_input_expire_password \
1090			   	user_password_expire "$user_password_expire" &&
1091			   	[ ! "$user_password_expire" ] &&
1092			   	no_password_expire=1 ;;
1093			9) # Account Expires On
1094			   f_dialog_input_expire_account \
1095			   	user_account_expire "$user_account_expire" &&
1096			   	[ ! "$user_account_expire" ] &&
1097			   	no_account_expire=1 ;;
1098			A) # Home Directory
1099			   f_dialog_input_home_dir \
1100			   	user_home_dir "$user_home_dir" ;;
1101			B) # Shell
1102			   f_dialog_input_shell user_shell "$user_shell" ;;
1103			C) # Create Home Directory?
1104			   if [ "${user_home_create:-$msg_no}" != "$msg_no" ]
1105			   then
1106			   	user_home_create="$msg_no"
1107			   else
1108			   	user_home_create="$msg_yes"
1109			   fi ;;
1110			D) # Create Dotfiles?
1111			   if [ "${user_dotfiles_create:-$msg_no}" != \
1112			        "$msg_no" ]
1113			   then
1114			   	user_dotfiles_create="$msg_no"
1115			   else
1116			   	user_dotfiles_create="$msg_yes"
1117			   fi ;;
1118			esac
1119		done
1120	else
1121		local var
1122		for var in account_expire class gecos gid home_dir \
1123			member_groups name password_expire shell uid \
1124		; do
1125			local _user_$var
1126			eval f_shell_escape \"\$user_$var\" _user_$var
1127		done
1128
1129		# Form the command
1130		local cmd="pw usermod -n '$_user_name'"
1131		[ "$user_gid"      ] && cmd="$cmd -g '$_user_gid'"
1132		[ "$user_home_dir" ] && cmd="$cmd -d '$_user_home_dir'"
1133		[ "$user_shell"    ] && cmd="$cmd -s '$_user_shell'"
1134		[ "$user_uid"      ] && cmd="$cmd -u '$_user_uid'"
1135		[ "$user_account_expire" -o "$no_account_expire" ] &&
1136			cmd="$cmd -e '$_user_account_expire'"
1137		[ "$user_class" -o "$null_class" ] &&
1138			cmd="$cmd -L '$_user_class'"
1139		[ "$user_gecos" -o "$null_gecos" ] &&
1140			cmd="$cmd -c '$_user_gecos'"
1141		[ "$user_member_groups" -o "$null_members" ] &&
1142			cmd="$cmd -G '$_user_member_groups'"
1143		[ "$user_password_expire" -o "$no_password_expire" ] &&
1144			cmd="$cmd -p '$_user_password_expire'"
1145
1146		# Execute the command
1147		local retval err
1148		if [ "$user_password_disable" ]; then
1149			f_eval_catch -k err $funcname pw '%s -h -' "$cmd"
1150		elif [ "$user_password" ]; then
1151			err=$( echo "$user_password" | f_eval_catch -de \
1152				$funcname pw '%s -h 0' "$cmd" 2>&1 )
1153		else
1154			f_eval_catch -k err $funcname pw '%s' "$cmd"
1155		fi
1156		retval=$?
1157		if [ $retval -ne $SUCCESS ]; then
1158			f_show_err "%s" "$err"
1159			return $retval
1160		fi
1161
1162		# Create home directory if desired
1163		[ "${user_home_create:-$msg_no}" != "$msg_no" ] &&
1164			f_user_create_homedir "$user_name"
1165
1166		# Copy dotfiles if desired
1167		[ "${user_dotfiles_create:-$msg_no}" != "$msg_no" ] &&
1168			f_user_copy_dotfiles "$user_name"
1169	fi
1170
1171	f_dialog_title "$title"
1172	$alert "$msg_login_updated"
1173	f_dialog_title_restore
1174	[ "$no_confirm" -a "$USE_DIALOG" ] && sleep 1
1175
1176	return $SUCCESS
1177}
1178
1179############################################################ MAIN
1180
1181f_dprintf "%s: Successfully loaded." usermgmt/user.subr
1182
1183fi # ! $_USERMGMT_USER_SUBR
1184