Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/usr.sbin/adduser/adduser.sh
102616 views
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 <[email protected]>
28
#
29
#
30
31
# err msg
32
# Display $msg on stderr, unless we're being quiet.
33
#
34
err() {
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
#
43
info() {
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
#
55
get_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
#
75
show_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
#
103
valid_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
#
121
fullpath_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
#
156
shell_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
#
173
save_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
#
195
add_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
#
334
get_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
#
369
get_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
#
389
get_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
#
427
get_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
#
453
get_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
#
476
get_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
#
504
get_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
#
529
get_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
#
551
get_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
#
570
get_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
#
590
get_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
#
603
get_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
#
636
get_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
#
668
create_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
#
681
umount_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
#
689
create_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
#
701
set_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
#
712
input_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
#
743
input_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
939
THISCMD=${0##*/}
940
DEFAULTSHELL=/bin/sh
941
ADDUSERCONF="${ADDUSERCONF:-/etc/adduser.conf}"
942
PWCMD="${PWCMD:-/usr/sbin/pw}"
943
MAILCMD="${MAILCMD:-mail}"
944
ETCSHELLS="${ETCSHELLS:-/etc/shells}"
945
NOHOME="/nonexistent"
946
NOLOGIN="nologin"
947
NOLOGIN_PATH="/usr/sbin/nologin"
948
GREPCMD="/usr/bin/grep"
949
DATECMD="/bin/date"
950
MKDIRCMD="/bin/mkdir"
951
MOUNTCMD="/sbin/mount"
952
UMOUNTCMD="/sbin/umount"
953
ZFSCMD="/sbin/zfs"
954
KLDSTATCMD="/sbin/kldstat"
955
956
# Set default values
957
#
958
username=
959
uuid=
960
uidstart=
961
ugecos=
962
ulogingroup=
963
uclass=
964
uhome=
965
uhomeperm=
966
upass=
967
ushell=
968
udotdir=/usr/share/skel
969
ugroups=
970
uexpire=
971
upwexpire=
972
shells="$(valid_shells)"
973
passwdtype="yes"
974
msgfile=/etc/adduser.msg
975
msgflag=
976
quietflag=
977
configflag=
978
fflag=
979
infile=
980
disableflag=
981
Dflag=
982
Sflag=
983
Zcreate="yes"
984
readconfig="yes"
985
homeprefix="/home"
986
randompass=
987
fileline=
988
savedpwtype=
989
defaultclass=
990
defaultLgroup=
991
defaultgroups=
992
defaultshell="${DEFAULTSHELL}"
993
defaultHomePerm=
994
zfsopt=
995
Zencrypt="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
#
1001
procowner=${procowner:-$(/usr/bin/id -u)}
1002
if [ "$procowner" != "0" ]; then
1003
err 'you must be the super-user (uid 0) to use this utility.'
1004
exit 1
1005
fi
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
#
1013
for _i in $* ; do
1014
if [ "$_i" = "-N" ]; then
1015
readconfig=
1016
break;
1017
fi
1018
done
1019
if [ -n "$readconfig" ] && [ -r "${ADDUSERCONF}" ]; then
1020
. "${ADDUSERCONF}"
1021
fi
1022
1023
# Process command-line options
1024
#
1025
for _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
1121
done
1122
1123
# If the -f switch was used, get input from a file. Otherwise,
1124
# this is an interactive session.
1125
#
1126
if [ -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
1136
else
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
1164
fi
1165
1166