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