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