#!/bin/sh
#
# SPDX-License-Identifier: BSD-2-Clause
#
# Copyright (c) 2002, 2003 Michael Telahun Makonnen. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
#	Email: Mike Makonnen <mtm@FreeBSD.Org>
#
#

ATJOBDIR="/var/at/jobs"
CRONJOBDIR="/var/cron/tabs"
MAILSPOOL="/var/mail"
SIGKILL="-KILL"
TEMPDIRS="/tmp /var/tmp"
THISCMD=`/usr/bin/basename $0`
PWCMD="${PWCMD:-/usr/sbin/pw}"

# err msg
#	Display $msg on stderr.
#
err() {
	echo 1>&2 ${THISCMD}: $*
}

# verbose
#	Returns 0 if verbose mode is set, 1 if it is not.
#
verbose() {
	[ -n "$vflag" ] && return 0 || return 1
}

# rm_files login
#	Removes files or empty directories belonging to $login from various
#	temporary directories.
#
rm_files() {
	# The argument is required
	[ -n $1 ] && login=$1 || return

	totalcount=0
	for _dir in ${TEMPDIRS} ; do
		filecount=0
		if [ ! -d $_dir ]; then
			err "$_dir is not a valid directory."
			continue
		fi
		verbose && echo -n "Removing files owned by ($login) in $_dir:"
		filecount=`find 2>/dev/null "$_dir" -user "$login" -delete -print |
		    wc -l | sed 's/ *//'`
		verbose && echo " $filecount removed."
		totalcount=$(($totalcount + $filecount))
	done
	! verbose && [ $totalcount -ne 0 ] && echo -n " files($totalcount)"
}

# rm_mail login
#	Removes unix mail and pop daemon files belonging to the user
#	specified in the $login argument.
#
rm_mail() {
	# The argument is required
	[ -n $1 ] && login=$1 || return

	verbose && echo -n "Removing mail spool(s) for ($login):"
	if [ -f ${MAILSPOOL}/$login ]; then
		verbose && echo -n " ${MAILSPOOL}/$login" ||
		    echo -n " mailspool"
		rm ${MAILSPOOL}/$login
	fi
	if [ -f ${MAILSPOOL}/.${login}.pop ]; then
		verbose && echo -n " ${MAILSPOOL}/.${login}.pop" ||
		    echo -n " pop3"
		rm ${MAILSPOOL}/.${login}.pop
	fi
	verbose && echo '.'
}

# kill_procs login
#	Send a SIGKILL to all processes owned by $login.
#
kill_procs() {
	# The argument is required
	[ -n $1 ] && login=$1 || return

	verbose && echo -n "Terminating all processes owned by ($login):"
	killcount=0
	proclist=`ps 2>/dev/null -U $login | grep -v '^\ *PID' | awk '{print $1}'`
	for _pid in $proclist ; do
		kill 2>/dev/null ${SIGKILL} $_pid
		killcount=$(($killcount + 1))
	done
	verbose && echo " ${SIGKILL} signal sent to $killcount processes."
	! verbose && [ $killcount -ne 0 ] && echo -n " processes(${killcount})"
}

# rm_at_jobs login
#	Remove at (1) jobs belonging to $login.
#
rm_at_jobs() {
	# The argument is required
	[ -n $1 ] && login=$1 || return

	atjoblist=`find 2>/dev/null ${ATJOBDIR} -maxdepth 1 -user $login -print`
	jobcount=0
	verbose && echo -n "Removing at(1) jobs owned by ($login):"
	for _atjob in $atjoblist ; do
		rm -f $_atjob
		jobcount=$(($jobcount + 1))
	done
	verbose && echo " $jobcount removed."
	! verbose && [ $jobcount -ne 0 ] && echo -n " at($jobcount)"
}

# rm_crontab login
#	Removes crontab file belonging to user $login.
#
rm_crontab() {
	# The argument is required
	[ -n $1 ] && login=$1 || return

	verbose && echo -n "Removing crontab for ($login):"
	if [ -f ${CRONJOBDIR}/$login ]; then
		verbose && echo -n " ${CRONJOBDIR}/$login" || echo -n " crontab"
		rm -f ${CRONJOBDIR}/$login
	fi
	verbose && echo '.'
}

# rm_ipc login
#	Remove all IPC mechanisms which are owned by $login.
#
rm_ipc() {
	verbose && echo -n "Removing IPC mechanisms"
	for i in s m q; do
		ipcs -$i |
		awk -v i=$i -v login=$1 '$1 == i && $5 == login { print $2 }' |
		xargs -n 1 ipcrm -$i
	done
	verbose && echo '.'
}

# rm_user login
#	Remove user $login from the system. This subroutine makes use
#	of the pw(8) command to remove a user from the system. The pw(8)
#	command will remove the specified user from the user database
#	and group file and remove any crontabs. His home
#	directory will be removed if it is owned by him and contains no 
#	files or subdirectories owned by other users. Mail spool files will
#	also be removed.
#
rm_user() {
	# The argument is required
	[ -n $1 ] && login=$1 || return

	verbose && echo -n "Removing user ($login)"
	[ -n "$pw_rswitch" ] && {
		verbose && echo -n " (including home directory)"
		! verbose && echo -n " home"
	}
	! verbose && echo -n " passwd"
	verbose && echo -n " from the system:"
	${PWCMD} userdel -n $login $pw_rswitch
	verbose && echo ' Done.'
}

# prompt_yesno msg
#	Prompts the user with a $msg. The answer is expected to be
#	yes, no, or some variation thereof. This subroutine returns 0
#	if the answer was yes, 1 if it was not.
#
prompt_yesno() {
	# The argument is required
	[ -n "$1" ] && msg="$1" || return

        while : ; do
                echo -n "$msg"
                read _ans
                case $_ans in
                [Nn][Oo]|[Nn])
			return 1
                        ;;
                [Yy][Ee][Ss]|[Yy][Ee]|[Yy])
                        return 0
                        ;;
                *)
                        ;;
                esac
	done
}

# show_usage
#	(no arguments)
#	Display usage message.
#
show_usage() {
	echo "usage: ${THISCMD} [-yv] [-f file] [user ...]"
	echo "       if the -y switch is used, either the -f switch or"
	echo "       one or more user names must be given"
}

#### END SUBROUTINE DEFENITION ####

ffile=
fflag=
procowner=
pw_rswitch=
userlist=
yflag=
vflag=

procowner=`/usr/bin/id -u`
if [ "$procowner" != "0" ]; then
	err 'you must be root (0) to use this utility.'
	exit 1
fi

args=`getopt 2>/dev/null yvf: $*`
if [ "$?" != "0" ]; then
	show_usage
	exit 1
fi
set -- $args
for _switch ; do
	case $_switch in
	-y)
		yflag=1
		shift
		;;
	-v)
		vflag=1
		shift
		;;
	-f)
		fflag=1
		ffile="$2"
		shift; shift
		;;
	--)
		shift
		break
		;;
	esac
done

# Get user names from a file if the -f switch was used. Otherwise,
# get them from the commandline arguments. If we're getting it
# from a file, the file must be owned by and writable only by root.
#
if [ $fflag ]; then
	_insecure=`find $ffile ! -user 0 -or -perm +0022`
	if [ -n "$_insecure" ]; then
		err "file ($ffile) must be owned by and writeable only by root."
		exit 1
	fi
	if [ -r "$ffile" ]; then
		userlist=`cat $ffile | while read _user _junk ; do
			case $_user in
			\#*|'')
				;;
			*)
				echo -n "$userlist $_user"
				;;
			esac
		done`
	fi
else
	while [ $1 ] ; do
		userlist="$userlist $1"
		shift
	done
fi

# If the -y or -f switch has been used and the list of users to remove
# is empty it is a fatal error. Otherwise, prompt the user for a list
# of one or more user names.
#
if [ ! "$userlist" ]; then
	if [ $fflag ]; then
		err "($ffile) does not exist or does not contain any user names."
		exit 1
	elif [ $yflag ]; then
		show_usage
		exit 1
	else
		echo -n "Please enter one or more usernames, or press enter to exit: "
		read userlist
	fi
fi

_user=
_uid=
for _user in $userlist ; do
	# Make sure the name exists in the passwd database and that it
	# does not have a uid of 0
	#
	userrec=`pw 2>/dev/null usershow -n $_user`
	if [ "$?" != "0" ]; then
		err "user ($_user) does not exist in the password database."
		continue
	fi
	_uid=`echo $userrec | awk -F: '{print $3}'`
	if [ "$_uid" = "0" ]; then
		err "user ($_user) has uid 0. You may not remove this user."
		continue
	fi

	# If the -y switch was not used ask for confirmation to remove the
	# user and home directory.
	#
	if [ -z "$yflag" ]; then
		echo "Matching password entry:"
		echo
		echo $userrec
		echo
		if ! prompt_yesno "Is this the entry you wish to remove? (yes/no): " ; then
			continue
		fi
		_homedir=`echo $userrec | awk -F: '{print $9}'`
		if prompt_yesno "Remove user's home directory? [$_homedir] (yes/no): "; then
			pw_rswitch="-r"
		fi
	else
		pw_rswitch="-r"
	fi

	# Disable any further attempts to log into this account
	${PWCMD} 2>/dev/null lock $_user

	# Remove crontab, mail spool, etc. Then obliterate the user from
	# the passwd and group database.
	#
	! verbose && echo -n "Removing user ($_user):"
	rm_crontab $_user
	rm_at_jobs $_user
	rm_ipc $_user
	kill_procs $_user
	rm_files $_user
	rm_mail $_user
	rm_user $_user
	! verbose && echo "."
done
