1#!/bin/sh 2# 3# SPDX-License-Identifier: BSD-2-Clause 4# 5# Copyright (c) 2002-2004 Michael Telahun Makonnen. 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 ``AS IS'' AND ANY EXPRESS OR 17# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 18# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 19# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 20# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 21# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 22# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 23# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 25# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26# 27# Email: Mike Makonnen <mtm@FreeBSD.Org> 28# 29# 30 31# err msg 32# Display $msg on stderr, unless we're being quiet. 33# 34err() { 35 if [ -z "$quietflag" ]; then 36 echo 1>&2 ${THISCMD}: ERROR: $* 37 fi 38} 39 40# info msg 41# Display $msg on stdout, unless we're being quiet. 42# 43info() { 44 if [ -z "$quietflag" ]; then 45 echo ${THISCMD}: INFO: $* 46 fi 47} 48 49# get_nextuid 50# Output the value of $_uid if it is available for use. If it 51# is not, output the value of the next higher uid that is available. 52# If a uid is not specified, output the first available uid, as indicated 53# by pw(8). 54# 55get_nextuid () { 56 local _uid=$1 _nextuid= 57 58 if [ -z "$_uid" ]; then 59 _nextuid="$(${PWCMD} usernext | cut -f1 -d:)" 60 else 61 while : ; do 62 if ! ${PWCMD} usershow $_uid > /dev/null 2>&1; then 63 _nextuid=$_uid 64 break 65 fi 66 _uid=$((_uid + 1)) 67 done 68 fi 69 echo $_nextuid 70} 71 72# show_usage 73# Display usage information for this utility. 74# 75show_usage() { 76 echo "usage: ${THISCMD} [options]" 77 echo " options may include:" 78 echo " -C save to the configuration file only" 79 echo " -D do not attempt to create the home directory" 80 echo " -E disable this account after creation" 81 echo " -G additional groups to add accounts to" 82 echo " -L login class of the user" 83 echo " -M file permission for home directory" 84 echo " -N do not read configuration file" 85 echo " -Z do not attempt to create ZFS home dataset" 86 echo " -S a nonexistent shell is not an error" 87 echo " -d home directory" 88 echo " -f file from which input will be received" 89 echo " -g default login group" 90 echo " -h display this usage message" 91 echo " -k path to skeleton home directory" 92 echo " -m user welcome message file" 93 echo " -q absolute minimal user feedback" 94 echo " -s shell" 95 echo " -u uid to start at" 96 echo " -w password type: no, none, yes or random" 97} 98 99# valid_shells 100# Outputs a list of valid shells from /etc/shells. Only the 101# basename of the shell is output. 102# 103valid_shells() { 104 local _prefix= 105 106 ${GREPCMD} '^[^#]' ${ETCSHELLS} | 107 while read _path _junk ; do 108 echo -n "${_prefix}${_path##*/}" 109 _prefix=' ' 110 done 111 112 # /usr/sbin/nologin is a special case 113 [ -x "${NOLOGIN_PATH}" ] && echo -n " ${NOLOGIN}" 114} 115 116# fullpath_from_shell shell 117# Given $shell, which is either the full path to a shell or 118# the basename component of a valid shell, get the 119# full path to the shell from the /etc/shells file. 120# 121fullpath_from_shell() { 122 local _shell=$1 _fullpath= 123 124 if [ -z "$_shell" ]; then 125 return 126 fi 127 128 # /usr/sbin/nologin is a special case; it needs to be handled 129 # before the grep | while loop, since a 'return' from within 130 # a subshell will not terminate the function's execution, and 131 # the path to the nologin shell might be printed out twice. 132 # 133 if [ "$_shell" = "${NOLOGIN}" ] || 134 [ "$_shell" = "${NOLOGIN_PATH}" ]; then 135 echo ${NOLOGIN_PATH} 136 return 137 fi 138 139 ${GREPCMD} '^[^#]' ${ETCSHELLS} | 140 while read _path _junk ; do 141 if [ "$_path" = "$_shell" ] || 142 [ "${_path##*/}" = "$_shell" ]; then 143 echo "$_path" 144 break 145 fi 146 done 147} 148 149# shell_exists shell 150# If the given shell is listed in ${ETCSHELLS} or it is 151# the nologin shell this function will return 0. 152# Otherwise, it will return 1. If shell is valid but 153# the path is invalid or it is not executable it 154# will emit an informational message saying so. 155# 156shell_exists() { 157 local _sh=$1 158 159 if [ -z "$(fullpath_from_shell "$_sh")" ] ; then 160 err "Invalid shell ($_sh) for user $username." 161 return 1 162 fi 163 [ -x "$_sh" ] || 164 info "The shell ($_sh) does not exist or is not executable." 165 return 0 166} 167 168# save_config 169# Save some variables to a configuration file. 170# Note: not all script variables are saved, only those that 171# it makes sense to save. 172# 173save_config() { 174 echo "# Configuration file for adduser(8)." > ${ADDUSERCONF} 175 echo "# NOTE: only *some* variables are saved." >> ${ADDUSERCONF} 176 echo "# Last Modified on $(${DATECMD})." >> ${ADDUSERCONF} 177 echo '' >> ${ADDUSERCONF} 178 echo "defaultHomePerm=$uhomeperm" >> ${ADDUSERCONF} 179 echo "defaultLgroup=$ulogingroup" >> ${ADDUSERCONF} 180 echo "defaultclass=$uclass" >> ${ADDUSERCONF} 181 echo "defaultgroups=$ugroups" >> ${ADDUSERCONF} 182 echo "passwdtype=$passwdtype" >> ${ADDUSERCONF} 183 echo "homeprefix=$homeprefix" >> ${ADDUSERCONF} 184 echo "defaultshell=$ushell" >> ${ADDUSERCONF} 185 echo "udotdir=$udotdir" >> ${ADDUSERCONF} 186 echo "msgfile=$msgfile" >> ${ADDUSERCONF} 187 echo "disableflag=$disableflag" >> ${ADDUSERCONF} 188 echo "uidstart=$uidstart" >> ${ADDUSERCONF} 189} 190 191# add_user 192# Add a user to the user database. If the user chose to send a welcome 193# message or lock the account, do so. 194# 195add_user() { 196 local _uid= _name= _comment= _gecos= _home= _group= _grouplist= 197 local _shell= _class= _dotdir= _expire= _pwexpire= _passwd= _upasswd= 198 local _passwdmethod= _pwcmd= 199 200 # Is this a configuration run? If so, don't modify user database. 201 # 202 if [ -n "$configflag" ]; then 203 save_config 204 return 205 fi 206 207 _name="-n '$username'" 208 [ -n "$uuid" ] && _uid='-u "$uuid"' 209 [ -n "$ulogingroup" ] && _group='-g "$ulogingroup"' 210 [ -n "$ugroups" ] && _grouplist='-G "$ugroups"' 211 [ -n "$ushell" ] && _shell='-s "$ushell"' 212 [ -n "$uclass" ] && _class='-L "$uclass"' 213 [ -n "$ugecos" ] && _comment='-c "$ugecos"' 214 [ -n "$udotdir" ] && _dotdir='-k "$udotdir"' 215 [ -n "$uexpire" ] && _expire='-e "$uexpire"' 216 [ -n "$upwexpire" ] && _pwexpire='-p "$upwexpire"' 217 if [ -z "$Dflag" ] && [ -n "$uhome" ]; then 218 # The /nonexistent home directory is special. It 219 # means the user has no home directory. 220 if [ "$uhome" = "$NOHOME" ]; then 221 _home='-d "$uhome"' 222 else 223 # Use home directory permissions if specified 224 if [ -n "$uhomeperm" ]; then 225 _home='-m -d "$uhome" -M "$uhomeperm"' 226 else 227 _home='-m -d "$uhome"' 228 fi 229 fi 230 elif [ -n "$Dflag" ] && [ -n "$uhome" ]; then 231 _home='-d "$uhome"' 232 fi 233 case $passwdtype in 234 no) 235 _passwdmethod="-w no" 236 _passwd="-h -" 237 ;; 238 yes) 239 # Note on processing the password: The outer double quotes 240 # make literal everything except ` and \ and $. 241 # The outer single quotes make literal ` and $. 242 # We can ensure the \ isn't treated specially by specifying 243 # the -r switch to the read command used to obtain the input. 244 # 245 _passwdmethod="-w yes" 246 _passwd="-h 0" 247 _upasswd='echo "$upass" |' 248 ;; 249 none) 250 _passwdmethod="-w none" 251 ;; 252 random) 253 _passwdmethod="-w random" 254 ;; 255 esac 256 257 # create ZFS dataset before home directory is created with pw 258 if [ "${Zcreate}" = "yes" ]; then 259 if [ "${Zencrypt}" = "yes" ]; then 260 echo "Enter encryption keyphrase for ZFS dataset (${zhome}):" 261 fi 262 if [ -n "$BSDINSTALL_CHROOT" ]; then 263 create_zfs_chrooted_dataset 264 else 265 if ! create_zfs_dataset; then 266 err "There was an error adding user ($username)." 267 return 1 268 fi 269 fi 270 fi 271 272 _pwcmd="$_upasswd ${PWCMD} useradd $_uid $_name $_group $_grouplist $_comment" 273 _pwcmd="$_pwcmd $_shell $_class $_home $_dotdir $_passwdmethod $_passwd" 274 _pwcmd="$_pwcmd $_expire $_pwexpire" 275 276 if ! _output=$(eval $_pwcmd) ; then 277 err "There was an error adding user ($username)." 278 return 1 279 else 280 info "Successfully added ($username) to the user database." 281 if [ "random" = "$passwdtype" ]; then 282 randompass="$_output" 283 info "Password for ($username) is: $randompass" 284 fi 285 fi 286 287 if [ -n "$disableflag" ]; then 288 if ${PWCMD} lock $username ; then 289 info "Account ($username) is locked." 290 else 291 info "Account ($username) could NOT be locked." 292 fi 293 fi 294 295 # give newly created user permissions to their home zfs dataset 296 if [ "${Zcreate}" = "yes" ]; then 297 set_zfs_perms 298 if [ -n "$BSDINSTALL_CHROOT" ]; then 299 umount_legacy_zfs 300 fi 301 fi 302 303 local _line= _owner= _perms= _file= _dir= 304 if [ -n "$msgflag" ]; then 305 if [ -r "$msgfile" ]; then 306 # We're evaluating the contents of an external file. 307 # Let's not open ourselves up for attack. _perms will 308 # be empty if it's writeable only by the owner. _owner 309 # will *NOT* be empty if the file is owned by root. 310 # 311 _dir="$(dirname "$msgfile")" 312 _file="$(basename "$msgfile")" 313 _perms=$(/usr/bin/find "$_dir" -name "$_file" -perm +07022 -prune) 314 _owner=$(/usr/bin/find "$_dir" -name "$_file" -user 0 -prune) 315 if [ -z "$_owner" ] || [ -n "$_perms" ]; then 316 err "The message file ($msgfile) may be writeable only by root." 317 return 1 318 fi 319 while read _line ; do 320 eval echo "$_line" 321 done <"$msgfile" | ${MAILCMD} -s"Welcome" ${username} 322 info "Sent welcome message to ($username)." 323 fi 324 fi 325} 326 327# get_user 328# Reads username of the account from standard input or from a global 329# variable containing an account line from a file. The username is 330# required. If this is an interactive session it will prompt in 331# a loop until a username is entered. If it is batch processing from 332# a file it will output an error message and return to the caller. 333# 334get_user() { 335 local _input= 336 337 # No need to take down user names if this is a configuration saving run. 338 [ -n "$configflag" ] && return 339 340 while : ; do 341 if [ -z "$fflag" ]; then 342 echo -n "Username: " 343 read _input 344 else 345 _input="$(echo "$fileline" | cut -f1 -d:)" 346 fi 347 348 # There *must* be a username, and it must not exist. If 349 # this is an interactive session give the user an 350 # opportunity to retry. 351 # 352 if [ -z "$_input" ]; then 353 err "You must enter a username!" 354 [ -z "$fflag" ] && continue 355 fi 356 if ${PWCMD} usershow "$_input" > /dev/null 2>&1; then 357 err "User exists!" 358 [ -z "$fflag" ] && continue 359 fi 360 break 361 done 362 username="$_input" 363} 364 365# get_gecos 366# Reads extra information about the user. Can be used both in interactive 367# and batch (from file) mode. 368# 369get_gecos() { 370 local _input= 371 372 # No need to take down additional user information for a configuration run. 373 [ -n "$configflag" ] && return 374 375 if [ -z "$fflag" ]; then 376 echo -n "Full name: " 377 read _input 378 else 379 _input="$(echo "$fileline" | cut -f7 -d:)" 380 fi 381 ugecos="$_input" 382} 383 384# get_shell 385# Get the account's shell. Works in interactive and batch mode. It 386# accepts either the base name of the shell or the full path. 387# If an invalid shell is entered it will simply use the default shell. 388# 389get_shell() { 390 local _input= _fullpath= 391 ushell="$defaultshell" 392 393 # Make sure the current value of the shell is a valid one 394 if [ -z "$Sflag" ]; then 395 if ! shell_exists $ushell ; then 396 info "Using default shell ${defaultshell}." 397 ushell="$defaultshell" 398 fi 399 fi 400 401 if [ -z "$fflag" ]; then 402 echo -n "Shell ($shells) [${ushell##*/}]: " 403 read _input 404 else 405 _input="$(echo "$fileline" | cut -f9 -d:)" 406 fi 407 if [ -n "$_input" ]; then 408 if [ -n "$Sflag" ]; then 409 ushell="$_input" 410 else 411 _fullpath=$(fullpath_from_shell "$_input") 412 if [ -n "$_fullpath" ]; then 413 ushell="$_fullpath" 414 else 415 err "Invalid shell ($_input) for user $username." 416 info "Using default shell ${defaultshell}." 417 ushell="$defaultshell" 418 fi 419 fi 420 fi 421} 422 423# get_homedir 424# Reads the account's home directory. Used both with interactive input 425# and batch input. 426# 427get_homedir() { 428 local _input= 429 if [ -z "$fflag" ]; then 430 echo -n "Home directory [${homeprefix}/${username}]: " 431 read _input 432 else 433 _input="$(echo "$fileline" | cut -f8 -d:)" 434 fi 435 436 if [ -n "$_input" ]; then 437 uhome="$_input" 438 # if this is a configuration run, then user input is the home 439 # directory prefix. Otherwise it is understood to 440 # be $prefix/$user 441 # 442 [ -z "$configflag" ] && 443 homeprefix="$(dirname "$uhome")" || 444 homeprefix="$uhome" 445 else 446 uhome="${homeprefix}/${username}" 447 fi 448} 449 450# get_homeperm 451# Reads the account's home directory permissions. 452# 453get_homeperm() { 454 local _input= _prompt= 455 uhomeperm=$defaultHomePerm 456 457 if [ -n "$uhomeperm" ]; then 458 _prompt="Home directory permissions [${uhomeperm}]: " 459 else 460 _prompt="Home directory permissions (Leave empty for default): " 461 fi 462 if [ -z "$fflag" ]; then 463 echo -n "$_prompt" 464 read _input 465 fi 466 467 if [ -n "$_input" ]; then 468 uhomeperm="$_input" 469 fi 470} 471 472# get_zfs_home 473# Determine if homeprefix is located on a ZFS filesystem and if 474# so, enable ZFS home dataset creation. 475# 476get_zfs_home() { 477 local _prefix= 478 479 # check if zfs kernel module is loaded before attempting to run zfs to 480 # prevent loading the kernel module on systems that don't use ZFS 481 if ! "$KLDSTATCMD" -q -m zfs; then 482 Zcreate="no" 483 return 484 fi 485 if ! _prefix=$(${ZFSCMD} list -Ho name "${homeprefix}" 2>/dev/null) || 486 [ -z "${_prefix}" ]; then 487 Zcreate="no" 488 return 489 fi 490 zhome="${_prefix}/${username}" 491} 492 493# get_uid 494# Reads a numeric userid in an interactive or batch session. Automatically 495# allocates one if it is not specified. 496# 497get_uid() { 498 local _input= _prompt= 499 uuid=${uidstart} 500 501 if [ -n "$uuid" ]; then 502 uuid=$(get_nextuid "$uuid") 503 _prompt="Uid [$uuid]: " 504 else 505 _prompt="Uid (Leave empty for default): " 506 fi 507 if [ -z "$fflag" ]; then 508 echo -n "$_prompt" 509 read _input 510 else 511 _input="$(echo "$fileline" | cut -f2 -d:)" 512 fi 513 514 [ -n "$_input" ] && uuid=$_input 515 uuid=$(get_nextuid "$uuid") 516 uidstart=$uuid 517} 518 519# get_class 520# Reads login class of account. Can be used in interactive or batch mode. 521# 522get_class() { 523 local _input= _class= 524 uclass="$defaultclass" 525 _class=${uclass:-"default"} 526 527 if [ -z "$fflag" ]; then 528 echo -n "Login class [$_class]: " 529 read _input 530 else 531 _input="$(echo "$fileline" | cut -f4 -d:)" 532 fi 533 534 [ -n "$_input" ] && uclass="$_input" 535} 536 537# get_logingroup 538# Reads user's login group. Can be used in both interactive and batch 539# modes. The specified value can be a group name or its numeric id. 540# This routine leaves the field blank if nothing is provided and 541# a default login group has not been set. The pw(8) command 542# will then provide a login group with the same name as the username. 543# 544get_logingroup() { 545 local _input= 546 ulogingroup="$defaultLgroup" 547 548 if [ -z "$fflag" ]; then 549 echo -n "Login group [${ulogingroup:-$username}]: " 550 read _input 551 else 552 _input="$(echo "$fileline" | cut -f3 -d:)" 553 fi 554 555 # Pw(8) will use the username as login group if it's left empty 556 [ -n "$_input" ] && ulogingroup="$_input" 557} 558 559# get_groups 560# Read additional groups for the user. It can be used in both interactive 561# and batch modes. 562# 563get_groups() { 564 local _input= _group= 565 ugroups="$defaultgroups" 566 _group=${ulogingroup:-"${username}"} 567 568 if [ -z "$configflag" ]; then 569 [ -z "$fflag" ] && echo -n "Login group is $_group. Invite $username" 570 [ -z "$fflag" ] && echo -n " into other groups? [$ugroups]: " 571 else 572 [ -z "$fflag" ] && echo -n "Enter additional groups [$ugroups]: " 573 fi 574 read _input 575 576 [ -n "$_input" ] && ugroups="$_input" 577} 578 579# get_expire_dates 580# Read expiry information for the account and also for the password. This 581# routine is used only from batch processing mode. 582# 583get_expire_dates() { 584 upwexpire="$(echo "$fileline" | cut -f5 -d:)" 585 uexpire="$(echo "$fileline" | cut -f6 -d:)" 586} 587 588# get_password 589# Read the password in batch processing mode. The password field matters 590# only when the password type is "yes" or "random". If the field is empty and the 591# password type is "yes", then it assumes the account has an empty passsword 592# and changes the password type accordingly. If the password type is "random" 593# and the password field is NOT empty, then it assumes the account will NOT 594# have a random password and set passwdtype to "yes." 595# 596get_password() { 597 # We may temporarily change a password type. Make sure it's changed 598 # back to whatever it was before we process the next account. 599 # 600 if [ -n "$savedpwtype" ]; then 601 passwdtype=$savedpwtype 602 savedpwtype= 603 fi 604 605 # There may be a ':' in the password 606 upass=${fileline#*:*:*:*:*:*:*:*:*:} 607 608 if [ -z "$upass" ]; then 609 case $passwdtype in 610 yes) 611 # if it's empty, assume an empty password 612 passwdtype=none 613 savedpwtype=yes 614 ;; 615 esac 616 else 617 case $passwdtype in 618 random) 619 passwdtype=yes 620 savedpwtype=random 621 ;; 622 esac 623 fi 624} 625 626# get_zfs_encryption 627# Ask user if they want to enable encryption on their ZFS home dataset. 628# 629get_zfs_encryption() { 630 local _input= _prompt= 631 _prompt="Enable ZFS encryption? (yes/no) [${Zencrypt}]: " 632 while : ; do 633 echo -n "$_prompt" 634 read _input 635 636 [ -z "$_input" ] && _input=$Zencrypt 637 case $_input in 638 [Nn][Oo]|[Nn]) 639 Zencrypt="no" 640 break 641 ;; 642 [Yy][Ee][Ss]|[Yy][Ee]|[Yy]) 643 Zencrypt="yes" 644 break 645 ;; 646 *) 647 # invalid answer; repeat loop 648 continue 649 ;; 650 esac 651 done 652 653 if [ "${Zencrypt}" = "yes" ]; then 654 zfsopt="-o encryption=on -o keylocation=prompt -o keyformat=passphrase" 655 fi 656} 657 658# create_zfs_chrooted_dataset 659# Create ZFS dataset owned by the user that was just added within a bsdinstall chroot 660# 661create_zfs_chrooted_dataset() { 662 if ! ${ZFSCMD} create -u ${zfsopt} "${zhome}"; then 663 err "There was an error creating ZFS dataset (${zhome})." 664 return 1 665 fi 666 ${ZFSCMD} set mountpoint=legacy "${zhome}" 667 ${MKDIRCMD} -p "${uhome}" 668 ${MOUNTCMD} -t zfs "${zhome}" "${uhome}" 669} 670 671# umount_legacy_zfs 672# Unmount ZFS home directory created as a legacy mount and switch inheritance 673# 674umount_legacy_zfs() { 675 ${UMOUNTCMD} "${uhome}" 676 ${ZFSCMD} inherit mountpoint "${zhome}" 677} 678 679# create_zfs_dataset 680# Create ZFS dataset owned by the user that was just added. 681# 682create_zfs_dataset() { 683 if ! ${ZFSCMD} create ${zfsopt} "${zhome}"; then 684 err "There was an error creating ZFS dataset (${zhome})." 685 return 1 686 else 687 info "Successfully created ZFS dataset (${zhome})." 688 fi 689} 690 691# set_zfs_perms 692# Give new user ownership of newly created zfs dataset. 693# 694set_zfs_perms() { 695 if ! ${ZFSCMD} allow "${username}" create,destroy,mount,snapshot "${zhome}"; then 696 err "There was an error setting permissions on ZFS dataset (${zhome})." 697 return 1 698 fi 699} 700 701# input_from_file 702# Reads a line of account information from standard input and 703# adds it to the user database. 704# 705input_from_file() { 706 local _field= 707 708 while read -r fileline ; do 709 case "$fileline" in 710 \#*|'') 711 ;; 712 *) 713 get_user || continue 714 get_gecos 715 get_uid 716 get_logingroup 717 get_class 718 get_shell 719 get_homedir 720 get_zfs_home 721 get_homeperm 722 get_password 723 get_expire_dates 724 ugroups="$defaultgroups" 725 726 add_user 727 ;; 728 esac 729 done 730} 731 732# input_interactive 733# Prompts for user information interactively, and commits to 734# the user database. 735# 736input_interactive() { 737 local _disable= _pass= _passconfirm= _input= 738 local _random="no" 739 local _emptypass="no" 740 local _usepass="yes" 741 local _logingroup_ok="no" 742 local _groups_ok="no" 743 local _all_ok="yes" 744 case $passwdtype in 745 none) 746 _emptypass="yes" 747 _usepass="yes" 748 ;; 749 no) 750 _usepass="no" 751 ;; 752 random) 753 _random="yes" 754 ;; 755 esac 756 757 get_user 758 get_gecos 759 get_uid 760 761 # The case where group = user is handled elsewhere, so 762 # validate any other groups the user is invited to. 763 until [ "$_logingroup_ok" = yes ]; do 764 get_logingroup 765 _logingroup_ok=yes 766 if [ -n "$ulogingroup" ] && [ "$username" != "$ulogingroup" ]; then 767 if ! ${PWCMD} show group $ulogingroup > /dev/null 2>&1; then 768 echo "Group $ulogingroup does not exist!" 769 _logingroup_ok=no 770 fi 771 fi 772 done 773 until [ "$_groups_ok" = yes ]; do 774 get_groups 775 _groups_ok=yes 776 for i in $ugroups; do 777 if [ "$username" != "$i" ]; then 778 if ! ${PWCMD} show group $i > /dev/null 2>&1; then 779 echo "Group $i does not exist!" 780 _groups_ok=no 781 fi 782 fi 783 done 784 done 785 786 get_class 787 get_shell 788 get_homedir 789 get_homeperm 790 get_zfs_home 791 [ "$Zcreate" = "yes" ] && get_zfs_encryption 792 793 while : ; do 794 echo -n "Use password-based authentication? [$_usepass]: " 795 read _input 796 [ -z "$_input" ] && _input=$_usepass 797 case $_input in 798 [Nn][Oo]|[Nn]) 799 passwdtype="no" 800 ;; 801 [Yy][Ee][Ss]|[Yy][Ee]|[Yy]) 802 while : ; do 803 echo -n "Use an empty password? (yes/no) [$_emptypass]: " 804 read _input 805 [ -n "$_input" ] && _emptypass=$_input 806 case $_emptypass in 807 [Nn][Oo]|[Nn]) 808 echo -n "Use a random password? (yes/no) [$_random]: " 809 read _input 810 [ -n "$_input" ] && _random="$_input" 811 case $_random in 812 [Yy][Ee][Ss]|[Yy][Ee]|[Yy]) 813 passwdtype="random" 814 break 815 ;; 816 esac 817 passwdtype="yes" 818 [ -n "$configflag" ] && break 819 trap 'stty echo; exit' 0 1 2 3 15 820 stty -echo 821 echo -n "Enter password: " 822 IFS= read -r upass 823 echo'' 824 echo -n "Enter password again: " 825 IFS= read -r _passconfirm 826 echo '' 827 stty echo 828 # if user entered a blank password 829 # explicitly ask again. 830 [ -z "$upass$_passconfirm" ] && continue 831 ;; 832 [Yy][Ee][Ss]|[Yy][Ee]|[Yy]) 833 passwdtype="none" 834 break; 835 ;; 836 *) 837 # invalid answer; repeat the loop 838 continue 839 ;; 840 esac 841 if [ "$upass" != "$_passconfirm" ]; then 842 echo "Passwords did not match!" 843 continue 844 fi 845 break 846 done 847 ;; 848 *) 849 # invalid answer; repeat loop 850 continue 851 ;; 852 esac 853 break; 854 done 855 _disable=${disableflag:-"no"} 856 while : ; do 857 echo -n "Lock out the account after creation? [$_disable]: " 858 read _input 859 [ -z "$_input" ] && _input=$_disable 860 case $_input in 861 [Nn][Oo]|[Nn]) 862 disableflag= 863 ;; 864 [Yy][Ee][Ss]|[Yy][Ee]|[Yy]) 865 disableflag=yes 866 ;; 867 *) 868 # invalid answer; repeat loop 869 continue 870 ;; 871 esac 872 break 873 done 874 875 # Display the information we have so far and prompt to 876 # commit it. 877 # 878 _disable=${disableflag:-"no"} 879 [ -z "$configflag" ] && printf "%-11s : %s\n" Username $username 880 case $passwdtype in 881 yes) 882 _pass='*****' 883 ;; 884 no) 885 _pass='<disabled>' 886 ;; 887 none) 888 _pass='<blank>' 889 ;; 890 random) 891 _pass='<random>' 892 ;; 893 esac 894 [ -z "$configflag" ] && printf "%-11s : %s\n" "Password" "$_pass" 895 [ -n "$configflag" ] && printf "%-11s : %s\n" "Pass Type" "$passwdtype" 896 [ -z "$configflag" ] && printf "%-11s : %s\n" "Full Name" "$ugecos" 897 [ -z "$configflag" ] && printf "%-11s : %s\n" "Uid" "$uuid" 898 [ "$Zcreate" = "yes" ] && [ -z "$configflag" ] && 899 printf "%-11s : %s\n" "ZFS dataset" "${zhome}" 900 [ "$Zencrypt" = "yes" ] && [ -z "$configflag" ] && 901 printf "%-11s : %s\n" "Encrypted" "${Zencrypt}" 902 printf "%-11s : %s\n" "Class" "$uclass" 903 printf "%-11s : %s %s\n" "Groups" "${ulogingroup:-$username}" "$ugroups" 904 printf "%-11s : %s\n" "Home" "$uhome" 905 printf "%-11s : %s\n" "Home Mode" "$uhomeperm" 906 printf "%-11s : %s\n" "Shell" "$ushell" 907 printf "%-11s : %s\n" "Locked" "$_disable" 908 while : ; do 909 echo -n "OK? (yes/no) [$_all_ok]: " 910 read _input 911 if [ -z "$_input" ]; then 912 _input=$_all_ok 913 fi 914 case $_input in 915 [Nn][Oo]|[Nn]) 916 return 1 917 ;; 918 [Yy][Ee][Ss]|[Yy][Ee]|[Yy]) 919 add_user 920 ;; 921 *) 922 continue 923 ;; 924 esac 925 break 926 done 927 return 0 928} 929 930#### END SUBROUTINE DEFINITION #### 931 932THISCMD=${0##*/} 933DEFAULTSHELL=/bin/sh 934ADDUSERCONF="${ADDUSERCONF:-/etc/adduser.conf}" 935PWCMD="${PWCMD:-/usr/sbin/pw}" 936MAILCMD="${MAILCMD:-mail}" 937ETCSHELLS="${ETCSHELLS:-/etc/shells}" 938NOHOME="/nonexistent" 939NOLOGIN="nologin" 940NOLOGIN_PATH="/usr/sbin/nologin" 941GREPCMD="/usr/bin/grep" 942DATECMD="/bin/date" 943MKDIRCMD="/bin/mkdir" 944MOUNTCMD="/sbin/mount" 945UMOUNTCMD="/sbin/umount" 946ZFSCMD="/sbin/zfs" 947KLDSTATCMD="/sbin/kldstat" 948 949# Set default values 950# 951username= 952uuid= 953uidstart= 954ugecos= 955ulogingroup= 956uclass= 957uhome= 958uhomeperm= 959upass= 960ushell= 961udotdir=/usr/share/skel 962ugroups= 963uexpire= 964upwexpire= 965shells="$(valid_shells)" 966passwdtype="yes" 967msgfile=/etc/adduser.msg 968msgflag= 969quietflag= 970configflag= 971fflag= 972infile= 973disableflag= 974Dflag= 975Sflag= 976Zcreate="yes" 977readconfig="yes" 978homeprefix="/home" 979randompass= 980fileline= 981savedpwtype= 982defaultclass= 983defaultLgroup= 984defaultgroups= 985defaultshell="${DEFAULTSHELL}" 986defaultHomePerm= 987zfsopt= 988Zencrypt="no" 989 990# Make sure the user running this program is root. This isn't a security 991# measure as much as it is a useful method of reminding the user to 992# 'su -' before he/she wastes time entering data that won't be saved. 993# 994procowner=${procowner:-$(/usr/bin/id -u)} 995if [ "$procowner" != "0" ]; then 996 err 'you must be the super-user (uid 0) to use this utility.' 997 exit 1 998fi 999 1000# Override from our conf file 1001# Quickly go through the commandline line to see if we should read 1002# from our configuration file. The actual parsing of the commandline 1003# arguments happens after we read in our configuration file (commandline 1004# should override configuration file). 1005# 1006for _i in $* ; do 1007 if [ "$_i" = "-N" ]; then 1008 readconfig= 1009 break; 1010 fi 1011done 1012if [ -n "$readconfig" ] && [ -r "${ADDUSERCONF}" ]; then 1013 . "${ADDUSERCONF}" 1014fi 1015 1016# Process command-line options 1017# 1018for _switch ; do 1019 case $_switch in 1020 -L) 1021 defaultclass="$2" 1022 shift; shift 1023 ;; 1024 -C) 1025 configflag=yes 1026 shift 1027 ;; 1028 -D) 1029 Dflag=yes 1030 shift 1031 ;; 1032 -E) 1033 disableflag=yes 1034 shift 1035 ;; 1036 -k) 1037 udotdir="$2" 1038 shift; shift 1039 ;; 1040 -f) 1041 [ "$2" != "-" ] && infile="$2" 1042 fflag=yes 1043 shift; shift 1044 ;; 1045 -g) 1046 defaultLgroup="$2" 1047 shift; shift 1048 ;; 1049 -G) 1050 defaultgroups="$2" 1051 shift; shift 1052 ;; 1053 -h) 1054 show_usage 1055 exit 0 1056 ;; 1057 -d) 1058 homeprefix="$2" 1059 shift; shift 1060 ;; 1061 -m) 1062 case "$2" in 1063 [Nn][Oo]) 1064 msgflag= 1065 ;; 1066 *) 1067 msgflag=yes 1068 msgfile="$2" 1069 ;; 1070 esac 1071 shift; shift 1072 ;; 1073 -M) 1074 defaultHomePerm=$2 1075 shift; shift 1076 ;; 1077 -N) 1078 readconfig= 1079 shift 1080 ;; 1081 -w) 1082 case "$2" in 1083 no|none|random|yes) 1084 passwdtype=$2 1085 ;; 1086 *) 1087 show_usage 1088 exit 1 1089 ;; 1090 esac 1091 shift; shift 1092 ;; 1093 -q) 1094 quietflag=yes 1095 shift 1096 ;; 1097 -s) 1098 defaultshell="$(fullpath_from_shell $2)" 1099 shift; shift 1100 ;; 1101 -S) 1102 Sflag=yes 1103 shift 1104 ;; 1105 -u) 1106 uidstart=$2 1107 shift; shift 1108 ;; 1109 -Z) 1110 Zcreate="no" 1111 shift 1112 ;; 1113 esac 1114done 1115 1116# If the -f switch was used, get input from a file. Otherwise, 1117# this is an interactive session. 1118# 1119if [ -n "$fflag" ]; then 1120 if [ -z "$infile" ]; then 1121 input_from_file 1122 elif [ -n "$infile" ]; then 1123 if [ -r "$infile" ]; then 1124 input_from_file < $infile 1125 else 1126 err "File ($infile) is unreadable or does not exist." 1127 fi 1128 fi 1129else 1130 input_interactive 1131 while : ; do 1132 _another_user="no" 1133 if [ -z "$configflag" ]; then 1134 echo -n "Add another user? (yes/no) [$_another_user]: " 1135 else 1136 echo -n "Re-edit the default configuration? (yes/no) [$_another_user]: " 1137 fi 1138 read _input 1139 if [ -z "$_input" ]; then 1140 _input=$_another_user 1141 fi 1142 case $_input in 1143 [Yy][Ee][Ss]|[Yy][Ee]|[Yy]) 1144 uidstart=$(get_nextuid $uidstart) 1145 input_interactive 1146 continue 1147 ;; 1148 [Nn][Oo]|[Nn]) 1149 echo "Goodbye!" 1150 ;; 1151 *) 1152 continue 1153 ;; 1154 esac 1155 break 1156 done 1157fi 1158