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:
Scott Long 2002-12-03 05:41:09 +00:00
parent 6bfd0bdc80
commit 7cdfce092a
7 changed files with 1461 additions and 2294 deletions

View File

@ -1,6 +1,6 @@
# $FreeBSD$
SCRIPTS=adduser.perl rmuser.perl
SCRIPTS=adduser.sh rmuser.sh
MAN= adduser.8 rmuser.8
.include <bsd.prog.mk>

View File

@ -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
View 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

View File

@ -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

View File

@ -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
View 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