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