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