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= _tmp= 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 # Make sure that _prefix is not a subdirectory within a dataset. If it 491 # is, the containing dataset will be the same for it and its parent. 492 _tmp=$(${ZFSCMD} list -Ho name "$(dirname "${homeprefix}")" 2>/dev/null) 493 if [ "${_tmp}" = "${_prefix}" ]; then 494 Zcreate="no" 495 return 496 fi 497 zhome="${_prefix}/${username}" 498} 499 500# get_uid 501# Reads a numeric userid in an interactive or batch session. Automatically 502# allocates one if it is not specified. 503# 504get_uid() { 505 local _input= _prompt= 506 uuid=${uidstart} 507 508 if [ -n "$uuid" ]; then 509 uuid=$(get_nextuid "$uuid") 510 _prompt="Uid [$uuid]: " 511 else 512 _prompt="Uid (Leave empty for default): " 513 fi 514 if [ -z "$fflag" ]; then 515 echo -n "$_prompt" 516 read _input 517 else 518 _input="$(echo "$fileline" | cut -f2 -d:)" 519 fi 520 521 [ -n "$_input" ] && uuid=$_input 522 uuid=$(get_nextuid "$uuid") 523 uidstart=$uuid 524} 525 526# get_class 527# Reads login class of account. Can be used in interactive or batch mode. 528# 529get_class() { 530 local _input= _class= 531 uclass="$defaultclass" 532 _class=${uclass:-"default"} 533 534 if [ -z "$fflag" ]; then 535 echo -n "Login class [$_class]: " 536 read _input 537 else 538 _input="$(echo "$fileline" | cut -f4 -d:)" 539 fi 540 541 [ -n "$_input" ] && uclass="$_input" 542} 543 544# get_logingroup 545# Reads user's login group. Can be used in both interactive and batch 546# modes. The specified value can be a group name or its numeric id. 547# This routine leaves the field blank if nothing is provided and 548# a default login group has not been set. The pw(8) command 549# will then provide a login group with the same name as the username. 550# 551get_logingroup() { 552 local _input= 553 ulogingroup="$defaultLgroup" 554 555 if [ -z "$fflag" ]; then 556 echo -n "Login group [${ulogingroup:-$username}]: " 557 read _input 558 else 559 _input="$(echo "$fileline" | cut -f3 -d:)" 560 fi 561 562 # Pw(8) will use the username as login group if it's left empty 563 [ -n "$_input" ] && ulogingroup="$_input" 564} 565 566# get_groups 567# Read additional groups for the user. It can be used in both interactive 568# and batch modes. 569# 570get_groups() { 571 local _input= _group= 572 ugroups="$defaultgroups" 573 _group=${ulogingroup:-"${username}"} 574 575 if [ -z "$configflag" ]; then 576 [ -z "$fflag" ] && echo -n "Login group is $_group. Invite $username" 577 [ -z "$fflag" ] && echo -n " into other groups? [$ugroups]: " 578 else 579 [ -z "$fflag" ] && echo -n "Enter additional groups [$ugroups]: " 580 fi 581 read _input 582 583 [ -n "$_input" ] && ugroups="$_input" 584} 585 586# get_expire_dates 587# Read expiry information for the account and also for the password. This 588# routine is used only from batch processing mode. 589# 590get_expire_dates() { 591 upwexpire="$(echo "$fileline" | cut -f5 -d:)" 592 uexpire="$(echo "$fileline" | cut -f6 -d:)" 593} 594 595# get_password 596# Read the password in batch processing mode. The password field matters 597# only when the password type is "yes" or "random". If the field is empty and the 598# password type is "yes", then it assumes the account has an empty passsword 599# and changes the password type accordingly. If the password type is "random" 600# and the password field is NOT empty, then it assumes the account will NOT 601# have a random password and set passwdtype to "yes." 602# 603get_password() { 604 # We may temporarily change a password type. Make sure it's changed 605 # back to whatever it was before we process the next account. 606 # 607 if [ -n "$savedpwtype" ]; then 608 passwdtype=$savedpwtype 609 savedpwtype= 610 fi 611 612 # There may be a ':' in the password 613 upass=${fileline#*:*:*:*:*:*:*:*:*:} 614 615 if [ -z "$upass" ]; then 616 case $passwdtype in 617 yes) 618 # if it's empty, assume an empty password 619 passwdtype=none 620 savedpwtype=yes 621 ;; 622 esac 623 else 624 case $passwdtype in 625 random) 626 passwdtype=yes 627 savedpwtype=random 628 ;; 629 esac 630 fi 631} 632 633# get_zfs_encryption 634# Ask user if they want to enable encryption on their ZFS home dataset. 635# 636get_zfs_encryption() { 637 local _input= _prompt= 638 _prompt="Enable ZFS encryption? (yes/no) [${Zencrypt}]: " 639 while : ; do 640 echo -n "$_prompt" 641 read _input 642 643 [ -z "$_input" ] && _input=$Zencrypt 644 case $_input in 645 [Nn][Oo]|[Nn]) 646 Zencrypt="no" 647 break 648 ;; 649 [Yy][Ee][Ss]|[Yy][Ee]|[Yy]) 650 Zencrypt="yes" 651 break 652 ;; 653 *) 654 # invalid answer; repeat loop 655 continue 656 ;; 657 esac 658 done 659 660 if [ "${Zencrypt}" = "yes" ]; then 661 zfsopt="-o encryption=on -o keylocation=prompt -o keyformat=passphrase" 662 fi 663} 664 665# create_zfs_chrooted_dataset 666# Create ZFS dataset owned by the user that was just added within a bsdinstall chroot 667# 668create_zfs_chrooted_dataset() { 669 if ! ${ZFSCMD} create -u ${zfsopt} "${zhome}"; then 670 err "There was an error creating ZFS dataset (${zhome})." 671 return 1 672 fi 673 ${ZFSCMD} set mountpoint=legacy "${zhome}" 674 ${MKDIRCMD} -p "${uhome}" 675 ${MOUNTCMD} -t zfs "${zhome}" "${uhome}" 676} 677 678# umount_legacy_zfs 679# Unmount ZFS home directory created as a legacy mount and switch inheritance 680# 681umount_legacy_zfs() { 682 ${UMOUNTCMD} "${uhome}" 683 ${ZFSCMD} inherit mountpoint "${zhome}" 684} 685 686# create_zfs_dataset 687# Create ZFS dataset owned by the user that was just added. 688# 689create_zfs_dataset() { 690 if ! ${ZFSCMD} create ${zfsopt} "${zhome}"; then 691 err "There was an error creating ZFS dataset (${zhome})." 692 return 1 693 else 694 info "Successfully created ZFS dataset (${zhome})." 695 fi 696} 697 698# set_zfs_perms 699# Give new user ownership of newly created zfs dataset. 700# 701set_zfs_perms() { 702 if ! ${ZFSCMD} allow "${username}" create,destroy,mount,snapshot "${zhome}"; then 703 err "There was an error setting permissions on ZFS dataset (${zhome})." 704 return 1 705 fi 706} 707 708# input_from_file 709# Reads a line of account information from standard input and 710# adds it to the user database. 711# 712input_from_file() { 713 local _field= 714 715 while read -r fileline ; do 716 case "$fileline" in 717 \#*|'') 718 ;; 719 *) 720 get_user || continue 721 get_gecos 722 get_uid 723 get_logingroup 724 get_class 725 get_shell 726 get_homedir 727 get_zfs_home 728 get_homeperm 729 get_password 730 get_expire_dates 731 ugroups="$defaultgroups" 732 733 add_user 734 ;; 735 esac 736 done 737} 738 739# input_interactive 740# Prompts for user information interactively, and commits to 741# the user database. 742# 743input_interactive() { 744 local _disable= _pass= _passconfirm= _input= 745 local _random="no" 746 local _emptypass="no" 747 local _usepass="yes" 748 local _logingroup_ok="no" 749 local _groups_ok="no" 750 local _all_ok="yes" 751 case $passwdtype in 752 none) 753 _emptypass="yes" 754 _usepass="yes" 755 ;; 756 no) 757 _usepass="no" 758 ;; 759 random) 760 _random="yes" 761 ;; 762 esac 763 764 get_user 765 get_gecos 766 get_uid 767 768 # The case where group = user is handled elsewhere, so 769 # validate any other groups the user is invited to. 770 until [ "$_logingroup_ok" = yes ]; do 771 get_logingroup 772 _logingroup_ok=yes 773 if [ -n "$ulogingroup" ] && [ "$username" != "$ulogingroup" ]; then 774 if ! ${PWCMD} show group $ulogingroup > /dev/null 2>&1; then 775 echo "Group $ulogingroup does not exist!" 776 _logingroup_ok=no 777 fi 778 fi 779 done 780 until [ "$_groups_ok" = yes ]; do 781 get_groups 782 _groups_ok=yes 783 for i in $ugroups; do 784 if [ "$username" != "$i" ]; then 785 if ! ${PWCMD} show group $i > /dev/null 2>&1; then 786 echo "Group $i does not exist!" 787 _groups_ok=no 788 fi 789 fi 790 done 791 done 792 793 get_class 794 get_shell 795 get_homedir 796 get_homeperm 797 get_zfs_home 798 [ "$Zcreate" = "yes" ] && get_zfs_encryption 799 800 while : ; do 801 echo -n "Use password-based authentication? [$_usepass]: " 802 read _input 803 [ -z "$_input" ] && _input=$_usepass 804 case $_input in 805 [Nn][Oo]|[Nn]) 806 passwdtype="no" 807 ;; 808 [Yy][Ee][Ss]|[Yy][Ee]|[Yy]) 809 while : ; do 810 echo -n "Use an empty password? (yes/no) [$_emptypass]: " 811 read _input 812 [ -n "$_input" ] && _emptypass=$_input 813 case $_emptypass in 814 [Nn][Oo]|[Nn]) 815 echo -n "Use a random password? (yes/no) [$_random]: " 816 read _input 817 [ -n "$_input" ] && _random="$_input" 818 case $_random in 819 [Yy][Ee][Ss]|[Yy][Ee]|[Yy]) 820 passwdtype="random" 821 break 822 ;; 823 esac 824 passwdtype="yes" 825 [ -n "$configflag" ] && break 826 trap 'stty echo; exit' 0 1 2 3 15 827 stty -echo 828 echo -n "Enter password: " 829 IFS= read -r upass 830 echo'' 831 echo -n "Enter password again: " 832 IFS= read -r _passconfirm 833 echo '' 834 stty echo 835 # if user entered a blank password 836 # explicitly ask again. 837 [ -z "$upass$_passconfirm" ] && continue 838 ;; 839 [Yy][Ee][Ss]|[Yy][Ee]|[Yy]) 840 passwdtype="none" 841 break; 842 ;; 843 *) 844 # invalid answer; repeat the loop 845 continue 846 ;; 847 esac 848 if [ "$upass" != "$_passconfirm" ]; then 849 echo "Passwords did not match!" 850 continue 851 fi 852 break 853 done 854 ;; 855 *) 856 # invalid answer; repeat loop 857 continue 858 ;; 859 esac 860 break; 861 done 862 _disable=${disableflag:-"no"} 863 while : ; do 864 echo -n "Lock out the account after creation? [$_disable]: " 865 read _input 866 [ -z "$_input" ] && _input=$_disable 867 case $_input in 868 [Nn][Oo]|[Nn]) 869 disableflag= 870 ;; 871 [Yy][Ee][Ss]|[Yy][Ee]|[Yy]) 872 disableflag=yes 873 ;; 874 *) 875 # invalid answer; repeat loop 876 continue 877 ;; 878 esac 879 break 880 done 881 882 # Display the information we have so far and prompt to 883 # commit it. 884 # 885 _disable=${disableflag:-"no"} 886 [ -z "$configflag" ] && printf "%-11s : %s\n" Username $username 887 case $passwdtype in 888 yes) 889 _pass='*****' 890 ;; 891 no) 892 _pass='<disabled>' 893 ;; 894 none) 895 _pass='<blank>' 896 ;; 897 random) 898 _pass='<random>' 899 ;; 900 esac 901 [ -z "$configflag" ] && printf "%-11s : %s\n" "Password" "$_pass" 902 [ -n "$configflag" ] && printf "%-11s : %s\n" "Pass Type" "$passwdtype" 903 [ -z "$configflag" ] && printf "%-11s : %s\n" "Full Name" "$ugecos" 904 [ -z "$configflag" ] && printf "%-11s : %s\n" "Uid" "$uuid" 905 [ "$Zcreate" = "yes" ] && [ -z "$configflag" ] && 906 printf "%-11s : %s\n" "ZFS dataset" "${zhome}" 907 [ "$Zencrypt" = "yes" ] && [ -z "$configflag" ] && 908 printf "%-11s : %s\n" "Encrypted" "${Zencrypt}" 909 printf "%-11s : %s\n" "Class" "$uclass" 910 printf "%-11s : %s %s\n" "Groups" "${ulogingroup:-$username}" "$ugroups" 911 printf "%-11s : %s\n" "Home" "$uhome" 912 printf "%-11s : %s\n" "Home Mode" "$uhomeperm" 913 printf "%-11s : %s\n" "Shell" "$ushell" 914 printf "%-11s : %s\n" "Locked" "$_disable" 915 while : ; do 916 echo -n "OK? (yes/no) [$_all_ok]: " 917 read _input 918 if [ -z "$_input" ]; then 919 _input=$_all_ok 920 fi 921 case $_input in 922 [Nn][Oo]|[Nn]) 923 return 1 924 ;; 925 [Yy][Ee][Ss]|[Yy][Ee]|[Yy]) 926 add_user 927 ;; 928 *) 929 continue 930 ;; 931 esac 932 break 933 done 934 return 0 935} 936 937#### END SUBROUTINE DEFINITION #### 938 939THISCMD=${0##*/} 940DEFAULTSHELL=/bin/sh 941ADDUSERCONF="${ADDUSERCONF:-/etc/adduser.conf}" 942PWCMD="${PWCMD:-/usr/sbin/pw}" 943MAILCMD="${MAILCMD:-mail}" 944ETCSHELLS="${ETCSHELLS:-/etc/shells}" 945NOHOME="/nonexistent" 946NOLOGIN="nologin" 947NOLOGIN_PATH="/usr/sbin/nologin" 948GREPCMD="/usr/bin/grep" 949DATECMD="/bin/date" 950MKDIRCMD="/bin/mkdir" 951MOUNTCMD="/sbin/mount" 952UMOUNTCMD="/sbin/umount" 953ZFSCMD="/sbin/zfs" 954KLDSTATCMD="/sbin/kldstat" 955 956# Set default values 957# 958username= 959uuid= 960uidstart= 961ugecos= 962ulogingroup= 963uclass= 964uhome= 965uhomeperm= 966upass= 967ushell= 968udotdir=/usr/share/skel 969ugroups= 970uexpire= 971upwexpire= 972shells="$(valid_shells)" 973passwdtype="yes" 974msgfile=/etc/adduser.msg 975msgflag= 976quietflag= 977configflag= 978fflag= 979infile= 980disableflag= 981Dflag= 982Sflag= 983Zcreate="yes" 984readconfig="yes" 985homeprefix="/home" 986randompass= 987fileline= 988savedpwtype= 989defaultclass= 990defaultLgroup= 991defaultgroups= 992defaultshell="${DEFAULTSHELL}" 993defaultHomePerm= 994zfsopt= 995Zencrypt="no" 996 997# Make sure the user running this program is root. This isn't a security 998# measure as much as it is a useful method of reminding the user to 999# 'su -' before he/she wastes time entering data that won't be saved. 1000# 1001procowner=${procowner:-$(/usr/bin/id -u)} 1002if [ "$procowner" != "0" ]; then 1003 err 'you must be the super-user (uid 0) to use this utility.' 1004 exit 1 1005fi 1006 1007# Override from our conf file 1008# Quickly go through the commandline line to see if we should read 1009# from our configuration file. The actual parsing of the commandline 1010# arguments happens after we read in our configuration file (commandline 1011# should override configuration file). 1012# 1013for _i in $* ; do 1014 if [ "$_i" = "-N" ]; then 1015 readconfig= 1016 break; 1017 fi 1018done 1019if [ -n "$readconfig" ] && [ -r "${ADDUSERCONF}" ]; then 1020 . "${ADDUSERCONF}" 1021fi 1022 1023# Process command-line options 1024# 1025for _switch ; do 1026 case $_switch in 1027 -L) 1028 defaultclass="$2" 1029 shift; shift 1030 ;; 1031 -C) 1032 configflag=yes 1033 shift 1034 ;; 1035 -D) 1036 Dflag=yes 1037 shift 1038 ;; 1039 -E) 1040 disableflag=yes 1041 shift 1042 ;; 1043 -k) 1044 udotdir="$2" 1045 shift; shift 1046 ;; 1047 -f) 1048 [ "$2" != "-" ] && infile="$2" 1049 fflag=yes 1050 shift; shift 1051 ;; 1052 -g) 1053 defaultLgroup="$2" 1054 shift; shift 1055 ;; 1056 -G) 1057 defaultgroups="$2" 1058 shift; shift 1059 ;; 1060 -h) 1061 show_usage 1062 exit 0 1063 ;; 1064 -d) 1065 homeprefix="$2" 1066 shift; shift 1067 ;; 1068 -m) 1069 case "$2" in 1070 [Nn][Oo]) 1071 msgflag= 1072 ;; 1073 *) 1074 msgflag=yes 1075 msgfile="$2" 1076 ;; 1077 esac 1078 shift; shift 1079 ;; 1080 -M) 1081 defaultHomePerm=$2 1082 shift; shift 1083 ;; 1084 -N) 1085 readconfig= 1086 shift 1087 ;; 1088 -w) 1089 case "$2" in 1090 no|none|random|yes) 1091 passwdtype=$2 1092 ;; 1093 *) 1094 show_usage 1095 exit 1 1096 ;; 1097 esac 1098 shift; shift 1099 ;; 1100 -q) 1101 quietflag=yes 1102 shift 1103 ;; 1104 -s) 1105 defaultshell="$(fullpath_from_shell $2)" 1106 shift; shift 1107 ;; 1108 -S) 1109 Sflag=yes 1110 shift 1111 ;; 1112 -u) 1113 uidstart=$2 1114 shift; shift 1115 ;; 1116 -Z) 1117 Zcreate="no" 1118 shift 1119 ;; 1120 esac 1121done 1122 1123# If the -f switch was used, get input from a file. Otherwise, 1124# this is an interactive session. 1125# 1126if [ -n "$fflag" ]; then 1127 if [ -z "$infile" ]; then 1128 input_from_file 1129 elif [ -n "$infile" ]; then 1130 if [ -r "$infile" ]; then 1131 input_from_file < $infile 1132 else 1133 err "File ($infile) is unreadable or does not exist." 1134 fi 1135 fi 1136else 1137 input_interactive 1138 while : ; do 1139 _another_user="no" 1140 if [ -z "$configflag" ]; then 1141 echo -n "Add another user? (yes/no) [$_another_user]: " 1142 else 1143 echo -n "Re-edit the default configuration? (yes/no) [$_another_user]: " 1144 fi 1145 read _input 1146 if [ -z "$_input" ]; then 1147 _input=$_another_user 1148 fi 1149 case $_input in 1150 [Yy][Ee][Ss]|[Yy][Ee]|[Yy]) 1151 uidstart=$(get_nextuid $uidstart) 1152 input_interactive 1153 continue 1154 ;; 1155 [Nn][Oo]|[Nn]) 1156 echo "Goodbye!" 1157 ;; 1158 *) 1159 continue 1160 ;; 1161 esac 1162 break 1163 done 1164fi 1165