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