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