1 if [ ! "$_USERMGMT_USER_SUBR" ]; then _USERMGMT_USER_SUBR=1
3 # Copyright (c) 2012 Ron McDowell
4 # Copyright (c) 2012-2014 Devin Teske
7 # Redistribution and use in source and binary forms, with or without
8 # modification, are permitted provided that the following conditions
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.
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
30 ############################################################ INCLUDES
32 BSDCFG_SHARE="/usr/share/bsdconfig"
33 . $BSDCFG_SHARE/common.subr || exit 1
34 f_dprintf "%s: loading includes..." usermgmt/user.subr
35 f_include $BSDCFG_SHARE/dialog.subr
36 f_include $BSDCFG_SHARE/strings.subr
37 f_include $BSDCFG_SHARE/usermgmt/group_input.subr
38 f_include $BSDCFG_SHARE/usermgmt/user_input.subr
40 BSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="070.usermgmt"
41 f_include_lang $BSDCFG_LIBE/$APP_DIR/include/messages.subr
43 ############################################################ CONFIGURATION
45 # set some reasonable defaults if /etc/adduser.conf does not exist.
46 [ -f /etc/adduser.conf ] && f_include /etc/adduser.conf
48 : ${defaultshell:="/bin/sh"}
49 : ${homeprefix:="/home"}
50 : ${passwdtype:="yes"}
51 : ${udotdir:="/usr/share/skel"}
53 # Default account expire time. Format is similar to upwexpire variable.
56 # The default password expiration time. Format of the date is either a
57 # UNIX time in decimal, or a date in dd-mmm-yy[yy] format, where dd is
58 # the day, mmm is the month in either numeric or alphabetic format, and
59 # yy[yy] is either a two or four digit year. This variable also accepts
60 # a relative date in the form of n[mhdwoy] where n is a decimal, octal
61 # (leading 0) or hexadecimal (leading 0x) digit followed by the number
62 # of Minutes, Hours, Days, Weeks, Months or Years from the current date
63 # at which the expiration time is to be set.
66 # uexpire and upwexpire from adduser.conf(5) differ only slightly from what
67 # pw(8) accepts as `date' argument(s); pw(8) requires a leading `+' for the
68 # relative date syntax (n[mhdwoy]).
70 case "$uexpire" in *[mhdwoy])
71 f_isinteger "${uexpire%[mhdwoy]}" && uexpire="+$uexpire"
73 case "$upwexpire" in *[mhdwoy])
74 f_isinteger "${upwexpire%[mhdwoy]}" && upwexpire="+$upwexpire"
77 ############################################################ FUNCTIONS
79 # f_user_create_homedir $user
81 # Create home directory for $user.
83 f_user_create_homedir()
85 local funcname=f_user_create_homedir
88 [ "$user" ] || return $FAILURE
90 local user_account_expire user_class user_gecos user_gid user_home_dir
91 local user_member_groups user_name user_password user_password_expire
92 local user_shell user_uid # Variables created by f_input_user() below
93 f_input_user "$user" || return $FAILURE
95 f_dprintf "Creating home directory \`%s' for user \`%s'" \
96 "$user_home_dir" "$user"
98 local _user_gid _user_home_dir _user_uid
99 f_shell_escape "$user_gid" _user_gid
100 f_shell_escape "$user_home_dir" _user_home_dir
101 f_shell_escape "$user_uid" _user_uid
102 f_eval_catch $funcname mkdir "mkdir -p '%s'" "$_user_home_dir" ||
104 f_eval_catch $funcname chown "chown '%i:%i' '%s'" \
105 "$_user_uid" "$_user_gid" "$_user_home_dir" || return $FAILURE
108 # f_user_copy_dotfiles $user
110 # Copy `skel' dot-files from $udotdir (global inherited from /etc/adduser.conf)
111 # to the home-directory of $user. Attempts to create the home-directory first
112 # if it doesn't exist.
114 f_user_copy_dotfiles()
116 local funcname=f_user_copy_dotfiles
119 [ "$udotdir" ] || return $FAILURE
120 [ "$user" ] || return $FAILURE
122 local user_account_expire user_class user_gecos user_gid user_home_dir
123 local user_member_groups user_name user_password user_password_expire
124 local user_shell user_uid # Variables created by f_input_user() below
125 f_input_user "$user" || return $FAILURE
127 f_dprintf "Copying dot-files from \`%s' to \`%s'" \
128 "$udotdir" "$user_home_dir"
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
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
139 local - # Localize `set' to this function
140 set +f # Enable glob pattern-matching for paths
141 cd "$udotdir" || return $FAILURE
143 local _file file retval
144 for file in dot.*; do
145 [ -e "$file" ] || continue # no-match
147 f_shell_escape "$file" "_file"
148 f_eval_catch $funcname cp "cp -n '%s' '%s'" \
149 "$_file" "$_user_home_dir/${_file#dot}"
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}"
157 [ $retval -eq $SUCCESS ] || break
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:
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
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
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.
217 # Returns success if the user account was successfully created.
221 local funcname=f_user_add
222 local title # Calculated below
223 local alert=f_show_msg no_confirm=
225 f_getvar $VAR_NO_CONFIRM no_confirm
226 [ "$no_confirm" ] && alert=f_show_info
229 f_getvar 3:-\$$VAR_USER input "$1"
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).
236 if [ "$input" ] && f_quietly pw usershow -n "$input" -u -1; then
237 f_show_err "$msg_login_already_used" "$input"
241 local user_name="$input"
242 while f_interactive && [ ! "$user_name" ]; do
243 f_dialog_input_name user_name "$user_name" ||
246 f_show_err "$msg_please_enter_a_user_name"
248 if [ ! "$user_name" ]; then
249 f_show_err "$msg_no_user_specified"
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 \
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
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"
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
296 if f_interactive && [ ! "$no_confirm" ]; then
298 "$msg_use_default_values_for_all_account_details"
300 if [ $retval -eq $DIALOG_ESC ]; then
302 elif [ $retval -ne $DIALOG_OK ]; then
304 # Ask series of questions to pre-fill the editor screen
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
312 f_dialog_input_gecos user_gecos "$user_gecos" ||
314 if [ "$passwdtype" = "yes" ]; then
315 f_dialog_input_password user_password \
316 user_password_disable ||
319 f_dialog_input_uid user_uid "$user_uid" ||
321 f_dialog_input_gid user_gid "$user_gid" ||
323 f_dialog_input_member_groups user_member_groups \
324 "$user_member_groups" || return $FAILURE
325 f_dialog_input_class user_class "$user_class" ||
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 ||
336 if [ "$user_home_create" = "$msg_yes" ]; then
337 f_dialog_input_dotfiles_create \
338 user_dotfiles_create ||
342 f_dialog_input_shell user_shell "$user_shell" ||
348 # Loop until the user decides to Exit, Cancel, or presses ESC
350 title="$msg_add $msg_user: $user_name"
351 if f_interactive; then
352 local mtag retval defaultitem=
354 f_dialog_title "$title"
355 f_dialog_menu_user_add "$defaultitem"
357 f_dialog_title_restore
358 f_dialog_menutag_fetch mtag
359 f_dprintf "retval=%u mtag=[%s]" $retval "$mtag"
362 # Return if user either pressed ESC or chose Cancel/No
363 [ $retval -eq $DIALOG_OK ] || return $FAILURE
368 for var in account_expire class gecos gid home_dir \
369 member_groups name password_expire shell uid \
372 eval f_shell_escape \"\$user_$var\" _user_$var
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'"
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"
401 f_eval_catch $funcname pw '%s' "$cmd"
404 # Create home directory if desired
405 [ "${user_home_create:-$msg_no}" != "$msg_no" ] &&
406 f_user_create_homedir "$user_name"
408 # Copy dotfiles if desired
409 [ "${user_dotfiles_create:-$msg_no}" != \
410 "$msg_no" ] && f_user_copy_dotfiles "$user_name"
414 1) # Login (prompt for new login name)
415 f_dialog_input_name input "$user_name" ||
417 if f_quietly pw usershow -n "$input" -u -1; then
418 f_show_err "$msg_login_already_used" "$input"
422 title="$msg_add $msg_user: $user_name"
423 user_home_dir="${homeprefix%/}/$user_name"
426 f_dialog_input_gecos user_gecos "$user_gecos" &&
427 [ ! "$user_gecos" ] && null_gecos=1 ;;
429 f_dialog_input_password \
430 user_password user_password_disable ;;
432 f_dialog_input_uid user_uid "$user_uid" ;;
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" ] &&
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 ;;
454 f_dialog_input_home_dir \
455 user_home_dir "$user_home_dir" ;;
457 f_dialog_input_shell user_shell "$user_shell" ;;
458 C) # Create Home Directory?
459 if [ "${user_home_create:-$msg_no}" != "$msg_no" ]
461 user_home_create="$msg_no"
463 user_home_create="$msg_yes"
465 D) # Create Dotfiles?
466 if [ "${user_dotfiles_create:-$msg_no}" != \
469 user_dotfiles_create="$msg_no"
471 user_dotfiles_create="$msg_yes"
477 for var in account_expire class gecos gid home_dir \
478 member_groups name password_expire shell uid \
481 eval f_shell_escape \"\$user_$var\" _user_$var
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'"
501 # Execute the command
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 )
509 f_eval_catch -k err $funcname pw '%s' "$cmd"
512 if [ $retval -ne $SUCCESS ]; then
513 f_show_err "%s" "$err"
517 # Create home directory if desired
518 [ "${user_home_create:-$msg_no}" != "$msg_no" ] &&
519 f_user_create_homedir "$user_name"
521 # Copy dotfiles if desired
522 [ "${user_dotfiles_create:-$msg_no}" != "$msg_no" ] &&
523 f_user_copy_dotfiles "$user_name"
526 f_dialog_title "$title"
527 $alert "$msg_login_added"
528 f_dialog_title_restore
529 [ "$no_confirm" -a "$USE_DIALOG" ] && sleep 1
534 # f_user_delete [$user]
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
541 # VAR_USER [Optional if running interactively]
542 # The user to delete. Ignored if given non-NULL first-argument.
544 # Returns success if the user account was successfully deleted.
548 local funcname=f_user_delete
549 local title # Calculated below
550 local alert=f_show_msg no_confirm=
552 f_getvar $VAR_NO_CONFIRM no_confirm
553 [ "$no_confirm" ] && alert=f_show_info
556 f_getvar 3:-\$$VAR_USER input "$1"
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"
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"
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
579 # Attempt to translate user GID into a group name
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"
591 # Loop until the user decides to Exit, Cancel, or presses ESC
593 title="$msg_delete $msg_user: $user_name"
594 if f_interactive; then
595 local mtag retval defaultitem=
597 f_dialog_title "$title"
598 f_dialog_menu_user_delete "$user_name" "$defaultitem"
600 f_dialog_title_restore
601 f_dialog_menutag_fetch mtag
602 f_dprintf "retval=%u mtag=[%s]" $retval "$mtag"
605 # Return if user either pressed ESC or chose Cancel/No
606 [ $retval -eq $DIALOG_OK ] || return $FAILURE
610 f_shell_escape "$user_uid" _user_uid
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" ]
616 local v vars="gid members name password"
617 for v in $vars; do local group_$var; done
618 f_input_group "$user_group"
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#*,}"
630 group_members="${_members#,}"
632 # Create escaped variables for f_eval_catch()
635 eval f_shell_escape \
636 \"\$group_$v\" _group_$v
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 \
649 f_eval_catch -e $funcname pw \
651 >&$DIALOG_TERMINAL_PASSTHRU_FD 2>&9 |
653 "$msg_deleting_home_directory"
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"
660 f_eval_catch $funcname pw '%s' "$cmd"
664 # pw(8) may conditionally delete the primary group,
665 # which may not be what is desired.
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.
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).
677 if [ "${user_group_delete:-$msg_no}" != "$msg_no" ]
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" ]
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'"
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"] \
700 if [ "$group_password_enc" ]; then
701 echo "$group_password_enc" |
702 f_eval_catch $funcname \
705 f_eval_catch $funcname \
712 1) # Login (select different login from list)
713 f_dialog_menu_user_list "$user_name" || continue
714 f_dialog_menutag_fetch mtag
716 [ "$mtag" = "X $msg_exit" ] && continue
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
725 title="$msg_delete $msg_user: $user_name"
727 C) # Delete Primary Group?
728 if [ "${user_group_delete:-$msg_no}" != "$msg_no" ]
730 user_group_delete="$msg_no"
732 user_group_delete="$msg_yes"
734 D) # Delete Home Directory?
735 if [ "${user_home_delete:-$msg_no}" != "$msg_no" ]
737 user_home_delete="$msg_no"
739 user_home_delete="$msg_yes"
744 f_shell_escape "$user_uid" _user_uid
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"
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#*,}"
763 group_members="${_members#,}"
765 # Create escaped variables for later f_eval_catch()
768 eval f_shell_escape \"\$group_$v\" _group_$v
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
778 f_eval_catch -de $funcname pw \
779 '%s -r' "$cmd" 2>&9 | f_xdialog_info \
780 "$msg_deleting_home_directory"
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"
787 f_eval_catch -k err $funcname pw '%s' "$cmd"
790 if [ $retval -ne $SUCCESS ]; then
791 f_show_err "%s" "$err"
796 # pw(8) may conditionally delete the primary group, which may
797 # not be what is desired.
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.
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).
808 if [ "${user_group_delete:-$msg_no}" != "$msg_no" ]
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" ]
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"] \
828 if [ "$group_password_enc" ]; then
829 echo "$group_password_enc" |
830 f_eval_catch $funcname \
833 f_eval_catch $funcname \
839 f_dialog_title "$title"
840 $alert "$msg_login_deleted"
841 f_dialog_title_restore
842 [ "$no_confirm" -a "$USE_DIALOG" ] && sleep 1
847 # f_user_edit [$user]
849 # Modify a login account. If both $user (as a first argument) and $VAR_USER are
850 # unset or NULL and we are running interactively, prompt the end-user to select
851 # a login account from a list of those available. Variables that can be used to
854 # VAR_USER [Optional if running interactively]
855 # The login to modify. Ignored if given non-NULL first-argument.
856 # VAR_USER_ACCOUNT_EXPIRE [Optional]
857 # The account expiration time. Format is similar to
858 # VAR_USER_PASSWORD_EXPIRE variable below. If unset, account
859 # expiry is unchanged. If set but NULL, account expiration is
860 # disabled (same as setting a value of `0').
861 # VAR_USER_DOTFILES_CREATE [Optional]
862 # If non-NULL, re-populate the user's home directory with the
863 # template files found in $udotdir (`/usr/share/skel' default).
864 # VAR_USER_GECOS [Optional]
865 # Often the full name of the account holder. If unset, the GECOS
866 # field is unmodified. If set but NULL, the field is blanked.
867 # VAR_USER_GID [Optional]
868 # Numerical primary-group ID to set. If NULL or unset, the group
870 # VAR_USER_GROUPS [Optional]
871 # Comma-separated list of additional groups to which the user is
872 # a member of. If set but NULL, group memberships are reset (this
873 # login will not be a member of any additional groups besides the
874 # primary group). If unset, group membership is unmodified.
875 # VAR_USER_HOME [Optional]
876 # The home directory to set. If NULL or unset, the home directory
878 # VAR_USER_HOME_CREATE [Optional]
879 # If non-NULL, create the user's home directory if it doesn't
881 # VAR_USER_LOGIN_CLASS [Optional]
882 # Login class to set. If unset, the login class is unchanged. If
883 # set but NULL, the field is blanked.
884 # VAR_USER_PASSWORD [Optional]
885 # Unencrypted password to set. If unset, the login password is
886 # unmodified. If set but NULL, password authentication for the
888 # VAR_USER_PASSWORD_EXPIRE [Optional]
889 # The password expiration time. Format of the date is either a
890 # UNIX time in decimal, or a date in dd-mmm-yy[yy] format, where
891 # dd is the day, mmm is the month in either numeric or alphabetic
892 # format, and yy[yy] is either a two or four digit year. This
893 # variable also accepts a relative date in the form of +n[mhdwoy]
894 # where n is a decimal, octal (leading 0) or hexadecimal (leading
895 # 0x) digit followed by the number of Minutes, Hours, Days,
896 # Weeks, Months or Years from the current date at which the
897 # expiration time is to be set. If unset, password expiry is
898 # unchanged. If set but NULL, password expiration is disabled
899 # (same as setting a value of `0').
900 # VAR_USER_SHELL [Optional]
901 # Path to login shell to set. If NULL or unset, the shell is
903 # VAR_USER_UID [Optional]
904 # Numerical user ID to set. If NULL or unset, the user ID is
907 # Returns success if the user account was successfully modified.
911 local funcname=f_user_edit
912 local title # Calculated below
913 local alert=f_show_msg no_confirm=
915 f_getvar $VAR_NO_CONFIRM no_confirm
916 [ "$no_confirm" ] && alert=f_show_info
919 f_getvar 3:-\$$VAR_USER input "$1"
922 # NB: pw(8) has a ``feature'' wherein `-n name' can be taken as UID
923 # instead of name. Work-around is to also pass `-u UID' at the same
924 # time (any UID will do; but `-1' is appropriate for this context).
926 if [ "$input" ] && ! f_quietly pw usershow -n "$input" -u -1; then
927 f_show_err "$msg_login_not_found" "$input"
931 if f_interactive && [ ! "$input" ]; then
932 f_dialog_menu_user_list || return $SUCCESS
933 f_dialog_menutag_fetch input
934 [ "$input" = "X $msg_exit" ] && return $SUCCESS
935 elif [ ! "$input" ]; then
936 f_show_err "$msg_no_user_specified"
940 local user_account_expire user_class user_gecos user_gid user_home_dir
941 local user_member_groups user_name user_password user_password_expire
942 local user_shell user_uid # Variables created by f_input_user() below
943 if ! f_input_user "$input"; then
944 f_show_err "$msg_login_not_found" "$input"
949 # Override values probed by f_input_user() with desired values
951 f_isset $VAR_USER_GID && f_getvar $VAR_USER_GID user_gid
952 f_isset $VAR_USER_HOME && f_getvar $VAR_USER_HOME user_home_dir
953 f_isset $VAR_USER_SHELL && f_getvar $VAR_USER_SHELL user_shell
954 f_isset $VAR_USER_UID && f_getvar $VAR_USER_UID user_uid
955 local user_dotfiles_create= user_home_create=
956 f_getvar $VAR_USER_DOTFILES_CREATE:+\$msg_yes user_dotfiles_create
957 f_getvar $VAR_USER_HOME_CREATE:+\$msg_yes user_home_create
958 local no_account_expire=
959 if f_isset $VAR_USER_ACCOUNT_EXPIRE; then
960 f_getvar $VAR_USER_ACCOUNT_EXPIRE user_account_expire
961 [ "$user_account_expire" ] || no_account_expire=1
964 if f_isset $VAR_USER_GECOS; then
965 f_getvar $VAR_USER_GECOS user_gecos
966 [ "$user_gecos" ] || null_gecos=1
969 if f_isset $VAR_USER_GROUPS; then
970 f_getvar $VAR_USER_GROUPS user_member_groups
971 [ "$user_member_groups" ] || null_members=1
974 if f_isset $VAR_USER_LOGIN_CLASS; then
975 f_getvar $VAR_USER_LOGIN_CLASS user_class
976 [ "$user_class" ] || null_class=1
978 local user_password_disable=
979 if f_isset $VAR_USER_PASSWORD; then
980 f_getvar $VAR_USER_PASSWORD user_password
981 [ "$user_password" ] || user_password_disable=1
983 local no_password_expire=
984 if f_isset $VAR_USER_PASSWORD_EXPIRE; then
985 f_getvar $VAR_USER_PASSWORD_EXPIRE user_password_expire
986 [ "$user_password_expire" ] || no_password_expire=1
990 # Loop until the user decides to Exit, Cancel, or presses ESC
992 title="$msg_edit_view $msg_user: $user_name"
993 if f_interactive; then
994 local mtag retval defaultitem=
996 f_dialog_title "$title"
997 f_dialog_menu_user_edit "$defaultitem"
999 f_dialog_title_restore
1000 f_dialog_menutag_fetch mtag
1001 f_dprintf "retval=%u mtag=[%s]" $retval "$mtag"
1004 # Return if user either pressed ESC or chose Cancel/No
1005 [ $retval -eq $DIALOG_OK ] || return $FAILURE
1010 for var in account_expire class gecos gid home_dir \
1011 member_groups name password_expire shell uid \
1014 eval f_shell_escape \"\$user_$var\" _user_$var
1017 local cmd="pw usermod -n '$_user_name'"
1018 [ "$user_gid" ] && cmd="$cmd -g '$_user_gid'"
1019 [ "$user_shell" ] && cmd="$cmd -s '$_user_shell'"
1020 [ "$user_uid" ] && cmd="$cmd -u '$_user_uid'"
1021 [ "$user_account_expire" -o \
1022 "$no_account_expire" ] &&
1023 cmd="$cmd -e '$_user_account_expire'"
1024 [ "$user_class" -o "$null_class" ] &&
1025 cmd="$cmd -L '$_user_class'"
1026 [ "$user_gecos" -o "$null_gecos" ] &&
1027 cmd="$cmd -c '$_user_gecos'"
1028 [ "$user_home_dir" ] &&
1029 cmd="$cmd -d '$_user_home_dir'"
1030 [ "$user_member_groups" -o "$null_members" ] &&
1031 cmd="$cmd -G '$_user_member_groups'"
1032 [ "$user_password_expire" -o \
1033 "$no_password_expire" ] &&
1034 cmd="$cmd -p '$_user_password_expire'"
1036 # Execute the command
1037 if [ "$user_password_disable" ]; then
1038 f_eval_catch $funcname pw '%s -h -' "$cmd"
1039 elif [ "$user_password" ]; then
1040 echo "$user_password" | f_eval_catch \
1041 $funcname pw '%s -h 0' "$cmd"
1043 f_eval_catch $funcname pw '%s' "$cmd"
1046 # Create home directory if desired
1047 [ "${user_home_create:-$msg_no}" != "$msg_no" ] &&
1048 f_user_create_homedir "$user_name"
1050 # Copy dotfiles if desired
1051 [ "${user_dotfiles_create:-$msg_no}" != \
1052 "$msg_no" ] && f_user_copy_dotfiles "$user_name"
1056 1) # Login (select different login from list)
1057 f_dialog_menu_user_list "$user_name" || continue
1058 f_dialog_menutag_fetch mtag
1060 [ "$mtag" = "X $msg_exit" ] && continue
1062 if ! f_input_user "$mtag"; then
1063 f_show_err "$msg_login_not_found" "$mtag"
1064 # Attempt to fall back to previous selection
1065 f_input_user "$input" || return $FAILURE
1069 title="$msg_edit_view $msg_user: $user_name"
1072 f_dialog_input_gecos user_gecos "$user_gecos" &&
1073 [ ! "$user_gecos" ] && null_gecos=1 ;;
1075 f_dialog_input_password \
1076 user_password user_password_disable ;;
1078 f_dialog_input_uid user_uid "$user_uid" ;;
1080 f_dialog_input_gid user_gid "$user_gid" ;;
1081 6) # Member of Groups
1082 f_dialog_input_member_groups \
1083 user_member_groups "$user_member_groups" &&
1084 [ ! "$user_member_groups" ] &&
1087 f_dialog_input_class user_class "$user_class" &&
1088 [ ! "$user_class" ] && null_class=1 ;;
1089 8) # Password Expires On
1090 f_dialog_input_expire_password \
1091 user_password_expire "$user_password_expire" &&
1092 [ ! "$user_password_expire" ] &&
1093 no_password_expire=1 ;;
1094 9) # Account Expires On
1095 f_dialog_input_expire_account \
1096 user_account_expire "$user_account_expire" &&
1097 [ ! "$user_account_expire" ] &&
1098 no_account_expire=1 ;;
1100 f_dialog_input_home_dir \
1101 user_home_dir "$user_home_dir" ;;
1103 f_dialog_input_shell user_shell "$user_shell" ;;
1104 C) # Create Home Directory?
1105 if [ "${user_home_create:-$msg_no}" != "$msg_no" ]
1107 user_home_create="$msg_no"
1109 user_home_create="$msg_yes"
1111 D) # Create Dotfiles?
1112 if [ "${user_dotfiles_create:-$msg_no}" != \
1115 user_dotfiles_create="$msg_no"
1117 user_dotfiles_create="$msg_yes"
1123 for var in account_expire class gecos gid home_dir \
1124 member_groups name password_expire shell uid \
1127 eval f_shell_escape \"\$user_$var\" _user_$var
1131 local cmd="pw usermod -n '$_user_name'"
1132 [ "$user_gid" ] && cmd="$cmd -g '$_user_gid'"
1133 [ "$user_home_dir" ] && cmd="$cmd -d '$_user_home_dir'"
1134 [ "$user_shell" ] && cmd="$cmd -s '$_user_shell'"
1135 [ "$user_uid" ] && cmd="$cmd -u '$_user_uid'"
1136 [ "$user_account_expire" -o "$no_account_expire" ] &&
1137 cmd="$cmd -e '$_user_account_expire'"
1138 [ "$user_class" -o "$null_class" ] &&
1139 cmd="$cmd -L '$_user_class'"
1140 [ "$user_gecos" -o "$null_gecos" ] &&
1141 cmd="$cmd -c '$_user_gecos'"
1142 [ "$user_member_groups" -o "$null_members" ] &&
1143 cmd="$cmd -G '$_user_member_groups'"
1144 [ "$user_password_expire" -o "$no_password_expire" ] &&
1145 cmd="$cmd -p '$_user_password_expire'"
1147 # Execute the command
1149 if [ "$user_password_disable" ]; then
1150 f_eval_catch -k err $funcname pw '%s -h -' "$cmd"
1151 elif [ "$user_password" ]; then
1152 err=$( echo "$user_password" | f_eval_catch -de \
1153 $funcname pw '%s -h 0' "$cmd" 2>&1 )
1155 f_eval_catch -k err $funcname pw '%s' "$cmd"
1158 if [ $retval -ne $SUCCESS ]; then
1159 f_show_err "%s" "$err"
1163 # Create home directory if desired
1164 [ "${user_home_create:-$msg_no}" != "$msg_no" ] &&
1165 f_user_create_homedir "$user_name"
1167 # Copy dotfiles if desired
1168 [ "${user_dotfiles_create:-$msg_no}" != "$msg_no" ] &&
1169 f_user_copy_dotfiles "$user_name"
1172 f_dialog_title "$title"
1173 $alert "$msg_login_updated"
1174 f_dialog_title_restore
1175 [ "$no_confirm" -a "$USE_DIALOG" ] && sleep 1
1180 ############################################################ MAIN
1182 f_dprintf "%s: Successfully loaded." usermgmt/user.subr
1184 fi # ! $_USERMGMT_USER_SUBR