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$ 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 223ffile= 224fflag= 225procowner= 226pw_rswitch= 227userlist= 228yflag= 229vflag= 230 231procowner=`/usr/bin/id -u` 232if [ "$procowner" != "0" ]; then 233 err 'you must be root (0) to use this utility.' 234 exit 1 235fi 236 237args=`getopt 2>/dev/null yvf: $*` 238if [ "$?" != "0" ]; then 239 show_usage 240 exit 1 241fi 242set -- $args 243for _switch ; do 244 case $_switch in 245 -y) 246 yflag=1 247 shift 248 ;; 249 -v) 250 vflag=1 251 shift 252 ;; 253 -f) 254 fflag=1 255 ffile="$2" 256 shift; shift 257 ;; 258 --) 259 shift 260 break 261 ;; 262 esac 263done 264 265# Get user names from a file if the -f switch was used. Otherwise, 266# get them from the commandline arguments. If we're getting it 267# from a file, the file must be owned by and writable only by root. 268# 269if [ $fflag ]; then 270 _insecure=`find $ffile ! -user 0 -or -perm +0022` 271 if [ -n "$_insecure" ]; then 272 err "file ($ffile) must be owned by and writeable only by root." 273 exit 1 274 fi 275 if [ -r "$ffile" ]; then 276 userlist=`cat $ffile | while read _user _junk ; do 277 case $_user in 278 \#*|'') 279 ;; 280 *) 281 echo -n "$userlist $_user" 282 ;; 283 esac 284 done` 285 fi 286else 287 while [ $1 ] ; do 288 userlist="$userlist $1" 289 shift 290 done 291fi 292 293# If the -y or -f switch has been used and the list of users to remove 294# is empty it is a fatal error. Otherwise, prompt the user for a list 295# of one or more user names. 296# 297if [ ! "$userlist" ]; then 298 if [ $fflag ]; then 299 err "($ffile) does not exist or does not contain any user names." 300 exit 1 301 elif [ $yflag ]; then 302 show_usage 303 exit 1 304 else 305 echo -n "Please enter one or more usernames: " 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=`pw 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