1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
|
#!/bin/sh
#
# Copyright (c) 2002, 2003 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.
#
# 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@FreeBSD.Org>
#
# $FreeBSD$
#
ATJOBDIR="/var/at/jobs"
CRONJOBDIR="/var/cron/tabs"
MAILSPOOL="/var/mail"
SIGKILL="-KILL"
TEMPDIRS="/tmp /var/tmp"
THISCMD=`/usr/bin/basename $0`
PWCMD="${PWCMD:-/usr/sbin/pw}"
# err msg
# Display $msg on stderr.
#
err() {
echo 1>&2 ${THISCMD}: $*
}
# verbose
# Returns 0 if verbose mode is set, 1 if it is not.
#
verbose() {
[ -n "$vflag" ] && return 0 || return 1
}
# 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
totalcount=0
for _dir in ${TEMPDIRS} ; do
filecount=0
if [ ! -d $_dir ]; then
err "$_dir is not a valid directory."
continue
fi
verbose && echo -n "Removing files owned by ($login) in $_dir:"
filecount=`find 2>/dev/null "$_dir" -user "$login" -delete -print |
wc -l | sed 's/ *//'`
verbose && echo " $filecount removed."
totalcount=$(($totalcount + $filecount))
done
! verbose && [ $totalcount -ne 0 ] && echo -n " files($totalcount)"
}
# 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
verbose && echo -n "Removing mail spool(s) for ($login):"
if [ -f ${MAILSPOOL}/$login ]; then
verbose && echo -n " ${MAILSPOOL}/$login" ||
echo -n " mailspool"
rm ${MAILSPOOL}/$login
fi
if [ -f ${MAILSPOOL}/.${login}.pop ]; then
verbose && echo -n " ${MAILSPOOL}/.${login}.pop" ||
echo -n " pop3"
rm ${MAILSPOOL}/.${login}.pop
fi
verbose && echo '.'
}
# kill_procs login
# Send a SIGKILL to all processes owned by $login.
#
kill_procs() {
# The argument is required
[ -n $1 ] && login=$1 || return
verbose && 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=$(($killcount + 1))
done
verbose && echo " ${SIGKILL} signal sent to $killcount processes."
! verbose && [ $killcount -ne 0 ] && echo -n " processes(${killcount})"
}
# 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
verbose && echo -n "Removing at(1) jobs owned by ($login):"
for _atjob in $atjoblist ; do
rm -f $_atjob
jobcount=$(($jobcount + 1))
done
verbose && echo " $jobcount removed."
! verbose && [ $jobcount -ne 0 ] && echo -n " at($jobcount)"
}
# rm_crontab login
# Removes crontab file belonging to user $login.
#
rm_crontab() {
# The argument is required
[ -n $1 ] && login=$1 || return
verbose && echo -n "Removing crontab for ($login):"
if [ -f ${CRONJOBDIR}/$login ]; then
verbose && echo -n " ${CRONJOBDIR}/$login" || echo -n " crontab"
rm -f ${CRONJOBDIR}/$login
fi
verbose && echo '.'
}
# rm_ipc login
# Remove all IPC mechanisms which are owned by $login.
#
rm_ipc() {
verbose && echo -n "Removing IPC mechanisms"
for i in s m q; do
ipcs -$i |
awk -v i=$i -v login=$1 '$1 == i && $5 == login { print $2 }' |
xargs -n 1 ipcrm -$i
done
verbose && 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
verbose && echo -n "Removing user ($login)"
[ -n "$pw_rswitch" ] && {
verbose && echo -n " (including home directory)"
! verbose && echo -n " home"
}
! verbose && echo -n " passwd"
verbose && echo -n " from the system:"
${PWCMD} userdel -n $login $pw_rswitch
verbose && 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} [-yv] [-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=
vflag=
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 yvf: $*`
if [ "$?" != "0" ]; then
show_usage
exit 1
fi
set -- $args
for _switch ; do
case $_switch in
-y)
yflag=1
shift
;;
-v)
vflag=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 usernames: "
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
${PWCMD} 2>/dev/null lock $_user
# Remove crontab, mail spool, etc. Then obliterate the user from
# the passwd and group database.
#
! verbose && echo -n "Removing user ($_user):"
rm_crontab $_user
rm_at_jobs $_user
rm_ipc $_user
kill_procs $_user
rm_files $_user
rm_mail $_user
rm_user $_user
! verbose && echo "."
done
|