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