1 if [ ! "$_USERMGMT_USER_SUBR" ]; then _USERMGMT_USER_SUBR=1
3 # Copyright (c) 2012 Ron McDowell
4 # Copyright (c) 2012-2015 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 (the UID is ignored in this case, so any UID will do).
236 if [ "$input" ] && f_quietly pw usershow -n "$input" -u 1337; 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 1337; 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 pw '%s -h -' "$cmd"
838 f_dialog_title "$title"
839 $alert "$msg_login_deleted"
840 f_dialog_title_restore
841 [ "$no_confirm" -a "$USE_DIALOG" ] && sleep 1
846 # f_user_edit [$user]
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
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
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
877 # VAR_USER_HOME_CREATE [Optional]
878 # If non-NULL, create the user's home directory if it doesn't
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
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
902 # VAR_USER_UID [Optional]
903 # Numerical user ID to set. If NULL or unset, the user ID is
906 # Returns success if the user account was successfully modified.
910 local funcname=f_user_edit
911 local title # Calculated below
912 local alert=f_show_msg no_confirm=
914 f_getvar $VAR_NO_CONFIRM no_confirm
915 [ "$no_confirm" ] && alert=f_show_info
918 f_getvar 3:-\$$VAR_USER input "$1"
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 (the UID is ignored in this case, so any UID will do).
925 if [ "$input" ] && ! f_quietly pw usershow -n "$input" -u 1337; then
926 f_show_err "$msg_login_not_found" "$input"
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"
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"
948 # Override values probed by f_input_user() with desired values
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
963 if f_isset $VAR_USER_GECOS; then
964 f_getvar $VAR_USER_GECOS user_gecos
965 [ "$user_gecos" ] || null_gecos=1
968 if f_isset $VAR_USER_GROUPS; then
969 f_getvar $VAR_USER_GROUPS user_member_groups
970 [ "$user_member_groups" ] || null_members=1
973 if f_isset $VAR_USER_LOGIN_CLASS; then
974 f_getvar $VAR_USER_LOGIN_CLASS user_class
975 [ "$user_class" ] || null_class=1
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
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
989 # Loop until the user decides to Exit, Cancel, or presses ESC
991 title="$msg_edit_view $msg_user: $user_name"
992 if f_interactive; then
993 local mtag retval defaultitem=
995 f_dialog_title "$title"
996 f_dialog_menu_user_edit "$defaultitem"
998 f_dialog_title_restore
999 f_dialog_menutag_fetch mtag
1000 f_dprintf "retval=%u mtag=[%s]" $retval "$mtag"
1003 # Return if user either pressed ESC or chose Cancel/No
1004 [ $retval -eq $DIALOG_OK ] || return $FAILURE
1009 for var in account_expire class gecos gid home_dir \
1010 member_groups name password_expire shell uid \
1013 eval f_shell_escape \"\$user_$var\" _user_$var
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'"
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"
1042 f_eval_catch $funcname pw '%s' "$cmd"
1045 # Create home directory if desired
1046 [ "${user_home_create:-$msg_no}" != "$msg_no" ] &&
1047 f_user_create_homedir "$user_name"
1049 # Copy dotfiles if desired
1050 [ "${user_dotfiles_create:-$msg_no}" != \
1051 "$msg_no" ] && f_user_copy_dotfiles "$user_name"
1055 1) # Login (select different login from list)
1056 f_dialog_menu_user_list "$user_name" || continue
1057 f_dialog_menutag_fetch mtag
1059 [ "$mtag" = "X $msg_exit" ] && continue
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
1068 title="$msg_edit_view $msg_user: $user_name"
1071 f_dialog_input_gecos user_gecos "$user_gecos" &&
1072 [ ! "$user_gecos" ] && null_gecos=1 ;;
1074 f_dialog_input_password \
1075 user_password user_password_disable ;;
1077 f_dialog_input_uid user_uid "$user_uid" ;;
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" ] &&
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 ;;
1099 f_dialog_input_home_dir \
1100 user_home_dir "$user_home_dir" ;;
1102 f_dialog_input_shell user_shell "$user_shell" ;;
1103 C) # Create Home Directory?
1104 if [ "${user_home_create:-$msg_no}" != "$msg_no" ]
1106 user_home_create="$msg_no"
1108 user_home_create="$msg_yes"
1110 D) # Create Dotfiles?
1111 if [ "${user_dotfiles_create:-$msg_no}" != \
1114 user_dotfiles_create="$msg_no"
1116 user_dotfiles_create="$msg_yes"
1122 for var in account_expire class gecos gid home_dir \
1123 member_groups name password_expire shell uid \
1126 eval f_shell_escape \"\$user_$var\" _user_$var
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'"
1146 # Execute the command
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 )
1154 f_eval_catch -k err $funcname pw '%s' "$cmd"
1157 if [ $retval -ne $SUCCESS ]; then
1158 f_show_err "%s" "$err"
1162 # Create home directory if desired
1163 [ "${user_home_create:-$msg_no}" != "$msg_no" ] &&
1164 f_user_create_homedir "$user_name"
1166 # Copy dotfiles if desired
1167 [ "${user_dotfiles_create:-$msg_no}" != "$msg_no" ] &&
1168 f_user_copy_dotfiles "$user_name"
1171 f_dialog_title "$title"
1172 $alert "$msg_login_updated"
1173 f_dialog_title_restore
1174 [ "$no_confirm" -a "$USE_DIALOG" ] && sleep 1
1179 ############################################################ MAIN
1181 f_dprintf "%s: Successfully loaded." usermgmt/user.subr
1183 fi # ! $_USERMGMT_USER_SUBR