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