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