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