rmuser.sh revision 111013
1#!/bin/sh
2#
3# Copyright (c) 2002 Michael Telahun Makonnen. All rights reserved.
4#
5# Redistribution and use in source and binary forms, with or without
6# modification, are permitted provided that the following conditions
7# are met:
8# 1. Redistributions of source code must retain the above copyright
9#    notice, this list of conditions and the following disclaimer.
10# 2. Redistributions in binary form must reproduce the above copyright
11#    notice, this list of conditions and the following disclaimer in the
12#    documentation and/or other materials provided with the distribution.
13# 3. The name of the author may not be used to endorse or promote products
14#    derived from this software without specific prior written permission.
15#
16# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26#
27#	Email: Mike Makonnen <mtm@identd.net>
28#
29# $FreeBSD: head/usr.sbin/adduser/rmuser.sh 111013 2003-02-16 18:04:06Z mtm $
30#
31
32ATJOBDIR="/var/at/jobs"
33CRONJOBDIR="/var/cron/tabs"
34MAILSPOOL="/var/mail"
35SIGKILL="-KILL"
36TEMPDIRS="/tmp /var/tmp"
37THISCMD=`/usr/bin/basename $0`
38
39# err msg
40#	Display $msg on stderr.
41#
42err() {
43	echo 1>&2 ${THISCMD}: $*
44}
45
46# verbose
47#	Returns 0 if verbose mode is set, 1 if it is not.
48#
49verbose() {
50	[ -n "$vflag" ] && return 0 || return 1
51}
52
53# rm_files login
54#	Removes files or empty directories belonging to $login from various
55#	temporary directories.
56#
57rm_files() {
58	# The argument is required
59	[ -n $1 ] && login=$1 || return
60	
61	totalcount=0
62	for _dir in ${TEMPDIRS} ; do
63		filecount=0
64		if [ ! -d $_dir ]; then
65			err "$_dir is not a valid directory."
66			continue
67		fi
68		verbose && echo -n "Removing files owned by ($login) in $_dir:"
69		filecount=`find 2>/dev/null "$_dir" -user "$login" -delete -print | \
70		    wc -l | sed 's/ *//'`
71		verbose && echo " $filecount removed."
72		totalcount=$(($totalcount + $filecount))
73	done
74	! verbose && [ $totalcount -ne 0 ] && echo -n " files($totalcount)"
75}
76
77# rm_mail login
78#	Removes unix mail and pop daemon files belonging to the user
79#	specified in the $login argument.
80#
81rm_mail() {
82	# The argument is required
83	[ -n $1 ] && login=$1 || return
84
85	verbose && echo -n "Removing mail spool(s) for ($login):"
86	if [ -f ${MAILSPOOL}/$login ]; then
87		verbose && echo -n " ${MAILSPOOL}/$login" || \
88		    echo -n " mailspool"
89		rm ${MAILSPOOL}/$login
90	fi
91	if [ -f ${MAILSPOOL}/${login}.pop ]; then
92		verbose && echo -n " ${MAILSPOOL}/${login}.pop" || \
93		    echo -n " pop3"
94		rm ${MAILSPOOL}/${login}.pop
95	fi
96	verbose && echo '.'
97}
98
99# kill_procs login
100#	Send a SIGKILL to all processes owned by $login.
101#
102kill_procs() {
103	# The argument is required
104	[ -n $1 ] && login=$1 || return
105
106	verbose && echo -n "Terminating all processes owned by ($login):"
107	killcount=0
108	proclist=`ps 2>/dev/null -U $login | grep -v '^\ *PID' | awk '{print $1}'`
109	for _pid in $proclist ; do
110		kill 2>/dev/null ${SIGKILL} $_pid
111		killcount=$(($killcount + 1))
112	done
113	verbose && echo " ${SIGKILL} signal sent to $killcount processes."
114	! verbose && [ $killcount -ne 0 ] && echo -n " processes(${killcount})"
115}
116
117# rm_at_jobs login
118#	Remove at (1) jobs belonging to $login.
119#
120rm_at_jobs() {
121	# The argument is required
122	[ -n $1 ] && login=$1 || return
123
124	atjoblist=`find 2>/dev/null ${ATJOBDIR} -maxdepth 1 -user $login -print`
125	jobcount=0
126	verbose && echo -n "Removing at(1) jobs owned by ($login):"
127	for _atjob in $atjoblist ; do
128		rm -f $_atjob
129		jobcount=$(($jobcount + 1))
130	done
131	verbose && echo " $jobcount removed."
132	! verbose && [ $jobcount -ne 0 ] && echo -n " at($jobcount)"
133}
134
135# rm_crontab login
136#	Removes crontab file belonging to user $login.
137#
138rm_crontab() {
139	# The argument is required
140	[ -n $1 ] && login=$1 || return
141
142	verbose && echo -n "Removing crontab for ($login):"
143	if [ -f ${CRONJOBDIR}/$login ]; then
144		verbose && echo -n " ${CRONJOBDIR}/$login" || echo -n " crontab"
145		rm -f ${CRONJOBDIR}/$login
146	fi
147	verbose && echo '.'
148}
149
150# rm_user login
151#	Remove user $login from the system. This subroutine makes use
152#	of the pw(8) command to remove a user from the system. The pw(8)
153#	command will remove the specified user from the user database
154#	and group file and remove any crontabs. His home
155#	directory will be removed if it is owned by him and contains no 
156#	files or subdirectories owned by other users. Mail spool files will
157#	also be removed.
158#
159rm_user() {
160	# The argument is required
161	[ -n $1 ] && login=$1 || return
162
163	verbose && echo -n "Removing user ($login)"
164	[ -n "$pw_rswitch" ] && {
165		verbose && echo -n " (including home directory)"
166		! verbose && echo -n " home"
167	}
168	! verbose && echo -n " passwd"
169	verbose && echo -n " from the system:"
170	pw userdel -n $login $pw_rswitch
171	verbose && echo ' Done.'
172}
173
174# prompt_yesno msg
175#	Prompts the user with a $msg. The answer is expected to be
176#	yes, no, or some variation thereof. This subroutine returns 0
177#	if the answer was yes, 1 if it was not.
178#
179prompt_yesno() {
180	# The argument is required
181	[ -n "$1" ] && msg="$1" || return
182
183        while : ; do
184                echo -n "$msg"
185                read _ans
186                case $_ans in
187                [Nn][Oo]|[Nn])
188			return 1
189                        ;;
190                [Yy][Ee][Ss]|[Yy][Ee]|[Yy])
191                        return 0
192                        ;;
193                *)
194                        ;;
195                esac
196	done
197}
198
199# show_usage
200#	(no arguments)
201#	Display usage message.
202#
203show_usage() {
204	echo "usage: ${THISCMD} [-yv] [-f file] [user ...]"
205	echo "       if the -y switch is used, either the -f switch or"
206	echo "       one or more user names must be given"
207}
208
209#### END SUBROUTINE DEFENITION ####
210
211ffile=
212fflag=
213procowner=
214pw_rswitch=
215userlist=
216yflag=
217vflag=
218
219procowner=`/usr/bin/id -u`
220if [ "$procowner" != "0" ]; then
221	err 'you must be root (0) to use this utility.'
222	exit 1
223fi
224
225args=`getopt 2>/dev/null yvf: $*`
226if [ "$?" != "0" ]; then
227	show_usage
228	exit 1
229fi
230set -- $args
231for _switch ; do
232	case $_switch in
233	-y)
234		yflag=1
235		shift
236		;;
237	-v)
238		vflag=1
239		shift
240		;;
241	-f)
242		fflag=1
243		ffile="$2"
244		shift; shift
245		;;
246	--)
247		shift
248		break
249		;;
250	esac
251done
252
253# Get user names from a file if the -f switch was used. Otherwise,
254# get them from the commandline arguments. If we're getting it
255# from a file, the file must be owned by and writable only by root.
256#
257if [ $fflag ]; then
258	_insecure=`find $ffile ! -user 0 -or -perm +0022`
259	if [ -n "$_insecure" ]; then
260		err "file ($ffile) must be owned by and writeable only by root."
261		exit 1
262	fi
263	if [ -r "$ffile" ]; then
264		userlist=`cat $ffile | while read _user _junk ; do
265			case $_user in
266			\#*|'')
267				;;
268			*)
269				echo -n "$userlist $_user"
270				;;
271			esac
272		done`
273	fi
274else
275	while [ $1 ] ; do
276		userlist="$userlist $1"
277		shift
278	done
279fi
280
281# If the -y or -f switch has been used and the list of users to remove
282# is empty it is a fatal error. Otherwise, prompt the user for a list
283# of one or more user names.
284#
285if [ ! "$userlist" ]; then
286	if [ $fflag ]; then
287		err "($ffile) does not exist or does not contain any user names."
288		exit 1
289	elif [ $yflag ]; then
290		show_usage
291		exit 1
292	else
293		echo -n "Please enter one or more user name's: "
294		read userlist
295	fi
296fi
297
298_user=
299_uid=
300for _user in $userlist ; do
301	# Make sure the name exists in the passwd database and that it
302	# does not have a uid of 0
303	#
304	userrec=`pw 2>/dev/null usershow -n $_user`
305	if [ "$?" != "0" ]; then
306		err "user ($_user) does not exist in the password database."
307		continue
308	fi
309	_uid=`echo $userrec | awk -F: '{print $3}'`
310	if [ "$_uid" = "0" ]; then
311		err "user ($_user) has uid 0. You may not remove this user."
312		continue
313	fi
314
315	# If the -y switch was not used ask for confirmation to remove the
316	# user and home directory.
317	#
318	if [ -z "$yflag" ]; then
319		echo "Matching password entry:"
320		echo
321		echo $userrec
322		echo
323		if ! prompt_yesno "Is this the entry you wish to remove? " ; then
324			continue
325		fi
326		_homedir=`echo $userrec | awk -F: '{print $9}'`
327		if prompt_yesno "Remove user's home directory ($_homedir)? "; then
328			pw_rswitch="-r"
329		fi
330	else
331		pw_rswitch="-r"
332	fi
333
334	# Disable any further attempts to log into this account
335	pw 2>/dev/null lock $_user
336
337	# Remove crontab, mail spool, etc. Then obliterate the user from
338	# the passwd and group database.
339	#
340	! verbose && echo -n "Removing user ($_user):"
341	rm_crontab $_user
342	rm_at_jobs $_user
343	kill_procs $_user
344	rm_files $_user
345	rm_mail $_user
346	rm_user $_user
347	! verbose && echo "."
348done
349