Replace the perl versions of adduser and rmuser with shell script versions.
Submitted by: Mike Makonnen <mtm@identd.net> Approved by: re
This commit is contained in:
parent
6bfd0bdc80
commit
7cdfce092a
@ -1,6 +1,6 @@
|
||||
# $FreeBSD$
|
||||
|
||||
SCRIPTS=adduser.perl rmuser.perl
|
||||
SCRIPTS=adduser.sh rmuser.sh
|
||||
MAN= adduser.8 rmuser.8
|
||||
|
||||
.include <bsd.prog.mk>
|
||||
|
@ -1,5 +1,7 @@
|
||||
.\" Copyright (c) 1995-1996 Wolfram Schneider <wosch@FreeBSD.org>. Berlin.
|
||||
.\" All rights reserved.
|
||||
.\" Copyright (c) 2002 Michael Telahun Makonnen <makonnen@pacbell.net>
|
||||
.\" All rights reserved.
|
||||
.\"
|
||||
.\" Redistribution and use in source and binary forms, with or without
|
||||
.\" modification, are permitted provided that the following conditions
|
||||
@ -24,7 +26,7 @@
|
||||
.\"
|
||||
.\" $FreeBSD$
|
||||
.\"
|
||||
.Dd January 9, 1995
|
||||
.Dd August 14, 2002
|
||||
.Dt ADDUSER 8
|
||||
.Os
|
||||
.Sh NAME
|
||||
@ -33,37 +35,39 @@
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Bk -words
|
||||
.Op Fl check_only
|
||||
.Op Fl class Ar login_class
|
||||
.Op Fl config_create
|
||||
.Op Fl dotdir Ar dotdir
|
||||
.Op Fl group Ar login_group
|
||||
.Op Fl h | help
|
||||
.Op Fl home Ar home
|
||||
.Op Fl message Ar message_file
|
||||
.Op Fl noconfig
|
||||
.Op Fl shell Ar shell
|
||||
.Op Fl s | silent | q | quiet
|
||||
.Op Fl uid Ar uid_start
|
||||
.Op Fl v | verbose
|
||||
.Op Fl CENhq
|
||||
.Op Fl G Ar groups
|
||||
.Op Fl L Ar login_class
|
||||
.Op Fl d Ar partition
|
||||
.Op Fl f Ar file
|
||||
.Op Fl k Ar dotdir
|
||||
.Op Fl m Ar message_file
|
||||
.Op Fl s Ar shell
|
||||
.Op Fl u Ar uid_start
|
||||
.Op Fl w Ar type
|
||||
.Ek
|
||||
.Sh DESCRIPTION
|
||||
The
|
||||
.Nm
|
||||
utility is a simple program for adding new users.
|
||||
It checks the passwd, group and shell databases.
|
||||
It creates passwd/group entries,
|
||||
.Ev HOME
|
||||
directory, dotfiles and sends the new user a welcome message.
|
||||
.Nm adduser
|
||||
program is a shell script, implemented around the
|
||||
.Xr pw 8
|
||||
command, for adding new users.
|
||||
It creates passwd/group entries, a home directory,
|
||||
copies dotfiles and sends the new user a welcome message.
|
||||
It supports two modes of operation. It may be used interactively
|
||||
at the command line to add one user at a time or it may be directed
|
||||
to get the list of new users from a file and operate in batch mode
|
||||
without requiring any user interaction.
|
||||
.Sh RESTRICTIONS
|
||||
.Bl -tag -width Ds -compact
|
||||
.It Sy username
|
||||
Login name.
|
||||
May contain only lowercase characters or digits.
|
||||
The user name is restricted to whatever
|
||||
.Xr pw 8
|
||||
will accept. Generally this means it
|
||||
may contain only lowercase characters or digits.
|
||||
Maximum length
|
||||
is 16 characters (see
|
||||
.Xr setlogin 2
|
||||
BUGS section).
|
||||
is 16 characters.
|
||||
The reasons for this limit are "Historical".
|
||||
Given that people have traditionally wanted to break this
|
||||
limit for aesthetic reasons, it's never been of great importance to break
|
||||
@ -80,20 +84,26 @@ The NIS protocol mandates an 8-character username.
|
||||
If you need a longer login name for e-mail addresses,
|
||||
you can define an alias in
|
||||
.Pa /etc/mail/aliases .
|
||||
.It Sy fullname
|
||||
Firstname and surname.
|
||||
.It Sy full name
|
||||
This is typically known as the gecos field and usually contains
|
||||
the user's full name. Additionally, it may contain a comma separated
|
||||
list of values such as office number and work and home phones. If the
|
||||
name contains an amperstand it will be replaced by the capitalized
|
||||
login name when displayed by other programs.
|
||||
The
|
||||
.Ql Pa \&:
|
||||
character is not allowed.
|
||||
.It Sy shell
|
||||
Only valid shells from the shell database or sliplogin and pppd
|
||||
Only valid shells from the shell database (/etc/shells) are allowed. In
|
||||
addition, only the base name of the shell is necessary, not the full path.
|
||||
.It Sy uid
|
||||
Automatically generated or your choice, must be less than 32000.
|
||||
Automatically generated or your choice. It must be less than 32000.
|
||||
.It Sy gid/login group
|
||||
Your choice or automatically generated.
|
||||
Automatically generated or your choice. It must be less than 32000.
|
||||
.It Sy password
|
||||
If not empty, password is encoded with
|
||||
.Xr crypt 3 .
|
||||
You may choose an empty password, disable the password, use a
|
||||
randomly generated password or specify your own plaintext password,
|
||||
which will be encrypted before being stored in the user database.
|
||||
.El
|
||||
.Sh UNIQUE GROUPS
|
||||
Perhaps you're missing what
|
||||
@ -114,96 +124,183 @@ users into groups and having to muck with the umask when working in a shared
|
||||
area.
|
||||
.Pp
|
||||
I have been using this model for almost 10 years and found that it works
|
||||
for most situations, and has never gotten in the way.
|
||||
(Rod Grimes)
|
||||
for most situations, and has never gotten in the way. (Rod Grimes)
|
||||
.Sh CONFIGURATION
|
||||
.Bl -enum
|
||||
.It
|
||||
Read internal variables.
|
||||
.It
|
||||
Read configuration file (/etc/adduser.conf).
|
||||
.It
|
||||
Parse command line options.
|
||||
.El
|
||||
The
|
||||
.Nm
|
||||
utility reads its configuration information from
|
||||
.Ar /etc/adduser.conf .
|
||||
If this file does not exist it will use predefined defaults. While
|
||||
this file may be edited by hand the safer option is to use the
|
||||
.Op Fl C
|
||||
command line argument. With this argument
|
||||
.Nm
|
||||
will start interactive input, save the answers to its prompts in
|
||||
.Ar /etc/adduser.conf ,
|
||||
and promptly exit without modifying the user
|
||||
database. Options specified on the command line will take precedence over
|
||||
any values saved in this file.
|
||||
.Sh OPTIONS
|
||||
.Bl -tag -width Ds
|
||||
.It Fl check_only
|
||||
Check /etc/passwd, /etc/group, /etc/shells and exit.
|
||||
.It Fl class Ar login_class
|
||||
Set default login class.
|
||||
.It Fl config_create
|
||||
Create new configuration and message file and exit.
|
||||
.It Fl dotdir Ar directory
|
||||
.It Fl C
|
||||
Create new configuration file and exit. This option is mutually exclusive
|
||||
with the
|
||||
.Op Fl f
|
||||
option.
|
||||
.It Fl d Ar partition
|
||||
Home partition. Default partition, under which all user directories
|
||||
will be located.
|
||||
.It Fl E
|
||||
Disable the account. This option will lock the account by prepending
|
||||
the string *LOCKED* to the password field. The account may be unlocked
|
||||
by the super-user with the
|
||||
.Xr pw 8
|
||||
command:
|
||||
.Pp
|
||||
.Dl "pw unlock [name|uid]"
|
||||
.It Fl f Ar file
|
||||
Get the list of accounts to create from
|
||||
.Ar file .
|
||||
If
|
||||
.Ar file
|
||||
is '`-'', then get the list from standard input. If this option
|
||||
is specified
|
||||
.Nm
|
||||
will operate in batch mode and will not seek any user input. If an
|
||||
error is encountered while processing an account it will write a
|
||||
message to standard error and move to the next account. The format
|
||||
of the input file is described below.
|
||||
.It Fl G Ar groups
|
||||
Additional group(s). By default the user name is used as the login group.
|
||||
This option allows the user to specify additional groups to add users to.
|
||||
.It Fl h
|
||||
Print a summary of options and exit.
|
||||
.It Fl k Ar directory
|
||||
Copy files from
|
||||
.Ar directory
|
||||
into the
|
||||
.Ev HOME
|
||||
into the home
|
||||
directory of new users,
|
||||
.Ql Pa dot.foo
|
||||
will be renamed to
|
||||
.Ql Pa .foo .
|
||||
Don't copy files if
|
||||
.Ar directory
|
||||
specified is equal to
|
||||
.Ar no .
|
||||
For security make all files writable and readable for owner,
|
||||
don't allow group or world to write files and allow only owner
|
||||
to read/execute/write
|
||||
.Pa .rhost ,
|
||||
.Pa .Xauthority ,
|
||||
.Pa .kermrc ,
|
||||
.Pa .netrc ,
|
||||
.Pa Mail ,
|
||||
.Pa prv ,
|
||||
.Pa iscreen ,
|
||||
.Pa term .
|
||||
.It Fl group Ar login_group
|
||||
Login group.
|
||||
.Ar USER
|
||||
means that the username is to be used as login group.
|
||||
.It Fl help , h , \&?
|
||||
Print a summary of options and exit.
|
||||
.It Fl home Ar partition
|
||||
Default home partition where all users located.
|
||||
.It Fl message Ar file
|
||||
.It Fl L Ar login_class
|
||||
Set default login class.
|
||||
.It Fl m Ar file
|
||||
Send new users a welcome message from
|
||||
.Ar file .
|
||||
Specifying a value of
|
||||
.Ar no
|
||||
for
|
||||
.Ar file
|
||||
causes no message to be sent to new users.
|
||||
.It Fl noconfig
|
||||
causes no message to be sent to new users. Please note that the message
|
||||
file can reference the internal variables of the
|
||||
.Nm
|
||||
script.
|
||||
.It Fl N
|
||||
Do not read the default configuration file.
|
||||
.It Fl shell Ar shell
|
||||
Default shell for new users.
|
||||
.It Fl silent , s , quiet , q
|
||||
Few warnings, questions, bug reports.
|
||||
.It Fl uid Ar uid
|
||||
.It Fl q
|
||||
Minimal user feedback. In particular, the random password will not be echoed to
|
||||
standard output.
|
||||
.It Fl s Ar shell
|
||||
Default shell for new users. The
|
||||
.Ar shell
|
||||
argument must be the base name of the shell , NOT the full path.
|
||||
It must exist in
|
||||
.Ar /etc/shells
|
||||
to be considered a valid shell.
|
||||
.It Fl u Ar uid
|
||||
Use uid's from
|
||||
.Ar uid
|
||||
on up.
|
||||
.It Fl verbose , v
|
||||
Many warnings, questions.
|
||||
Recommended for novice users.
|
||||
.El
|
||||
.Sh FORMATS
|
||||
.Bl -tag -width Ds -compact
|
||||
.Ql Pa #
|
||||
is a comment.
|
||||
.It Sy configuration file
|
||||
The
|
||||
.It Fl w Ar type
|
||||
Password type. The
|
||||
.Nm
|
||||
utility reads and writes this file.
|
||||
See
|
||||
.Pa /etc/adduser.conf
|
||||
for more details.
|
||||
.It Sy message file
|
||||
Eval variables in this file.
|
||||
See
|
||||
.Pa /etc/adduser.message
|
||||
for more
|
||||
details.
|
||||
utility allows the user to specify what type of password to create.
|
||||
The
|
||||
.Ar type
|
||||
argument may have one of the following values:
|
||||
.Bl -tag -width ".Dv random"
|
||||
.It Dv no
|
||||
Disable the password. Instead of an encrypted string the passowrd field
|
||||
will contain a single '`*'' character.
|
||||
The user may not login until the super-user
|
||||
manually enables the password.
|
||||
.It Dv none
|
||||
Use an empty string as the password.
|
||||
.It Dv yes
|
||||
Use a user supplied string as the password. In interactive mode
|
||||
the user will be prompted for the password. In batch mode, the
|
||||
last (10th) field in the line is assumed to be the password.
|
||||
.It Dv random
|
||||
Generate a random string and use it as a password. The password will
|
||||
be echoed to standard output. In addition it will be available for
|
||||
inclusion in the message file in the
|
||||
.Ar randompass
|
||||
environment variable.
|
||||
.El
|
||||
.Sh FORMAT
|
||||
.Bl -tag -width Ds -compact
|
||||
When the
|
||||
.Op Fl f
|
||||
option is used the account information must be stored in a specific
|
||||
format. All empty lines or lines beginning with a
|
||||
.Ql Pa #
|
||||
will be ignored. All other lines must contain ten colon (:) separated
|
||||
fields as described below. Command line options do not take precedence
|
||||
over values in the fields. Only the password field may contain a
|
||||
.Ql Pa :
|
||||
character as part of the string.
|
||||
.Pp
|
||||
.Dl "name:uid:gid:class:change:expire:gecos:home_dir:shell:password"
|
||||
.Bl -tag -width ".Dv password"
|
||||
.It Dv name
|
||||
Login name. This field may not be empty.
|
||||
.It Dv uid
|
||||
Numeric login user id. If this field is left empty it will be automatically
|
||||
generated.
|
||||
.It Dv gid
|
||||
Numeric primary group id. If this field is left empty a group with the
|
||||
same name as the user name will be created and its gid will be used
|
||||
instead.
|
||||
.It Dv class
|
||||
Login class. This field may be left empty.
|
||||
.It Dv change
|
||||
Password ageing.
|
||||
This field denotes the password change date for the account. The format of this
|
||||
field is the same as the format of the
|
||||
.Op Fl p
|
||||
argument to
|
||||
.Xr pw 8 .
|
||||
It may be 'dd-mmm-yy[yy]', where 'dd' is for the day, 'mmm' is for the month
|
||||
in numeric or alphabetical format: '10 or Oct', and 'yy[yy]' is the four or two digit year.
|
||||
To denote a time relative to the current date the format
|
||||
is: '+n[mhdwoy]', where 'n' denotes a number, followed by the Minutes, Hours,
|
||||
Days, Weeks, Months or Years after which the password must be changed.
|
||||
This field may be left empty to turn it off.
|
||||
.It Dv expire
|
||||
Account expiration. This field denotes the expiry date of the account. The account may
|
||||
not be used after the specified date. The format of this field is the same as that
|
||||
for password ageing. This field may be left empty to turn it off.
|
||||
.It Dv gecos
|
||||
Full name and other extra information about the user.
|
||||
.It Dv home_dir
|
||||
Home directory. If this field is left empty it will be automatically
|
||||
created by appending the username to the home partition.
|
||||
.It Dv shell
|
||||
Login Shell. This field should contain the full path to a valid login shell.
|
||||
.It Dv password
|
||||
User password. This field should contain a plaintext string, which will
|
||||
be encrypted before being placed in the user database. If the password type is 'yes'
|
||||
and this field is empty it is assumed the account will have any empty password. If
|
||||
the password type is 'random' and this field is NOT empty its contents will be used
|
||||
as a password. This field will be ignored if the
|
||||
.Op Fl p
|
||||
flag is used with a
|
||||
.Ar no
|
||||
or
|
||||
.Ar none
|
||||
argument. Be carefull not to terminate this field with a closing ':' because it will
|
||||
be treated as part of the password.
|
||||
.El
|
||||
.Sh FILES
|
||||
.Bl -tag -width /etc/master.passwdxx -compact
|
||||
@ -226,9 +323,7 @@ logfile for adduser
|
||||
.El
|
||||
.Sh SEE ALSO
|
||||
.Xr chpass 1 ,
|
||||
.Xr finger 1 ,
|
||||
.Xr passwd 1 ,
|
||||
.Xr setlogin 2 ,
|
||||
.Xr aliases 5 ,
|
||||
.Xr group 5 ,
|
||||
.Xr login.conf 5 ,
|
||||
@ -239,9 +334,28 @@ logfile for adduser
|
||||
.Xr rmuser 8 ,
|
||||
.Xr vipw 8 ,
|
||||
.Xr yp 8
|
||||
.\" .Sh BUGS
|
||||
.Sh HISTORY
|
||||
The
|
||||
.Nm
|
||||
utility appeared in
|
||||
command appeared in
|
||||
.Fx 2.1 .
|
||||
.Sh AUTHORS
|
||||
This manual page and the original script, in perl, was written by
|
||||
.An Wolfram Schneider <wosch@FreeBSD.org>. The replacement script, written as a Bourne
|
||||
shell script with some enhancements, and the man page modification that
|
||||
came with it were done by
|
||||
.An Mike Makonnen <mtm@identd.net> .
|
||||
.Sh BUGS
|
||||
In order for
|
||||
.Nm
|
||||
to correctly expand variables such as $username and $randompass in the message sent
|
||||
to new users it must let the shell evaluate each line of the message file. This means
|
||||
that shell commands can also be embedded in the message file. The
|
||||
.Nm
|
||||
utility attemps to mitigate the possibility of an attacker using this feature by
|
||||
refusing to evaluate the file if it is not owned and writeable only by the root user.
|
||||
In addition, shell special characters and operators will have to be escaped when
|
||||
used in the message file.
|
||||
.Pp
|
||||
Also, password ageing and account expiry times are currently setable only in batch mode.
|
||||
The user should be able to set them in interactive mode as well.
|
||||
|
File diff suppressed because it is too large
Load Diff
874
usr.sbin/adduser/adduser.sh
Normal file
874
usr.sbin/adduser/adduser.sh
Normal file
@ -0,0 +1,874 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Copyright (c) 2002 Michael Telahun Makonnen. All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions
|
||||
# are met:
|
||||
# 1. Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# 2. Redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution.
|
||||
# 3. The name of the author may not be used to endorse or promote products
|
||||
# derived from this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#
|
||||
# Email: Mike Makonnen <mtm@identd.net>
|
||||
#
|
||||
# $FreeBSD$
|
||||
#
|
||||
|
||||
# err msg
|
||||
# Display $msg on stderr, unless we're being quiet.
|
||||
#
|
||||
err() {
|
||||
if [ -z "$quietflag" ]; then
|
||||
echo 1>&2 ${THISCMD}: ERROR: $*
|
||||
fi
|
||||
}
|
||||
|
||||
# info msg
|
||||
# Display $msg on stdout, unless we're being quiet.
|
||||
#
|
||||
info() {
|
||||
if [ -z "$quietflag" ]; then
|
||||
echo ${THISCMD}: INFO: $*
|
||||
fi
|
||||
}
|
||||
|
||||
# get_nextuid
|
||||
# Output the value of $_uid if it is available for use. If it
|
||||
# is not, output the value of the next higher uid that is available.
|
||||
# If a uid is not specified, output the first available uid, as indicated
|
||||
# by pw(8).
|
||||
#
|
||||
get_nextuid () {
|
||||
_uid=$1
|
||||
_nextuid=
|
||||
|
||||
if [ -z "$_uid" ]; then
|
||||
_nextuid="`${PWCMD} usernext | cut -f1 -d:`"
|
||||
else
|
||||
while : ; do
|
||||
${PWCMD} usershow $_uid > /dev/null 2>&1
|
||||
if [ ! "$?" -eq 0 ]; then
|
||||
_nextuid=$_uid
|
||||
break
|
||||
fi
|
||||
_uid=$(($_uid + 1))
|
||||
done
|
||||
fi
|
||||
echo $_nextuid
|
||||
}
|
||||
|
||||
# show_usage
|
||||
# Display usage information for this utility.
|
||||
#
|
||||
show_usage() {
|
||||
echo "usage: ${THISCMD} [options]"
|
||||
echo " options may include:"
|
||||
echo " -C save to the configuration file only"
|
||||
echo " -E disable this account after creation"
|
||||
echo " -G additional groups to add accounts to"
|
||||
echo " -L login class of the user"
|
||||
echo " -N do not read configuration file"
|
||||
echo " -d home directory"
|
||||
echo " -f file from which input will be received"
|
||||
echo " -h display this usage message"
|
||||
echo " -k path to skeleton home directory"
|
||||
echo " -m user welcome message file"
|
||||
echo " -q absolute minimal user feedback"
|
||||
echo " -s shell"
|
||||
echo " -u uid to start at"
|
||||
echo " -w password type: no, none, yes or random"
|
||||
}
|
||||
|
||||
# valid_shells
|
||||
# Outputs a list of valid shells from /etc/shells. Only the
|
||||
# basename of the shell is output.
|
||||
#
|
||||
valid_shells() {
|
||||
_prefix=
|
||||
cat ${ETCSHELLS} |
|
||||
while read _path _junk ; do
|
||||
case $_path in
|
||||
\#*|'')
|
||||
;;
|
||||
*)
|
||||
echo -n "${_prefix}`basename $_path`"
|
||||
_prefix=' '
|
||||
;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
# fullpath_from_shell shell
|
||||
# Given $shell, the basename component of a valid shell, get the
|
||||
# full path to the shell from the /etc/shells file.
|
||||
#
|
||||
fullpath_from_shell() {
|
||||
_shell=$1
|
||||
[ -z "$_shell" ] && return 1
|
||||
|
||||
cat ${ETCSHELLS} |
|
||||
while read _path _junk ; do
|
||||
case "$_path" in
|
||||
\#*|'')
|
||||
;;
|
||||
*)
|
||||
if [ "`basename $_path`" = "$_shell" ]; then
|
||||
echo $_path
|
||||
return 0
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
# save_config
|
||||
# Save some variables to a configuration file.
|
||||
# Note: not all script variables are saved, only those that
|
||||
# it makes sense to save.
|
||||
#
|
||||
save_config() {
|
||||
echo "# Configuration file for adduser(8)." > ${ADDUSERCONF}
|
||||
echo "# NOTE: only *some* variables are saved." >> ${ADDUSERCONF}
|
||||
echo "# Last Modified on `date`." >> ${ADDUSERCONF}
|
||||
echo '' >> ${ADDUSERCONF}
|
||||
echo "defaultclass=$uclass" >> ${ADDUSERCONF}
|
||||
echo "defaultgroups=$ugroups" >> ${ADDUSERCONF}
|
||||
echo "passwdtype=$passwdtype" >> ${ADDUSERCONF}
|
||||
echo "homeprefix=$homeprefix" >> ${ADDUSERCONF}
|
||||
echo "defaultshell=$ushell" >> ${ADDUSERCONF}
|
||||
echo "udotdir=$udotdir" >> ${ADDUSERCONF}
|
||||
echo "msgfile=$msgfile" >> ${ADDUSERCONF}
|
||||
echo "disableflag=$disableflag" >> ${ADDUSERCONF}
|
||||
}
|
||||
|
||||
# add_user
|
||||
# Add a user to the user database. If the user chose to send a welcome
|
||||
# message or lock the account, do so.
|
||||
#
|
||||
add_user() {
|
||||
|
||||
# Is this a configuration run? If so, don't modify user database.
|
||||
#
|
||||
if [ -n "$configflag" ]; then
|
||||
save_config
|
||||
return
|
||||
fi
|
||||
|
||||
_uid=
|
||||
_name=
|
||||
_comment=
|
||||
_gecos=
|
||||
_home=
|
||||
_group=
|
||||
_grouplist=
|
||||
_shell=
|
||||
_class=
|
||||
_dotdir=
|
||||
_expire=
|
||||
_pwexpire=
|
||||
_passwd=
|
||||
_upasswd=
|
||||
_passwdmethod=
|
||||
|
||||
_name="-n $username"
|
||||
[ -n "$uuid" ] && _uid="-u $uuid"
|
||||
[ -n "$ulogingroup" ] && _group="-g $ulogingroup"
|
||||
[ -n "$ugroups" ] && _grouplist="-G $ugroups"
|
||||
[ -n "$ushell" ] && _shell="-s $ushell"
|
||||
[ -n "$uhome" ] && _home="-m -d $uhome"
|
||||
[ -n "$uclass" ] && _class="-L $uclass"
|
||||
[ -n "$ugecos" ] && _comment="-c '$ugecos'"
|
||||
[ -n "$udotdir" ] && _dotdir="-k $udotdir"
|
||||
[ -n "$uexpire" ] && _expire="-e '$uexpire'"
|
||||
[ -n "$upwexpire" ] && _pwexpire="-p '$upwexpire'"
|
||||
case $passwdtype in
|
||||
no)
|
||||
_passwdmethod="-w no"
|
||||
_passwd="-h -"
|
||||
;;
|
||||
yes)
|
||||
_passwdmethod="-w yes"
|
||||
_passwd="-h 0"
|
||||
_upasswd="echo $upass |"
|
||||
;;
|
||||
none)
|
||||
_passwdmethod="-w none"
|
||||
;;
|
||||
random)
|
||||
_passwdmethod="-w random"
|
||||
;;
|
||||
esac
|
||||
|
||||
_pwcmd="$_upasswd ${PWCMD} useradd $_uid $_name $_group $_grouplist $_comment"
|
||||
_pwcmd="$_pwcmd $_shell $_class $_home $_dotdir $_passwdmethod $_passwd"
|
||||
_pwcmd="$_pwcmd $_expire $_pwexpire"
|
||||
|
||||
if ! _output=`eval $_pwcmd` ; then
|
||||
err "There was an error adding user ($username)."
|
||||
return 1
|
||||
else
|
||||
info "Successfully added ($username) to the user database."
|
||||
if [ "random" = "$passwdtype" ]; then
|
||||
randompass="$_output"
|
||||
info "Password for ($username) is: $randompass"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -n "$disableflag" ]; then
|
||||
if ${PWCMD} lock $username ; then
|
||||
info "Account ($username) is locked."
|
||||
else
|
||||
info "Account ($username) could NOT be locked."
|
||||
fi
|
||||
fi
|
||||
|
||||
_line=
|
||||
_owner=
|
||||
_perms=
|
||||
if [ -n "$msgflag" ]; then
|
||||
[ -r "$msgfile" ] && {
|
||||
# We're evaluating the contents of an external file.
|
||||
# Let's not open ourselves up for attack. _perms will
|
||||
# be empty if it's writeable only by the owner. _owner
|
||||
# will *NOT* be empty if the file is owned by root.
|
||||
#
|
||||
_dir="`dirname $msgfile`"
|
||||
_file="`basename $msgfile`"
|
||||
_perms=`/usr/bin/find $_dir -name $_file -perm +07022 -prune`
|
||||
_owner=`/usr/bin/find $_dir -name $_file -user 0 -prune`
|
||||
if [ -z "$_owner" -o -n "$_perms" ]; then
|
||||
err "The message file ($msgfile) may be writeable only by root."
|
||||
return 1
|
||||
fi
|
||||
cat "$msgfile" |
|
||||
while read _line ; do
|
||||
eval echo "$_line"
|
||||
done | ${MAILCMD} -s"Welcome" ${username}
|
||||
info "Sent welcome message to ($username)."
|
||||
}
|
||||
fi
|
||||
}
|
||||
|
||||
# get_user
|
||||
# Reads username of the account from standard input or from a global
|
||||
# variable containing an account line from a file. The username is
|
||||
# required. If this is an interactive session it will prompt in
|
||||
# a loop until a username is entered. If it is batch processing from
|
||||
# a file it will output an error message and return to the caller.
|
||||
#
|
||||
get_user() {
|
||||
_input=
|
||||
|
||||
# No need to take down user names if this is a configuration saving run.
|
||||
[ -n "$configflag" ] && return
|
||||
|
||||
while : ; do
|
||||
if [ -z "$fflag" ]; then
|
||||
echo -n "Username: "
|
||||
read _input
|
||||
else
|
||||
_input="`echo "$fileline" | cut -f1 -d:`"
|
||||
fi
|
||||
|
||||
# There *must* be a username. If this is an interactive
|
||||
# session give the user an opportunity to retry.
|
||||
#
|
||||
if [ -z "$_input" ]; then
|
||||
err "You must enter a username!"
|
||||
[ -z "$fflag" ] && continue
|
||||
fi
|
||||
break
|
||||
done
|
||||
username="$_input"
|
||||
}
|
||||
|
||||
# get_gecos
|
||||
# Reads extra information about the user. Can be used both in interactive
|
||||
# and batch (from file) mode.
|
||||
#
|
||||
get_gecos() {
|
||||
_input=
|
||||
|
||||
# No need to take down additional user information for a configuration run.
|
||||
[ -n "$configflag" ] && return
|
||||
|
||||
if [ -z "$fflag" ]; then
|
||||
echo -n "Full name: "
|
||||
read _input
|
||||
else
|
||||
_input="`echo "$fileline" | cut -f7 -d:`"
|
||||
fi
|
||||
ugecos="$_input"
|
||||
}
|
||||
|
||||
# get_shell
|
||||
# Get the account's shell. Works in interactive and batch mode. It
|
||||
# accepts only the base name of the shell, NOT the full path.
|
||||
# If an invalid shell is entered it will simply use the default shell.
|
||||
#
|
||||
get_shell() {
|
||||
_input=
|
||||
_fullpath=
|
||||
ushell="$defaultshell"
|
||||
|
||||
# Make sure the current value of the shell is a valid one
|
||||
_shellchk="grep '^$ushell$' ${ETCSHELLS} > /dev/null 2>&1"
|
||||
eval $_shellchk || {
|
||||
err "Invalid shell ($ushell). Using default shell ${defaultshell}."
|
||||
ushell="$defaultshell"
|
||||
}
|
||||
|
||||
if [ -z "$fflag" ]; then
|
||||
echo -n "Shell ($shells) [`basename $ushell`]: "
|
||||
read _input
|
||||
else
|
||||
_input="`echo "$fileline" | cut -f9 -d:`"
|
||||
fi
|
||||
if [ -n "$_input" ]; then
|
||||
_fullpath=`fullpath_from_shell $_input`
|
||||
if [ -n "$_fullpath" ]; then
|
||||
ushell="$_fullpath"
|
||||
else
|
||||
err "Invalid shell selection. Using default shell ${defaultshell}."
|
||||
ushell="$defaultshell"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# get_homedir
|
||||
# Reads the account's home directory. Used both with interactive input
|
||||
# and batch input.
|
||||
#
|
||||
get_homedir() {
|
||||
_input=
|
||||
if [ -z "$fflag" ]; then
|
||||
echo -n "Home directory [${homeprefix}/${username}]: "
|
||||
read _input
|
||||
else
|
||||
_input="`echo "$fileline" | cut -f8 -d:`"
|
||||
fi
|
||||
|
||||
if [ -n "$_input" ]; then
|
||||
uhome="$_input"
|
||||
# if this is a configuration run, then user input is the home
|
||||
# directory prefix. Otherwise it is understood to
|
||||
# be $prefix/$user
|
||||
#
|
||||
[ -z "$configflag" ] && homeprefix="`dirname $uhome`" || homeprefix="$uhome"
|
||||
else
|
||||
uhome="${homeprefix}/${username}"
|
||||
fi
|
||||
}
|
||||
|
||||
# get_uid
|
||||
# Reads a numeric userid in an interactive or batch session. Automatically
|
||||
# allocates one if it is not specified.
|
||||
#
|
||||
get_uid() {
|
||||
uuid=${uidstart}
|
||||
_input=
|
||||
_prompt=
|
||||
|
||||
# No need to take down uids for a configuration saving run.
|
||||
[ -n "$configflag" ] && return
|
||||
|
||||
if [ -n "$uuid" ]; then
|
||||
_prompt="Uid [$uuid]: "
|
||||
else
|
||||
_prompt="Uid (Leave empty for default): "
|
||||
fi
|
||||
if [ -z "$fflag" ]; then
|
||||
echo -n $_prompt
|
||||
read _input
|
||||
else
|
||||
_input="`echo "$fileline" | cut -f2 -d:`"
|
||||
fi
|
||||
|
||||
[ -n "$_input" ] && uuid=$_input
|
||||
uuid=`get_nextuid $uuid`
|
||||
uidstart=$uuid
|
||||
}
|
||||
|
||||
# get_class
|
||||
# Reads login class of account. Can be used in interactive or batch mode.
|
||||
#
|
||||
get_class() {
|
||||
uclass="$defaultclass"
|
||||
_input=
|
||||
_class=${uclass:-"default"}
|
||||
|
||||
if [ -z "$fflag" ]; then
|
||||
echo -n "Login class [$_class]: "
|
||||
read _input
|
||||
else
|
||||
_input="`echo "$fileline" | cut -f4 -d:`"
|
||||
fi
|
||||
|
||||
[ -n "$_input" ] && uclass="$_input"
|
||||
}
|
||||
|
||||
# get_logingroup
|
||||
# Reads user's login group. Can be used in both interactive and batch
|
||||
# modes. The specified value can be a group name or its numeric id.
|
||||
# This routine leaves the field blank if nothing is provided. The pw(8)
|
||||
# command will then provide a login group with the same name as the username.
|
||||
#
|
||||
get_logingroup() {
|
||||
ulogingroup=
|
||||
_input=
|
||||
|
||||
# No need to take down a login group for a configuration saving run.
|
||||
[ -n "$configflag" ] && return
|
||||
|
||||
if [ -z "$fflag" ]; then
|
||||
echo -n "Login group [$username]: "
|
||||
read _input
|
||||
else
|
||||
_input="`echo "$fileline" | cut -f3 -d:`"
|
||||
fi
|
||||
|
||||
# Pw(8) will use the username as login group if it's left empty
|
||||
[ -n "$_input" ] && ulogingroup="$_input" || ulogingroup=
|
||||
}
|
||||
|
||||
# get_groups
|
||||
# Read additional groups for the user. It can be used in both interactive
|
||||
# and batch modes.
|
||||
#
|
||||
get_groups() {
|
||||
ugroups="$defaultgroups"
|
||||
_input=
|
||||
_group=${ulogingroup:-"${username}"}
|
||||
|
||||
if [ -z "$configflag" ]; then
|
||||
[ -z "$fflag" ] && echo -n "Login group is $_group. Invite $username"
|
||||
[ -z "$fflag" ] && echo -n " into other groups? [$ugroups]: "
|
||||
else
|
||||
[ -z "$fflag" ] && echo -n "Enter additional groups [$ugroups]: "
|
||||
fi
|
||||
read _input
|
||||
|
||||
[ -n "$_input" ] && ugroups="$_input"
|
||||
}
|
||||
|
||||
# get_expire_dates
|
||||
# Read expiry information for the account and also for the password. This
|
||||
# routine is used only from batch processing mode.
|
||||
#
|
||||
get_expire_dates() {
|
||||
upwexpire="`echo "$fileline" | cut -f5 -d:`"
|
||||
uexpire="`echo "$fileline" | cut -f6 -d:`"
|
||||
}
|
||||
|
||||
# get_password
|
||||
# Read the password in batch processing mode. The password field matters
|
||||
# only when the password type is "yes" or "random". If the field is empty and the
|
||||
# password type is "yes", then it assumes the account has an empty passsword
|
||||
# and changes the password type accordingly. If the password type is "random"
|
||||
# and the password field is NOT empty, then it assumes the account will NOT
|
||||
# have a random password and set passwdtype to "yes."
|
||||
#
|
||||
get_password() {
|
||||
# We may temporarily change a password type. Make sure it's changed
|
||||
# back to whatever it was before we process the next account.
|
||||
#
|
||||
[ -n "$savedpwtype" ] && {
|
||||
passwdtype=$savedpwtype
|
||||
savedpwtype=
|
||||
}
|
||||
|
||||
# There may be a ':' in the password
|
||||
upass=${fileline#*:*:*:*:*:*:*:*:*:}
|
||||
|
||||
if [ -z "$upass" ]; then
|
||||
case $passwdtype in
|
||||
yes)
|
||||
# if it's empty, assume an empty password
|
||||
passwdtype=none
|
||||
savedpwtype=yes
|
||||
;;
|
||||
esac
|
||||
else
|
||||
case $passwdtype in
|
||||
random)
|
||||
passwdtype=yes
|
||||
savedpwtype=random
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
}
|
||||
|
||||
# input_from_file
|
||||
# Reads a line of account information from standard input and
|
||||
# adds it to the user database.
|
||||
#
|
||||
input_from_file() {
|
||||
_field=
|
||||
|
||||
while read fileline ; do
|
||||
case "$fileline" in
|
||||
\#*|'')
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
|
||||
get_user || continue
|
||||
get_gecos
|
||||
get_uid
|
||||
get_logingroup
|
||||
get_class
|
||||
get_shell
|
||||
get_homedir
|
||||
get_password
|
||||
get_expire_dates
|
||||
|
||||
add_user
|
||||
done
|
||||
}
|
||||
|
||||
# input_interactive
|
||||
# Prompts for user information interactively, and commits to
|
||||
# the user database.
|
||||
#
|
||||
input_interactive() {
|
||||
|
||||
_disable=
|
||||
_pass=
|
||||
_passconfirm=
|
||||
_random="no"
|
||||
_emptypass="no"
|
||||
_usepass="yes"
|
||||
case $passwdtype in
|
||||
none)
|
||||
_emptypass="yes"
|
||||
_usepass="yes"
|
||||
;;
|
||||
no)
|
||||
_usepass="no"
|
||||
;;
|
||||
random)
|
||||
_random="yes"
|
||||
;;
|
||||
esac
|
||||
|
||||
get_user
|
||||
get_gecos
|
||||
get_uid
|
||||
get_logingroup
|
||||
get_groups
|
||||
get_class
|
||||
get_shell
|
||||
get_homedir
|
||||
|
||||
while : ; do
|
||||
echo -n "Use password-based authentication? [$_usepass]: "
|
||||
read _input
|
||||
[ -z "$_input" ] && _input=$_usepass
|
||||
case $_input in
|
||||
[Nn][Oo]|[Nn])
|
||||
passwdtype="no"
|
||||
;;
|
||||
[Yy][Ee][Ss]|[Yy][Ee]|[Yy])
|
||||
while : ; do
|
||||
echo -n "Use an empty password? (yes/no) [$_emptypass]: "
|
||||
read _input
|
||||
[ -n "$_input" ] && _emptypass=$_input
|
||||
case $_emptypass in
|
||||
[Nn][Oo]|[Nn])
|
||||
echo -n "Use a random password? (yes/no) [$_random]: "
|
||||
read _input
|
||||
[ -n "$_input" ] && _random="$_input"
|
||||
case $_random in
|
||||
[Yy][Ee][Ss]|[Yy][Ee]|[Yy])
|
||||
passwdtype="random"
|
||||
break
|
||||
;;
|
||||
esac
|
||||
passwdtype="yes"
|
||||
trap 'stty echo; exit' 0 1 2 3 15
|
||||
stty -echo
|
||||
echo -n "Enter password: "
|
||||
read upass
|
||||
echo''
|
||||
echo -n "Enter password again: "
|
||||
read _passconfirm
|
||||
echo ''
|
||||
stty echo
|
||||
# if user entered a blank password
|
||||
# explicitly ask again.
|
||||
[ -z "$upass" -a -z "$_passconfirm" ] \
|
||||
&& continue
|
||||
;;
|
||||
[Yy][Ee][Ss]|[Yy][Ee]|[Yy])
|
||||
passwdtype="none"
|
||||
break;
|
||||
;;
|
||||
*)
|
||||
# invalid answer; repeat the loop
|
||||
continue
|
||||
;;
|
||||
esac
|
||||
if [ "$upass" != "$_passconfirm" ]; then
|
||||
echo "Passwords did not match!"
|
||||
continue
|
||||
fi
|
||||
break
|
||||
done
|
||||
;;
|
||||
*)
|
||||
# invalid answer; repeat loop
|
||||
continue
|
||||
;;
|
||||
esac
|
||||
break;
|
||||
done
|
||||
_disable=${disableflag:-"no"}
|
||||
while : ; do
|
||||
echo -n "Lock out the account after creation? [$_disable]: "
|
||||
read _input
|
||||
[ -z "$_input" ] && _input=$_disable
|
||||
case $_input in
|
||||
[Nn][Oo]|[Nn])
|
||||
disableflag=
|
||||
;;
|
||||
[Yy][Ee][Ss]|[Yy][Ee]|[Yy])
|
||||
disableflag=yes
|
||||
;;
|
||||
*)
|
||||
# invalid answer; repeat loop
|
||||
continue
|
||||
;;
|
||||
esac
|
||||
break
|
||||
done
|
||||
|
||||
# Display the information we have so far and prompt to
|
||||
# commit it.
|
||||
#
|
||||
_disable=${disableflag:-"no"}
|
||||
[ -z "$configflag" ] && printf "%-10s : %s\n" Username $username
|
||||
case $passwdtype in
|
||||
yes)
|
||||
_pass='*****'
|
||||
;;
|
||||
no)
|
||||
_pass='<disabled>'
|
||||
;;
|
||||
none)
|
||||
_pass='<blank>'
|
||||
;;
|
||||
random)
|
||||
_pass='<random>'
|
||||
;;
|
||||
esac
|
||||
printf "%-10s : %s\n" "Password" "$_pass"
|
||||
[ -z "$configflag" ] && printf "%-10s : %s\n" "Full Name" "$ugecos"
|
||||
[ -z "$configflag" ] && printf "%-10s : %s\n" "Uid" "$uuid"
|
||||
printf "%-10s : %s\n" "Class" "$uclass"
|
||||
[ -z "$configflag" ] && printf "%-10s : %s %s\n" "Groups" "${ulogingroup:-$username}" "$ugroups"
|
||||
printf "%-10s : %s\n" "Home" "$uhome"
|
||||
printf "%-10s : %s\n" "Shell" "$ushell"
|
||||
printf "%-10s : %s\n" "Locked" "$_disable"
|
||||
while : ; do
|
||||
echo -n "OK? (yes/no): "
|
||||
read _input
|
||||
case $_input in
|
||||
[Nn][Oo]|[Nn])
|
||||
return 1
|
||||
;;
|
||||
[Yy][Ee][Ss]|[Yy][Ee]|[Yy])
|
||||
add_user
|
||||
;;
|
||||
*)
|
||||
continue
|
||||
;;
|
||||
esac
|
||||
break
|
||||
done
|
||||
return 0
|
||||
}
|
||||
|
||||
#### END SUBROUTINE DEFENITION ####
|
||||
|
||||
THISCMD=`/usr/bin/basename $0`
|
||||
DEFAULTSHELL=/bin/sh
|
||||
ADDUSERCONF="${ADDUSERCONF:-/etc/adduser.conf}"
|
||||
PWCMD="${PWCMD:-/usr/sbin/pw}"
|
||||
MAILCMD="${MAILCMD:-mail}"
|
||||
ETCSHELLS="${ETCSHELLS:-/etc/shells}"
|
||||
|
||||
# Set default values
|
||||
#
|
||||
username=
|
||||
uuid=
|
||||
uidstart=
|
||||
ugecos=
|
||||
ulogingroup=
|
||||
uclass=
|
||||
uhome=
|
||||
upass=
|
||||
ushell=
|
||||
udotdir=/usr/share/skel
|
||||
ugroups=
|
||||
uexpire=
|
||||
upwexpire=
|
||||
shells="`valid_shells`"
|
||||
passwdtype="yes"
|
||||
msgfile=/etc/adduser.msg
|
||||
msgflag=
|
||||
quietflag=
|
||||
configflag=
|
||||
fflag=
|
||||
infile=
|
||||
disableflag=
|
||||
readconfig="yes"
|
||||
homeprefix="/home"
|
||||
randompass=
|
||||
fileline=
|
||||
savedpwtype=
|
||||
defaultclass=
|
||||
defaultgoups=
|
||||
defaultshell="${DEFAULTSHELL}"
|
||||
|
||||
# Make sure the user running this program is root. This isn't a security
|
||||
# measure as much as it is a usefull method of reminding the user to
|
||||
# 'su -' before he/she wastes time entering data that won't be saved.
|
||||
#
|
||||
procowner=${procowner:-`/usr/bin/id -u`}
|
||||
if [ "$procowner" != "0" ]; then
|
||||
err 'you must be the super-user (uid 0) to use this utility.'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Overide from our conf file
|
||||
# Quickly go through the commandline line to see if we should read
|
||||
# from our configuration file. The actual parsing of the commandline
|
||||
# arguments happens after we read in our configuration file (commandline
|
||||
# should override configuration file).
|
||||
#
|
||||
for _i in $* ; do
|
||||
if [ "$_i" = "-N" ]; then
|
||||
readconfig=
|
||||
break;
|
||||
fi
|
||||
done
|
||||
if [ -n "$readconfig" ]; then
|
||||
# On a long-lived system, the first time this script is run it
|
||||
# will barf upon reading the configuration file for its perl predecessor.
|
||||
if ( . ${ADDUSERCONF} > /dev/null 2>&1 ); then
|
||||
[ -r ${ADDUSERCONF} ] && . ${ADDUSERCONF} > /dev/null 2>&1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Proccess command-line options
|
||||
#
|
||||
for _switch ; do
|
||||
case $_switch in
|
||||
-L)
|
||||
defaultclass="$2"
|
||||
shift; shift
|
||||
;;
|
||||
-C)
|
||||
configflag=yes
|
||||
shift
|
||||
;;
|
||||
-E)
|
||||
disableflag=yes
|
||||
shift
|
||||
;;
|
||||
-k)
|
||||
udotdir="$2"
|
||||
shift; shift
|
||||
;;
|
||||
-f)
|
||||
[ "$2" != "-" ] && infile="$2"
|
||||
fflag=yes
|
||||
shift; shift
|
||||
;;
|
||||
-G)
|
||||
defaultgroups="$2"
|
||||
shift; shift
|
||||
;;
|
||||
-h)
|
||||
show_usage
|
||||
exit 0
|
||||
;;
|
||||
-d)
|
||||
homeprefix="$2"
|
||||
shift; shift
|
||||
;;
|
||||
-m)
|
||||
case "$2" in
|
||||
[Nn][Oo])
|
||||
msgflag=
|
||||
;;
|
||||
*)
|
||||
msgflag=yes
|
||||
msgfile="$2"
|
||||
;;
|
||||
esac
|
||||
shift; shift
|
||||
;;
|
||||
-N)
|
||||
readconfig=
|
||||
shift
|
||||
;;
|
||||
-w)
|
||||
case "$2" in
|
||||
no|none|random|yes)
|
||||
passwdtype=$2
|
||||
;;
|
||||
*)
|
||||
show_usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
shift; shift
|
||||
;;
|
||||
-q)
|
||||
quietflag=yes
|
||||
shift
|
||||
;;
|
||||
-s)
|
||||
defaultshell="`fullpath_from_shell $2`"
|
||||
shift; shift
|
||||
;;
|
||||
-u)
|
||||
uidstart=$2
|
||||
shift; shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# If the -f switch was used, get input from a file. Otherwise,
|
||||
# this is an interactive session.
|
||||
#
|
||||
if [ -n "$fflag" ]; then
|
||||
if [ -z "$infile" ]; then
|
||||
input_from_file
|
||||
elif [ -n "$infile" ]; then
|
||||
if [ -r "$infile" ]; then
|
||||
input_from_file < $infile
|
||||
else
|
||||
err "File ($infile) is unreadable or does not exist."
|
||||
fi
|
||||
fi
|
||||
else
|
||||
input_interactive
|
||||
fi
|
@ -26,7 +26,7 @@
|
||||
.\"
|
||||
.\" $FreeBSD$
|
||||
.\"
|
||||
.Dd February 23, 1997
|
||||
.Dd May 10, 2002
|
||||
.Dt RMUSER 8
|
||||
.Os
|
||||
.Sh NAME
|
||||
@ -35,11 +35,13 @@
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Op Fl y
|
||||
.Op Ar username
|
||||
.Op Fl f Ar file
|
||||
.Op Ar username ...
|
||||
.Sh DESCRIPTION
|
||||
The
|
||||
.Nm
|
||||
utility
|
||||
utility removes one or more users submitted on the command line
|
||||
or from a file. In removing a user from the system, this utility
|
||||
.Pp
|
||||
.Bl -enum
|
||||
.It
|
||||
@ -77,29 +79,27 @@ the group is removed; this complements
|
||||
per-user unique groups).
|
||||
.El
|
||||
.Pp
|
||||
The
|
||||
The
|
||||
.Nm
|
||||
utility
|
||||
politely refuses to remove users whose uid is 0 (typically root), since
|
||||
utility refuses to remove users whose uid is 0 (typically root), since
|
||||
certain actions (namely, killing all the user's processes, and perhaps
|
||||
removing the user's home directory) would cause damage to a running system.
|
||||
If it is necessary to remove a user whose uid is 0, see
|
||||
.Xr vipw 8
|
||||
for information on directly editing the password file, by which the desired
|
||||
user's
|
||||
.Xr passwd 5
|
||||
entry may be removed manually.
|
||||
for information on directly editing the password file
|
||||
.Pp
|
||||
If not running "affirmatively" (i.e., option
|
||||
If
|
||||
.Nm
|
||||
was not invoked with the
|
||||
.Fl y
|
||||
is not specified),
|
||||
.Nm
|
||||
shows the selected user's password file entry and asks for confirmation
|
||||
that you wish to remove the user. If the user's home directory is owned
|
||||
by the user,
|
||||
.Nm
|
||||
asks whether you wish to remove the user's home directory and everything
|
||||
below.
|
||||
switch it will
|
||||
show the selected user's password file entry and ask for confirmation
|
||||
that the user be removed. It will then ask for confirmation to delete
|
||||
the user's home directory. If the answer is in the affirmative, the home
|
||||
directory and any files and subdirectories under it will be deleted only if
|
||||
they are owned by the user. See
|
||||
.Xr pw 8
|
||||
for more details.
|
||||
.Pp
|
||||
As
|
||||
.Nm
|
||||
@ -112,13 +112,27 @@ Available options:
|
||||
.Pp
|
||||
.Bl -tag -width username
|
||||
.It Fl y
|
||||
Affirm - any question that would be asked is answered implicitly in
|
||||
the affirmative (i.e., yes). A username must also be specified on the
|
||||
command line if this option is used.
|
||||
.It Ar \&username
|
||||
Identifies the user to be removed; if not present,
|
||||
Implicitly answer "yes" to any and all prompts. Currently this includes
|
||||
prompts on whether to remove the specified user and whether to remove
|
||||
the home directory. This option requires that either the
|
||||
.Fl f
|
||||
option be used or one or more user names be given as commmand line
|
||||
arguments.
|
||||
.It Fl f
|
||||
The
|
||||
.Nm
|
||||
interactively asks for the user to be removed.
|
||||
utility will get a list of users to be removed from
|
||||
.Ar file ,
|
||||
which will contain one user per line. Anything following a hash mark (#),
|
||||
including the hash mark itself, is considered a comment and will not
|
||||
be processed. If the file is owned by anyone other than a user with
|
||||
uid 0 or is writeable by anyone other than the owner
|
||||
.Nm
|
||||
will refuse to continue.
|
||||
.It Ar \&username
|
||||
Identifies one or more users to be removed; if not present,
|
||||
.Nm
|
||||
interactively asks for one or more users to be removed.
|
||||
.El
|
||||
.Sh FILES
|
||||
.Bl -tag -width /etc/master.passwd -compact
|
||||
@ -137,12 +151,13 @@ interactively asks for the user to be removed.
|
||||
.Xr group 5 ,
|
||||
.Xr passwd 5 ,
|
||||
.Xr adduser 8 ,
|
||||
.Xr pw 8 ,
|
||||
.Xr pwd_mkdb 8 ,
|
||||
.Xr vipw 8
|
||||
.Sh HISTORY
|
||||
The
|
||||
.Nm
|
||||
utility appeared in
|
||||
command appeared in
|
||||
.Fx 2.2 .
|
||||
.\" .Sh AUTHOR
|
||||
.\" Guy Helmer, Ames, Iowa
|
||||
@ -152,9 +167,7 @@ The
|
||||
utility does not comprehensively search the filesystem for all files
|
||||
owned by the removed user and remove them; to do so on a system
|
||||
of any size is prohibitively slow and I/O intensive.
|
||||
The
|
||||
.Nm
|
||||
utility also is unable to remove symbolic links that were created by the
|
||||
It is also unable to remove symbolic links that were created by the
|
||||
user in
|
||||
.Pa /tmp
|
||||
or
|
||||
|
@ -1,601 +0,0 @@
|
||||
#!/usr/bin/perl
|
||||
# -*- perl -*-
|
||||
# Copyright 1995, 1996, 1997 Guy Helmer, Ames, Iowa 50014.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions
|
||||
# are met:
|
||||
# 1. Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer as
|
||||
# the first lines of this file unmodified.
|
||||
# 2. Redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution.
|
||||
# 3. The name of the author may not be used to endorse or promote products
|
||||
# derived from this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY GUY HELMER ``AS IS'' AND ANY EXPRESS OR
|
||||
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
# IN NO EVENT SHALL GUY HELMER BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#
|
||||
# rmuser - Perl script to remove users
|
||||
#
|
||||
# Guy Helmer <ghelmer@cs.iastate.edu>, 02/23/97
|
||||
#
|
||||
# $FreeBSD$
|
||||
|
||||
use Fcntl;
|
||||
|
||||
sub LOCK_SH {0x01;}
|
||||
sub LOCK_EX {0x02;}
|
||||
sub LOCK_NB {0x04;}
|
||||
sub LOCK_UN {0x08;}
|
||||
sub F_SETFD {2;}
|
||||
|
||||
$ENV{"PATH"} = "/bin:/sbin:/usr/bin:/usr/sbin";
|
||||
umask(022);
|
||||
$whoami = $0;
|
||||
$passwd_file = "/etc/master.passwd";
|
||||
$passwd_tmp = "/etc/ptmp";
|
||||
$group_file = "/etc/group";
|
||||
$new_group_file = "${group_file}.new.$$";
|
||||
$mail_dir = "/var/mail";
|
||||
$crontab_dir = "/var/cron/tabs";
|
||||
$affirm = 0;
|
||||
|
||||
#$debug = 1;
|
||||
|
||||
sub cleanup {
|
||||
local($sig) = @_;
|
||||
|
||||
print STDERR "Caught signal SIG$sig -- cleaning up.\n";
|
||||
&unlockpw;
|
||||
if (-e $new_passwd_file) {
|
||||
unlink $new_passwd_file;
|
||||
}
|
||||
exit(0);
|
||||
}
|
||||
|
||||
sub lockpw {
|
||||
# Open the password file for reading
|
||||
if (!open(MASTER_PW, "$passwd_file")) {
|
||||
print STDERR "${whoami}: Error: Couldn't open ${passwd_file}: $!\n";
|
||||
exit(1);
|
||||
}
|
||||
# Set the close-on-exec flag just in case
|
||||
fcntl(MASTER_PW, &F_SETFD, 1);
|
||||
# Apply an advisory lock the password file
|
||||
if (!flock(MASTER_PW, &LOCK_EX|&LOCK_NB)) {
|
||||
print STDERR "${whoami}: Error: Couldn't lock ${passwd_file}: $!\n";
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
sub unlockpw {
|
||||
flock(MASTER_PW, &LOCK_UN);
|
||||
}
|
||||
|
||||
$SIG{'INT'} = 'cleanup';
|
||||
$SIG{'QUIT'} = 'cleanup';
|
||||
$SIG{'HUP'} = 'cleanup';
|
||||
$SIG{'TERM'} = 'cleanup';
|
||||
|
||||
if ($#ARGV == 1 && $ARGV[0] eq '-y') {
|
||||
shift @ARGV;
|
||||
$affirm = 1;
|
||||
}
|
||||
|
||||
if ($#ARGV > 0) {
|
||||
print STDERR "usage: ${whoami} [-y] [username]\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if ($< != 0) {
|
||||
print STDERR "${whoami}: Error: you must be root to use ${whoami}\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
&lockpw;
|
||||
|
||||
if ($#ARGV == 0) {
|
||||
# Username was given as a parameter
|
||||
$login_name = pop(@ARGV);
|
||||
} else {
|
||||
if ($affirm) {
|
||||
print STDERR "${whoami}: Error: -y option given without username!\n";
|
||||
&unlockpw;
|
||||
exit 1;
|
||||
}
|
||||
# Get the user name from the user
|
||||
$login_name = &get_login_name;
|
||||
}
|
||||
|
||||
($name, $password, $uid, $gid, $change, $class, $gecos, $home_dir, $shell) =
|
||||
(getpwnam("$login_name"));
|
||||
|
||||
if (!defined $uid) {
|
||||
print STDERR "${whoami}: Error: User ${login_name} not in password database\n";
|
||||
&unlockpw;
|
||||
exit 1;
|
||||
}
|
||||
|
||||
if ($uid == 0) {
|
||||
print "${whoami}: Error: I'd rather not remove a user with a uid of 0.\n";
|
||||
&unlockpw;
|
||||
exit 1;
|
||||
}
|
||||
|
||||
if (! $affirm) {
|
||||
print "Matching password entry:\n\n$name\:$password\:$uid\:$gid\:$class\:$change\:0\:$gecos\:$home_dir\:$shell\n\n";
|
||||
|
||||
$ans = &get_yn("Is this the entry you wish to remove? ");
|
||||
|
||||
if ($ans eq 'N') {
|
||||
print "${whoami}: Informational: User ${login_name} not removed.\n";
|
||||
&unlockpw;
|
||||
exit 0;
|
||||
}
|
||||
}
|
||||
|
||||
#
|
||||
# Get owner of user's home directory; don't remove home dir if not
|
||||
# owned by $login_name
|
||||
|
||||
$remove_directory = 1;
|
||||
|
||||
if (-l $home_dir) {
|
||||
$real_home_dir = &resolvelink($home_dir);
|
||||
} else {
|
||||
$real_home_dir = $home_dir;
|
||||
}
|
||||
|
||||
#
|
||||
# If home_dir is a symlink and points to something that isn't a directory,
|
||||
# or if home_dir is not a symlink and is not a directory, don't remove
|
||||
# home_dir -- seems like a good thing to do, but probably isn't necessary...
|
||||
|
||||
if (((-l $home_dir) && ((-e $real_home_dir) && !(-d $real_home_dir))) ||
|
||||
(!(-l $home_dir) && !(-d $home_dir))) {
|
||||
print STDERR "${whoami}: Informational: Home ${home_dir} is not a directory, so it won't be removed\n";
|
||||
$remove_directory = 0;
|
||||
}
|
||||
|
||||
if (length($real_home_dir) && -d $real_home_dir) {
|
||||
$dir_owner = (stat($real_home_dir))[4]; # UID
|
||||
if ($dir_owner != $uid) {
|
||||
print STDERR "${whoami}: Informational: Home dir ${real_home_dir} is" .
|
||||
" not owned by ${login_name} (uid ${dir_owner})\n," .
|
||||
"\tso it won't be removed\n";
|
||||
$remove_directory = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if ($remove_directory && ! $affirm) {
|
||||
$ans = &get_yn("Remove user's home directory ($home_dir)? ");
|
||||
if ($ans eq 'N') {
|
||||
$remove_directory = 0;
|
||||
}
|
||||
}
|
||||
|
||||
#exit 0 if $debug;
|
||||
|
||||
#
|
||||
# Remove the user's crontab, if there is one
|
||||
# (probably needs to be done before password databases are updated)
|
||||
|
||||
if (-e "$crontab_dir/$login_name") {
|
||||
print STDERR "Removing user's crontab:";
|
||||
system('/usr/bin/crontab', '-u', $login_name, '-r');
|
||||
print STDERR " done.\n";
|
||||
}
|
||||
|
||||
#
|
||||
# Remove the user's at jobs, if any
|
||||
# (probably also needs to be done before password databases are updated)
|
||||
|
||||
&remove_at_jobs($login_name);
|
||||
|
||||
#
|
||||
# Kill all the user's processes
|
||||
|
||||
&kill_users_processes($login_name, $uid);
|
||||
|
||||
#
|
||||
# Copy master password file to new file less removed user's entry
|
||||
|
||||
&update_passwd_file;
|
||||
|
||||
#
|
||||
# Remove the user from all groups in /etc/group
|
||||
|
||||
&update_group_file($login_name);
|
||||
|
||||
#
|
||||
# Remove the user's home directory
|
||||
|
||||
if ($remove_directory) {
|
||||
print STDERR "Removing user's home directory ($home_dir):";
|
||||
&remove_dir($home_dir);
|
||||
print STDERR " done.\n";
|
||||
}
|
||||
|
||||
#
|
||||
# Remove files related to the user from the mail directory
|
||||
|
||||
#&remove_files_from_dir($mail_dir, $login_name, $uid);
|
||||
$file = "$mail_dir/$login_name";
|
||||
if (-e $file || -l $file) {
|
||||
print STDERR "Removing user's incoming mail file ${file}:";
|
||||
unlink $file ||
|
||||
print STDERR "\n${whoami}: Warning: unlink on $file failed ($!) - continuing\n";
|
||||
print STDERR " done.\n";
|
||||
}
|
||||
|
||||
#
|
||||
# Remove some pop daemon's leftover file
|
||||
|
||||
$file = "$mail_dir/.${login_name}.pop";
|
||||
if (-e $file || -l $file) {
|
||||
print STDERR "Removing pop daemon's temporary mail file ${file}:";
|
||||
unlink $file ||
|
||||
print STDERR "\n${whoami}: Warning: unlink on $file failed ($!) - continuing\n";
|
||||
print STDERR " done.\n";
|
||||
}
|
||||
|
||||
#
|
||||
# Remove files belonging to the user from the directories /tmp, /var/tmp,
|
||||
# and /var/tmp/vi.recover. Note that this doesn't take care of the
|
||||
# problem where a user may have directories or symbolic links in those
|
||||
# directories -- only regular files are removed.
|
||||
|
||||
&remove_files_from_dir('/tmp', $login_name, $uid);
|
||||
&remove_files_from_dir('/var/tmp', $login_name, $uid);
|
||||
&remove_files_from_dir('/var/tmp/vi.recover', $login_name, $uid)
|
||||
if (-e '/var/tmp/vi.recover');
|
||||
|
||||
#
|
||||
# All done!
|
||||
|
||||
exit 0;
|
||||
|
||||
sub get_login_name {
|
||||
#
|
||||
# Get new user's name
|
||||
local($done, $login_name);
|
||||
|
||||
for ($done = 0; ! $done; ) {
|
||||
print "Enter login name for user to remove: ";
|
||||
$login_name = <>;
|
||||
chomp $login_name;
|
||||
if (not getpwnam("$login_name")) {
|
||||
print STDERR "Sorry, login name not in password database.\n";
|
||||
} else {
|
||||
$done = 1;
|
||||
}
|
||||
}
|
||||
|
||||
print "User name is ${login_name}\n" if $debug;
|
||||
return($login_name);
|
||||
}
|
||||
|
||||
sub get_yn {
|
||||
#
|
||||
# Get a yes or no answer; return 'Y' or 'N'
|
||||
local($prompt) = @_;
|
||||
local($done, $ans);
|
||||
|
||||
for ($done = 0; ! $done; ) {
|
||||
print $prompt;
|
||||
$ans = <>;
|
||||
chop $ans;
|
||||
$ans =~ tr/a-z/A-Z/;
|
||||
if (!($ans =~ /^[YN]/)) {
|
||||
print STDERR "Please answer (y)es or (n)o.\n";
|
||||
} else {
|
||||
$done = 1;
|
||||
}
|
||||
}
|
||||
|
||||
return(substr($ans, 0, 1));
|
||||
}
|
||||
|
||||
sub update_passwd_file {
|
||||
local($skipped);
|
||||
|
||||
print STDERR "Updating password file,";
|
||||
seek(MASTER_PW, 0, 0);
|
||||
|
||||
sysopen(NEW_PW, $passwd_tmp, O_RDWR|O_CREAT|O_EXCL, 0600) ||
|
||||
die "\n${whoami}: Error: Couldn't open file ${passwd_tmp}:\n $!\n";
|
||||
|
||||
$skipped = 0;
|
||||
while (<MASTER_PW>) {
|
||||
if (/^\Q$login_name:/o) {
|
||||
print STDERR "Dropped entry for $login_name\n" if $debug;
|
||||
$skipped = 1;
|
||||
} else {
|
||||
print NEW_PW;
|
||||
# The other perl password tools assume all lowercase entries.
|
||||
# Add a warning to help unsuspecting admins who might be
|
||||
# using the wrong tool for the job, or might otherwise
|
||||
# be unwittingly holding a loaded foot-shooting device.
|
||||
if (/^\Q$login_name:/io) {
|
||||
my $name = $_;
|
||||
$name =~ s#\:.*\n##;
|
||||
print STDERR "\n\n\tThere is also an entry for $name in your",
|
||||
"password file.\n\tThis can cause problems in some ",
|
||||
"situations.\n\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
close(NEW_PW);
|
||||
seek(MASTER_PW, 0, 0);
|
||||
|
||||
if ($skipped == 0) {
|
||||
print STDERR "\n${whoami}: Whoops! Didn't find ${login_name}'s entry second time around!\n";
|
||||
unlink($passwd_tmp) ||
|
||||
print STDERR "\n${whoami}: Warning: couldn't unlink $passwd_tmp ($!)\n\tPlease investigate, as this file should not be left in the filesystem\n";
|
||||
&unlockpw;
|
||||
exit 1;
|
||||
}
|
||||
|
||||
#
|
||||
# Run pwd_mkdb to install the updated password files and databases
|
||||
|
||||
print STDERR " updating databases,";
|
||||
system('/usr/sbin/pwd_mkdb', '-p', ${passwd_tmp});
|
||||
print STDERR " done.\n";
|
||||
|
||||
close(MASTER_PW); # Not useful anymore
|
||||
}
|
||||
|
||||
sub update_group_file {
|
||||
local($login_name) = @_;
|
||||
|
||||
local($i, $j, $grmember_list, $new_grent, $changes);
|
||||
local($grname, $grpass, $grgid, $grmember_list, @grmembers);
|
||||
|
||||
$changes = 0;
|
||||
print STDERR "Updating group file:";
|
||||
open(GROUP, $group_file) ||
|
||||
die "\n${whoami}: Error: couldn't open ${group_file}: $!\n";
|
||||
if (!flock(GROUP, &LOCK_EX|&LOCK_NB)) {
|
||||
print STDERR "\n${whoami}: Error: couldn't lock ${group_file}: $!\n";
|
||||
exit 1;
|
||||
}
|
||||
local($group_perms, $group_uid, $group_gid) =
|
||||
(stat(GROUP))[2, 4, 5]; # File Mode, uid, gid
|
||||
open(NEW_GROUP, ">$new_group_file") ||
|
||||
die "\n${whoami}: Error: couldn't open ${new_group_file}: $!\n";
|
||||
chmod($group_perms, $new_group_file) ||
|
||||
printf STDERR "\n${whoami}: Warning: could not set permissions of new group file to %o ($!)\n\tContinuing, but please check permissions of $group_file!\n", $group_perms;
|
||||
chown($group_uid, $group_gid, $new_group_file) ||
|
||||
print STDERR "\n${whoami}: Warning: could not set owner/group of new group file to ${group_uid}/${group_gid} ($!)\n\rContinuing, but please check ownership of $group_file!\n";
|
||||
while ($i = <GROUP>) {
|
||||
if (!($i =~ /\Q$login_name\E/)) {
|
||||
# Line doesn't contain any references to the user, so just add it
|
||||
# to the new file
|
||||
print NEW_GROUP $i;
|
||||
} else {
|
||||
#
|
||||
# Remove the user from the group
|
||||
if ($i =~ /\n$/) {
|
||||
chop $i;
|
||||
}
|
||||
($grname, $grpass, $grgid, $grmember_list) = split(/:/, $i);
|
||||
@grmembers = split(/,/, $grmember_list);
|
||||
undef @new_grmembers;
|
||||
local(@new_grmembers);
|
||||
foreach $j (@grmembers) {
|
||||
if ($j ne $login_name) {
|
||||
push(@new_grmembers, $j);
|
||||
} else {
|
||||
print STDERR " $grname";
|
||||
$changes = 1;
|
||||
}
|
||||
}
|
||||
if ($grname eq $login_name && $#new_grmembers == -1) {
|
||||
# Remove a user's personal group if empty
|
||||
print STDERR " (removing group $grname -- personal group is empty)";
|
||||
$changes = 1;
|
||||
} else {
|
||||
$grmember_list = join(',', @new_grmembers);
|
||||
$new_grent = join(':', $grname, $grpass, $grgid, $grmember_list);
|
||||
print NEW_GROUP "$new_grent\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
close(NEW_GROUP);
|
||||
rename($new_group_file, $group_file) || # Replace old group file with new
|
||||
die "\n${whoami}: Error: couldn't rename $new_group_file to $group_file ($!)\n";
|
||||
close(GROUP); # File handle is worthless now
|
||||
print STDERR " (no changes)" if (! $changes);
|
||||
print STDERR " done.\n";
|
||||
}
|
||||
|
||||
sub remove_dir {
|
||||
# Remove the user's home directory
|
||||
local($dir) = @_;
|
||||
local($linkdir);
|
||||
|
||||
if (-l $dir) {
|
||||
$linkdir = &resolvelink($dir);
|
||||
# Remove the symbolic link
|
||||
unlink($dir) ||
|
||||
warn "${whoami}: Warning: could not unlink symlink $dir: $!\n";
|
||||
if (!(-e $linkdir)) {
|
||||
#
|
||||
# Dangling symlink - just return now
|
||||
return;
|
||||
}
|
||||
# Set dir to be the resolved pathname
|
||||
$dir = $linkdir;
|
||||
}
|
||||
if (!(-d $dir)) {
|
||||
print STDERR "${whoami}: Warning: $dir is not a directory\n";
|
||||
unlink($dir) || warn "${whoami}: Warning: could not unlink $dir: $!\n";
|
||||
return;
|
||||
}
|
||||
system('/bin/rm', '-rf', $dir);
|
||||
}
|
||||
|
||||
sub remove_files_from_dir {
|
||||
local($dir, $login_name, $uid) = @_;
|
||||
local($path, $i, $owner);
|
||||
|
||||
print STDERR "Removing files belonging to ${login_name} from ${dir}:";
|
||||
|
||||
if (!opendir(DELDIR, $dir)) {
|
||||
print STDERR "\n${whoami}: Warning: couldn't open directory ${dir} ($!)\n";
|
||||
return;
|
||||
}
|
||||
while ($i = readdir(DELDIR)) {
|
||||
next if $i eq '.';
|
||||
next if $i eq '..';
|
||||
|
||||
$owner = (stat("$dir/$i"))[4]; # UID
|
||||
if ($uid == $owner) {
|
||||
if (-f "$dir/$i") {
|
||||
print STDERR " $i";
|
||||
unlink "$dir/$i" ||
|
||||
print STDERR "\n${whoami}: Warning: unlink on ${dir}/${i} failed ($!) - continuing\n";
|
||||
} else {
|
||||
print STDERR " ($i not a regular file - skipped)";
|
||||
}
|
||||
}
|
||||
}
|
||||
closedir(DELDIR);
|
||||
|
||||
printf STDERR " done.\n";
|
||||
}
|
||||
|
||||
|
||||
sub invoke_atq {
|
||||
local *ATQ;
|
||||
my($user) = (shift || "");
|
||||
my($path_atq) = "/usr/bin/atq";
|
||||
my(@at) = ();
|
||||
my($pid, $line);
|
||||
|
||||
return @at if ($user eq "");
|
||||
|
||||
if (!defined($pid = open(ATQ, "-|"))) {
|
||||
die("creating pipe to atq: $!\n");
|
||||
} elsif ($pid == 0) {
|
||||
exec($path_atq, $user);
|
||||
die("executing $path_atq: $!\n");
|
||||
}
|
||||
|
||||
while(defined($_ = <ATQ>)) {
|
||||
chomp;
|
||||
if (/^\d\d.\d\d.\d\d\s+\d\d.\d\d.\d\d\s+(\S+)\s+\S+\s+(\d+)$/) {
|
||||
push(@at, $2) if ($1 eq $user);
|
||||
}
|
||||
}
|
||||
close ATQ;
|
||||
return @at;
|
||||
}
|
||||
|
||||
sub invoke_atrm {
|
||||
local *ATRM;
|
||||
my($user) = (shift || "");
|
||||
my($path_atrm) = "/usr/bin/atrm";
|
||||
my(@jobs) = @_;
|
||||
my($pid);
|
||||
my($txt) = "";
|
||||
|
||||
return "Invalid arguments" if (($user eq "") || ($#jobs == -1));
|
||||
|
||||
if (!defined($pid = open(ATRM, "-|"))) {
|
||||
die("creating pipe to atrm: $!\n");
|
||||
} elsif ($pid == 0) {
|
||||
exec($path_atrm, $user, @jobs);
|
||||
}
|
||||
|
||||
while(defined($_ = <ATRM>)) {
|
||||
$txt .= $_;
|
||||
}
|
||||
close ATRM;
|
||||
return $txt;
|
||||
}
|
||||
|
||||
sub remove_at_jobs {
|
||||
my($user) = (shift || "");
|
||||
my(@at, $atrm);
|
||||
|
||||
return 1 if ($user eq "");
|
||||
|
||||
@at = invoke_atq($user);
|
||||
return 0 if ($#at == -1);
|
||||
|
||||
print STDERR "Removing user's at jobs:";
|
||||
print STDERR " @at:";
|
||||
$atrm = invoke_atrm($user, @at);
|
||||
if ($atrm ne "") {
|
||||
print STDERR " -- $atrm\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
print STDERR " done.\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
sub resolvelink {
|
||||
local($path) = @_;
|
||||
local($l);
|
||||
|
||||
while (-l $path && -e $path) {
|
||||
if (!defined($l = readlink($path))) {
|
||||
die "${whoami}: readlink on $path failed (but it should have worked!): $!\n";
|
||||
}
|
||||
if ($l =~ /^\//) {
|
||||
# Absolute link
|
||||
$path = $l;
|
||||
} else {
|
||||
# Relative link
|
||||
$path =~ s/\/[^\/]+\/?$/\/$l/; # Replace last component of path
|
||||
}
|
||||
}
|
||||
return $path;
|
||||
}
|
||||
|
||||
sub kill_users_processes {
|
||||
local($login_name, $uid) = @_;
|
||||
local($pid, $result);
|
||||
|
||||
#
|
||||
# Do something a little complex: fork a child that changes its
|
||||
# real and effective UID to that of the removed user, then issues
|
||||
# a "kill(9, -1)" to kill all processes of the same uid as the sender
|
||||
# (see kill(2) for details).
|
||||
# The parent waits for the exit of the child and then returns.
|
||||
|
||||
if ($pid = fork) {
|
||||
# Parent process
|
||||
waitpid($pid, 0);
|
||||
} elsif (defined $pid) {
|
||||
# Child process
|
||||
$< = $uid;
|
||||
$> = $uid;
|
||||
if ($< != $uid || $> != $uid) {
|
||||
print STDERR "${whoami}: Error (kill_users_processes):\n" .
|
||||
"\tCouldn't reset uid/euid to ${uid}: current uid/euid's are $< and $>\n";
|
||||
exit 1;
|
||||
}
|
||||
$result = kill(9, -1);
|
||||
print STDERR "Killed process(es) belonging to $login_name.\n"
|
||||
if $result;
|
||||
exit 0;
|
||||
} else {
|
||||
# Couldn't fork!
|
||||
print STDERR "${whoami}: Error: couldn't fork to kill ${login_name}'s processes - continuing\n";
|
||||
}
|
||||
}
|
325
usr.sbin/adduser/rmuser.sh
Normal file
325
usr.sbin/adduser/rmuser.sh
Normal file
@ -0,0 +1,325 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Copyright (c) 2002 Michael Telahun Makonnen. All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions
|
||||
# are met:
|
||||
# 1. Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# 2. Redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution.
|
||||
# 3. The name of the author may not be used to endorse or promote products
|
||||
# derived from this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#
|
||||
# Email: Mike Makonnen <mtm@identd.net>
|
||||
#
|
||||
# $FreeBSD$
|
||||
#
|
||||
|
||||
ATJOBDIR="/var/at/jobs"
|
||||
CRONJOBDIR="/var/cron/tabs"
|
||||
MAILSPOOL="/var/mail"
|
||||
SIGKILL="-KILL"
|
||||
TEMPDIRS="/tmp /var/tmp /var/tmp/vi.recover"
|
||||
THISCMD=`/usr/bin/basename $0`
|
||||
|
||||
# err msg
|
||||
# Display $msg on stderr.
|
||||
#
|
||||
err() {
|
||||
echo 1>&2 ${THISCMD}: $*
|
||||
}
|
||||
|
||||
# rm_files login
|
||||
# Removes files or empty directories belonging to $login from various
|
||||
# temporary directories.
|
||||
#
|
||||
rm_files() {
|
||||
# The argument is required
|
||||
[ -n $1 ] && login=$1 || return
|
||||
|
||||
for _dir in ${TEMPDIRS} ; do
|
||||
if [ ! -d $_dir ]; then
|
||||
err "$_dir is not a valid directory."
|
||||
continue
|
||||
fi
|
||||
echo -n "Removing files owned by ($login) in $_dir:"
|
||||
filecount=0
|
||||
_ownedfiles=`find 2>/dev/null $_dir -maxdepth 1 -user $login -print`
|
||||
for _file in $_ownedfiles ; do
|
||||
rm -fd $_file
|
||||
filecount=`expr $filecount + 1`
|
||||
done
|
||||
echo " $filecount removed."
|
||||
done
|
||||
}
|
||||
|
||||
# rm_mail login
|
||||
# Removes unix mail and pop daemon files belonging to the user
|
||||
# specified in the $login argument.
|
||||
#
|
||||
rm_mail() {
|
||||
# The argument is required
|
||||
[ -n $1 ] && login=$1 || return
|
||||
|
||||
echo -n "Removing mail spool(s) for ($login):"
|
||||
if [ -f ${MAILSPOOL}/$login ]; then
|
||||
echo -n " ${MAILSPOOL}/$login"
|
||||
rm ${MAILSPOOL}/$login
|
||||
fi
|
||||
if [ -f ${MAILSPOOL}/${login}.pop ]; then
|
||||
echo -n " ${MAILSPOOL}/${login}.pop"
|
||||
rm ${MAILSPOOL}/${login}.pop
|
||||
fi
|
||||
echo '.'
|
||||
}
|
||||
|
||||
# kill_procs login
|
||||
# Send a SIGKILL to all processes owned by $login.
|
||||
#
|
||||
kill_procs() {
|
||||
# The argument is required
|
||||
[ -n $1 ] && login=$1 || return
|
||||
|
||||
echo -n "Terminating all processes owned by ($login):"
|
||||
killcount=0
|
||||
proclist=`ps 2>/dev/null -U $login | grep -v '^\ *PID' | awk '{print $1}'`
|
||||
for _pid in $proclist ; do
|
||||
kill 2>/dev/null ${SIGKILL} $_pid
|
||||
killcount=`expr $killcount + 1`
|
||||
done
|
||||
echo " ${SIGKILL} signal sent to $killcount processes."
|
||||
}
|
||||
|
||||
# rm_at_jobs login
|
||||
# Remove at (1) jobs belonging to $login.
|
||||
#
|
||||
rm_at_jobs() {
|
||||
# The argument is required
|
||||
[ -n $1 ] && login=$1 || return
|
||||
|
||||
atjoblist=`find 2>/dev/null ${ATJOBDIR} -maxdepth 1 -user $login -print`
|
||||
jobcount=0
|
||||
echo -n "Removing at(1) jobs owned by ($login):"
|
||||
for _atjob in $atjoblist ; do
|
||||
rm -f $_atjob
|
||||
jobcount=`expr $jobcount + 1`
|
||||
done
|
||||
echo " $jobcount removed."
|
||||
}
|
||||
|
||||
# rm_crontab login
|
||||
# Removes crontab file belonging to user $login.
|
||||
#
|
||||
rm_crontab() {
|
||||
# The argument is required
|
||||
[ -n $1 ] && login=$1 || return
|
||||
|
||||
echo -n "Removing crontab for ($login):"
|
||||
if [ -f ${CRONJOBDIR}/$login ]; then
|
||||
echo -n " ${CRONJOBDIR}/$login"
|
||||
rm -f ${CRONJOBDIR}/$login
|
||||
fi
|
||||
echo '.'
|
||||
}
|
||||
|
||||
# rm_user login
|
||||
# Remove user $login from the system. This subroutine makes use
|
||||
# of the pw(8) command to remove a user from the system. The pw(8)
|
||||
# command will remove the specified user from the user database
|
||||
# and group file and remove any crontabs. His home
|
||||
# directory will be removed if it is owned by him and contains no
|
||||
# files or subdirectories owned by other users. Mail spool files will
|
||||
# also be removed.
|
||||
#
|
||||
rm_user() {
|
||||
# The argument is required
|
||||
[ -n $1 ] && login=$1 || return
|
||||
|
||||
echo -n "Removing user ($login)"
|
||||
[ -n "$pw_rswitch" ] && echo -n " (including home directory)"
|
||||
echo -n " from the system:"
|
||||
pw userdel -n $login $pw_rswitch
|
||||
echo ' Done.'
|
||||
}
|
||||
|
||||
# prompt_yesno msg
|
||||
# Prompts the user with a $msg. The answer is expected to be
|
||||
# yes, no, or some variation thereof. This subroutine returns 0
|
||||
# if the answer was yes, 1 if it was not.
|
||||
#
|
||||
prompt_yesno() {
|
||||
# The argument is required
|
||||
[ -n "$1" ] && msg=$1 || return
|
||||
|
||||
while : ; do
|
||||
echo -n $msg
|
||||
read _ans
|
||||
case $_ans in
|
||||
[Nn][Oo]|[Nn])
|
||||
return 1
|
||||
;;
|
||||
[Yy][Ee][Ss]|[Yy][Ee]|[Yy])
|
||||
return 0
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
# show_usage
|
||||
# (no arguments)
|
||||
# Display usage message.
|
||||
#
|
||||
show_usage() {
|
||||
echo "usage: ${THISCMD} [-y] [-f file] [user ...]"
|
||||
echo " if the -y switch is used, either the -f switch or"
|
||||
echo " one or more user names must be given"
|
||||
}
|
||||
|
||||
#### END SUBROUTINE DEFENITION ####
|
||||
|
||||
ffile=
|
||||
fflag=
|
||||
procowner=
|
||||
pw_rswitch=
|
||||
userlist=
|
||||
yflag=
|
||||
|
||||
procowner=`/usr/bin/id -u`
|
||||
if [ "$procowner" != "0" ]; then
|
||||
err 'you must be root (0) to use this utility.'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
args=`getopt 2>/dev/null yf: $*`
|
||||
if [ "$?" != "0" ]; then
|
||||
show_usage
|
||||
exit 1
|
||||
fi
|
||||
set -- $args
|
||||
for _switch ; do
|
||||
case $_switch in
|
||||
-y)
|
||||
yflag=1
|
||||
shift
|
||||
;;
|
||||
-f)
|
||||
fflag=1
|
||||
ffile="$2"
|
||||
shift; shift
|
||||
;;
|
||||
--)
|
||||
shift
|
||||
break
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Get user names from a file if the -f switch was used. Otherwise,
|
||||
# get them from the commandline arguments. If we're getting it
|
||||
# from a file, the file must be owned by and writable only by root.
|
||||
#
|
||||
if [ $fflag ]; then
|
||||
_insecure=`find $ffile ! -user 0 -or -perm +0022`
|
||||
if [ -n "$_insecure" ]; then
|
||||
err "file ($ffile) must be owned by and writeable only by root."
|
||||
exit 1
|
||||
fi
|
||||
if [ -r "$ffile" ]; then
|
||||
userlist=`cat $ffile | while read _user _junk ; do
|
||||
case $_user in
|
||||
\#*|'')
|
||||
;;
|
||||
*)
|
||||
echo -n "$userlist $_user"
|
||||
;;
|
||||
esac
|
||||
done`
|
||||
fi
|
||||
else
|
||||
while [ $1 ] ; do
|
||||
userlist="$userlist $1"
|
||||
shift
|
||||
done
|
||||
fi
|
||||
|
||||
# If the -y or -f switch has been used and the list of users to remove
|
||||
# is empty it is a fatal error. Otherwise, prompt the user for a list
|
||||
# of one or more user names.
|
||||
#
|
||||
if [ ! "$userlist" ]; then
|
||||
if [ $fflag ]; then
|
||||
err "($ffile) does not exist or does not contain any user names."
|
||||
exit 1
|
||||
elif [ $yflag ]; then
|
||||
show_usage
|
||||
exit 1
|
||||
else
|
||||
echo -n "Please enter one or more user name's: "
|
||||
read userlist
|
||||
fi
|
||||
fi
|
||||
|
||||
_user=
|
||||
_uid=
|
||||
for _user in $userlist ; do
|
||||
# Make sure the name exists in the passwd database and that it
|
||||
# does not have a uid of 0
|
||||
#
|
||||
userrec=`pw 2>/dev/null usershow -n $_user`
|
||||
if [ "$?" != "0" ]; then
|
||||
err "user ($_user) does not exist in the password database."
|
||||
continue
|
||||
fi
|
||||
_uid=`echo $userrec | awk -F: '{print $3}'`
|
||||
if [ "$_uid" = "0" ]; then
|
||||
err "user ($_user) has uid 0. You may not remove this user."
|
||||
continue
|
||||
fi
|
||||
|
||||
# If the -y switch was not used ask for confirmation to remove the
|
||||
# user and home directory.
|
||||
#
|
||||
if [ -z "$yflag" ]; then
|
||||
echo "Matching password entry:"
|
||||
echo
|
||||
echo $userrec
|
||||
echo
|
||||
if ! prompt_yesno "Is this the entry you wish to remove? " ; then
|
||||
continue
|
||||
fi
|
||||
_homedir=`echo $userrec | awk -F: '{print $9}'`
|
||||
if prompt_yesno "Remove user's home directory ($_homedir)?: "; then
|
||||
pw_rswitch="-r"
|
||||
fi
|
||||
else
|
||||
pw_rswitch="-r"
|
||||
fi
|
||||
|
||||
# Disable any further attempts to log into this account
|
||||
pw 2>/dev/null lock $_user
|
||||
|
||||
# Remove crontab, mail spool, etc. Then obliterate the user from
|
||||
# the passwd and group database.
|
||||
rm_crontab $_user
|
||||
rm_at_jobs $_user
|
||||
kill_procs $_user
|
||||
rm_mail $_user
|
||||
rm_files $_user
|
||||
rm_user $_user
|
||||
done
|
Loading…
Reference in New Issue
Block a user