xref: /freebsd/usr.sbin/adduser/rmuser.sh (revision d0b2dbfa)
1#!/bin/sh
2#
3# SPDX-License-Identifier: BSD-2-Clause
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#
30
31ATJOBDIR="/var/at/jobs"
32CRONJOBDIR="/var/cron/tabs"
33MAILSPOOL="/var/mail"
34SIGKILL="-KILL"
35TEMPDIRS="/tmp /var/tmp"
36THISCMD=`/usr/bin/basename $0`
37PWCMD="${PWCMD:-/usr/sbin/pw}"
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_ipc login
151#	Remove all IPC mechanisms which are owned by $login.
152#
153rm_ipc() {
154	verbose && echo -n "Removing IPC mechanisms"
155	for i in s m q; do
156		ipcs -$i |
157		awk -v i=$i -v login=$1 '$1 == i && $5 == login { print $2 }' |
158		xargs -n 1 ipcrm -$i
159	done
160	verbose && echo '.'
161}
162
163# rm_user login
164#	Remove user $login from the system. This subroutine makes use
165#	of the pw(8) command to remove a user from the system. The pw(8)
166#	command will remove the specified user from the user database
167#	and group file and remove any crontabs. His home
168#	directory will be removed if it is owned by him and contains no
169#	files or subdirectories owned by other users. Mail spool files will
170#	also be removed.
171#
172rm_user() {
173	# The argument is required
174	[ -n $1 ] && login=$1 || return
175
176	verbose && echo -n "Removing user ($login)"
177	[ -n "$pw_rswitch" ] && {
178		verbose && echo -n " (including home directory)"
179		! verbose && echo -n " home"
180	}
181	! verbose && echo -n " passwd"
182	verbose && echo -n " from the system:"
183	${PWCMD} userdel -n $login $pw_rswitch
184	verbose && echo ' Done.'
185}
186
187# prompt_yesno msg
188#	Prompts the user with a $msg. The answer is expected to be
189#	yes, no, or some variation thereof. This subroutine returns 0
190#	if the answer was yes, 1 if it was not.
191#
192prompt_yesno() {
193	# The argument is required
194	[ -n "$1" ] && msg="$1" || return
195
196        while : ; do
197                echo -n "$msg"
198                read _ans
199                case $_ans in
200                [Nn][Oo]|[Nn])
201			return 1
202                        ;;
203                [Yy][Ee][Ss]|[Yy][Ee]|[Yy])
204                        return 0
205                        ;;
206                *)
207                        ;;
208                esac
209	done
210}
211
212# show_usage
213#	(no arguments)
214#	Display usage message.
215#
216show_usage() {
217	echo "usage: ${THISCMD} [-yv] [-f file] [user ...]"
218	echo "       if the -y switch is used, either the -f switch or"
219	echo "       one or more user names must be given"
220}
221
222#### END SUBROUTINE DEFENITION ####
223
224ffile=
225fflag=
226procowner=
227pw_rswitch=
228userlist=
229yflag=
230vflag=
231
232procowner=`/usr/bin/id -u`
233if [ "$procowner" != "0" ]; then
234	err 'you must be root (0) to use this utility.'
235	exit 1
236fi
237
238args=`getopt 2>/dev/null yvf: $*`
239if [ "$?" != "0" ]; then
240	show_usage
241	exit 1
242fi
243set -- $args
244for _switch ; do
245	case $_switch in
246	-y)
247		yflag=1
248		shift
249		;;
250	-v)
251		vflag=1
252		shift
253		;;
254	-f)
255		fflag=1
256		ffile="$2"
257		shift; shift
258		;;
259	--)
260		shift
261		break
262		;;
263	esac
264done
265
266# Get user names from a file if the -f switch was used. Otherwise,
267# get them from the commandline arguments. If we're getting it
268# from a file, the file must be owned by and writable only by root.
269#
270if [ $fflag ]; then
271	_insecure=`find $ffile ! -user 0 -or -perm +0022`
272	if [ -n "$_insecure" ]; then
273		err "file ($ffile) must be owned by and writeable only by root."
274		exit 1
275	fi
276	if [ -r "$ffile" ]; then
277		userlist=`cat $ffile | while read _user _junk ; do
278			case $_user in
279			\#*|'')
280				;;
281			*)
282				echo -n "$userlist $_user"
283				;;
284			esac
285		done`
286	fi
287else
288	while [ $1 ] ; do
289		userlist="$userlist $1"
290		shift
291	done
292fi
293
294# If the -y or -f switch has been used and the list of users to remove
295# is empty it is a fatal error. Otherwise, prompt the user for a list
296# of one or more user names.
297#
298if [ ! "$userlist" ]; then
299	if [ $fflag ]; then
300		err "($ffile) does not exist or does not contain any user names."
301		exit 1
302	elif [ $yflag ]; then
303		show_usage
304		exit 1
305	else
306		echo -n "Please enter one or more usernames: "
307		read userlist
308	fi
309fi
310
311_user=
312_uid=
313for _user in $userlist ; do
314	# Make sure the name exists in the passwd database and that it
315	# does not have a uid of 0
316	#
317	userrec=`pw 2>/dev/null usershow -n $_user`
318	if [ "$?" != "0" ]; then
319		err "user ($_user) does not exist in the password database."
320		continue
321	fi
322	_uid=`echo $userrec | awk -F: '{print $3}'`
323	if [ "$_uid" = "0" ]; then
324		err "user ($_user) has uid 0. You may not remove this user."
325		continue
326	fi
327
328	# If the -y switch was not used ask for confirmation to remove the
329	# user and home directory.
330	#
331	if [ -z "$yflag" ]; then
332		echo "Matching password entry:"
333		echo
334		echo $userrec
335		echo
336		if ! prompt_yesno "Is this the entry you wish to remove? " ; then
337			continue
338		fi
339		_homedir=`echo $userrec | awk -F: '{print $9}'`
340		if prompt_yesno "Remove user's home directory ($_homedir)? "; then
341			pw_rswitch="-r"
342		fi
343	else
344		pw_rswitch="-r"
345	fi
346
347	# Disable any further attempts to log into this account
348	${PWCMD} 2>/dev/null lock $_user
349
350	# Remove crontab, mail spool, etc. Then obliterate the user from
351	# the passwd and group database.
352	#
353	! verbose && echo -n "Removing user ($_user):"
354	rm_crontab $_user
355	rm_at_jobs $_user
356	rm_ipc $_user
357	kill_procs $_user
358	rm_files $_user
359	rm_mail $_user
360	rm_user $_user
361	! verbose && echo "."
362done
363