1
0
mirror of https://git.FreeBSD.org/src.git synced 2024-12-15 10:17:20 +00:00

Cleaned up some messages, added a check to remove a leftover popd file

from /var/mail, added a routine to delete the removed user's files
from /tmp, /var/tmp, & /var/tmp/vi.recover, and added code to kill any
running processes owned by the removed user).  I've also added a flag
for non-interactive execution, cleaned up the man page, and adjusted
my address.

Submitted by: ghelmer@cs.iastate.edu (Guy Helmer)
This commit is contained in:
Wolfram Schneider 1997-03-08 18:04:45 +00:00
parent 53a8ba6224
commit 1e85e4dbb0
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=23534
2 changed files with 227 additions and 50 deletions

View File

@ -1,5 +1,5 @@
.\" Copyright 1995, 1996
.\" Guy Helmer, Madison, South Dakota 57042. All rights reserved.
.\" Copyright 1995, 1996, 1997
.\" Guy Helmer, Ames, Iowa 50014. All rights reserved.
.\"
.\" Redistribution and use in source and binary forms, with or without
.\" modification, are permitted provided that the following conditions
@ -26,50 +26,92 @@
.\"
.\" $Id$
.\"
.Dd July 16, 1996
.Dd February 23, 1997
.Dt RMUSER 8
.Os
.Sh NAME
.Nm rmuser
.Nd remove users from the system
.Nd removes users from the system
.Sh SYNOPSIS
.Nm rmuser
.Op Fl y
.Op Ar username
.Sh DESCRIPTION
The
The utility
.Nm rmuser
utility removes a user's
.Pp
.Bl -enum
.It
Removes the user's
.Xr crontab 1
entry (if any) and any
entry (if any)
.It
Removes any
.Xr at 1
jobs belonging to the user,
then removes a user from the system's local password file, removes
the user's home directory if it is owned by the user, and removes
the user's incoming mail file if it exists. The username is removed
from any groups to which it belongs in the file
jobs belonging to the user
.It
Sends a SIGKILL signal to all processes owned by the user
.It
Removes the user from the system's local password file
.It
Removes the user's home directory (if it is owned by the user),
including handling of symbolic links in the path to the actual home
directory
.It
Removes the incoming mail and pop daemon mail files belonging to the
user from
.Pa /var/mail
.It
Removes all files owned by the user from
.Pa /tmp ,
.Pa /var/tmp ,
and
.Pa /var/tmp/vi.recover .
.It
Removes the username from all groups to which it belongs in
.Pa /etc/group .
If a group becomes empty and the group name is the same as the username,
the group is removed (this complements
(If a group becomes empty and the group name is the same as the username,
the group is removed; this complements
.Xr adduser 8 's
per-user unique groups).
.El
.Pp
.Nm rmuser
politely refuses to remove users whose uid is 0 (typically root), since
it seemed like a good idea at the time
.Nm rmuser
was written.
certain actions (namely, killing all the user's processes, and perhaps
removing the user's home directory) would cause damage to a running system.
If it is necessary to remove a user whose uid is 0, see
.Xr vipw 8
for information on directly editing the password file, by which the desired
user's
.Xr passwd 5
entry may be removed manually.
.Pp
If not running "affirmitively" (i.e., option
.Fl y
is not specified),
.Nm rmuser
shows the selected user's password file entry and asks for confirmation
that you wish to remove the user. If the user's home directory is owned
by the user (and not by any other user),
by the user,
.Nm rmuser
asks whether you wish to remove the user's home directory and everything
below.
.Pp
As
.Nm rmuser
operates, it informs the user regarding the current activity. If any
errors occur, they are posted to standard error and, if it is possible for
.Nm rmuser
to continue, it will.
.Pp
Available options:
.Pp
.Bl -tag -width username
.It Fl y
Affirm - any question that would be asked is answered implicitly in
the affirmative (i.e., yes). A username must also be specified on the
command line if this option is used.
.It Ar \&username
Identifies the user to be removed; if not present,
.Nm rmuser
@ -98,7 +140,30 @@ interactively asks for the user to be removed.
.Sh HISTORY
The
.Nm
command appeared in
.Fx 2.1.5 .
command appeared in
.Fx 2.2 .
.\" .Sh AUTHOR
.\" Guy Helmer, Madison, South Dakota
.\" Guy Helmer, Ames, Iowa
.Sh BUGS
.Nm rmuser
does not comprehensively search the filesystem for all files
owned by the removed user and remove them; to do so on a system
of any size is prohibitively slow and I/O intensive.
.Nm rmuser
also is unable to remove symbolic links that were created by the
user in
.Pa /tmp
or
.Pa /var/tmp
as symbolic links on 4.4BSD filesystems do not contain information
as to who created them. Also, there may be other files created in
.Pa /var/mail
other than
.Pa /var/mail/username
and
.Pa /var/mail/.pop.username
that are not owned by the removed user but should be removed.
.Pp
.Nm rmuser
has no knowledge of NIS (Yellow Pages), and it operates only on the
local password file.

View File

@ -1,6 +1,6 @@
#!/usr/bin/perl
# -*- perl -*-
# Copyright 1995, 1996 Guy Helmer, Madison, South Dakota 57042.
# Copyright 1995, 1996, 1997 Guy Helmer, Ames, Iowa 50014.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
@ -28,7 +28,7 @@
#
# rmuser - Perl script to remove users
#
# Guy Helmer <ghelmer@alpha.dsu.edu>, 07/17/96
# Guy Helmer <ghelmer@cs.iastate.edu>, 02/23/97
#
# $Id$
@ -48,6 +48,7 @@ $new_group_file = "${group_file}.new.$$";
$mail_dir = "/var/mail";
$crontab_dir = "/var/cron/tabs";
$atjob_dir = "/var/at/jobs";
$affirm = 0;
#$debug = 1;
@ -72,7 +73,7 @@ sub lockpw {
fcntl(MASTER_PW, &F_SETFD, 1);
# Apply an advisory lock the password file
if (!flock(MASTER_PW, &LOCK_EX|&LOCK_NB)) {
print STDERR "Couldn't lock ${passwd_file}: $!\n";
print STDERR "${whoami}: Error: Couldn't lock ${passwd_file}: $!\n";
exit(1);
}
}
@ -86,8 +87,13 @@ $SIG{'QUIT'} = 'cleanup';
$SIG{'HUP'} = 'cleanup';
$SIG{'TERM'} = 'cleanup';
if ($#ARGV == 1 && $ARGV[0] eq '-y') {
shift @ARGV;
$affirm = 1;
}
if ($#ARGV > 0) {
print STDERR "usage: ${whoami} [username]\n";
print STDERR "usage: ${whoami} [-y] [username]\n";
exit(1);
}
@ -104,6 +110,11 @@ if ($#ARGV == 0) {
die "Sorry, login name must contain alphanumeric characters only.\n"
if ($login_name !~ /^[a-z0-9_][a-z0-9_\-]*$/);
} else {
if ($affirm) {
print STDERR "${whoami}: Error: -y option given without username!\n";
&unlockpw;
exit 1;
}
# Get the user name from the user
$login_name = &get_login_name;
}
@ -118,19 +129,21 @@ if (($pw_ent = &check_login_name($login_name)) eq '0') {
$shell) = split(/:/, $pw_ent);
if ($uid == 0) {
print "${whoami}: Sorry, I'd rather not remove a user with a uid of 0.\n";
print "${whoami}: Error: I'd rather not remove a user with a uid of 0.\n";
&unlockpw;
exit 1;
}
print "Matching password entry:\n\n$pw_ent\n\n";
if (! $affirm) {
print "Matching password entry:\n\n$pw_ent\n\n";
$ans = &get_yn("Is this the entry you wish to remove? ");
$ans = &get_yn("Is this the entry you wish to remove? ");
if ($ans eq 'N') {
print "User ${login_name} not removed.\n";
&unlockpw;
exit 0;
if ($ans eq 'N') {
print "${whoami}: Informational: User ${login_name} not removed.\n";
&unlockpw;
exit 0;
}
}
#
@ -149,21 +162,24 @@ if (-l $home_dir) {
# If home_dir is a symlink and points to something that isn't a directory,
# or if home_dir is not a symlink and is not a directory, don't remove
# home_dir -- seems like a good thing to do, but probably isn't necessary...
if (((-l $home_dir) && ((-e $real_home_dir) && !(-d $real_home_dir))) ||
(!(-l $home_dir) && !(-d $home_dir))) {
print STDERR "${whoami}: Home ${home_dir} is not a directory, so it won't be removed\n";
print STDERR "${whoami}: Informational: Home ${home_dir} is not a directory, so it won't be removed\n";
$remove_directory = 0;
}
if (length($real_home_dir) && -d $real_home_dir) {
$dir_owner = (stat($real_home_dir))[4]; # UID
if ($dir_owner != $uid) {
print STDERR "${whoami}: Home dir ${real_home_dir} is not owned by ${login_name} (uid ${dir_owner})\n";
print STDERR "${whoami}: Informational: Home dir ${real_home_dir} is" .
" not owned by ${login_name} (uid ${dir_owner})\n," .
"\tso it won't be removed\n";
$remove_directory = 0;
}
}
if ($remove_directory) {
if ($remove_directory && ! $affirm) {
$ans = &get_yn("Remove user's home directory ($home_dir)? ");
if ($ans eq 'N') {
$remove_directory = 0;
@ -188,6 +204,11 @@ if (-e "$crontab_dir/$login_name") {
&remove_at_jobs($login_name, $uid);
#
# Kill all the user's processes
&kill_users_processes($login_name, $uid);
#
# Copy master password file to new file less removed user's entry
@ -208,15 +229,39 @@ if ($remove_directory) {
}
#
# Remove the user's incoming mail file
# Remove files related to the user from the mail directory
if (-e "$mail_dir/$login_name" || -l "$mail_dir/$login_name") {
print STDERR "Removing user's incoming mail file ($mail_dir/$login_name):";
unlink "$mail_dir/$login_name" ||
print STDERR "\n${whoami}: warning: unlink on $mail_dir/$login_name failed ($!) - continuing\n";
#&remove_files_from_dir($mail_dir, $login_name, $uid);
$file = "$mail_dir/$login_name";
if (-e $file || -l $file) {
print STDERR "Removing user's incoming mail file ${file}:";
unlink $file ||
print STDERR "\n${whoami}: Warning: unlink on $file failed ($!) - continuing\n";
print STDERR " done.\n";
}
#
# Remove some pop daemon's leftover file
$file = "$maildir/.${login_name}.pop";
if (-e $file || -l $file) {
print STDERR "Removing pop daemon's temporary mail file ${file}:";
unlink $file ||
print STDERR "\n${whoami}: Warning: unlink on $file failed ($!) - continuing\n";
print STDERR " done.\n";
}
#
# Remove files belonging to the user from the directories /tmp, /var/tmp,
# and /var/tmp/vi.recover. Note that this doesn't take care of the
# problem where a user may have directories or symbolic links in those
# directories -- only regular files are removed.
&remove_files_from_dir('/tmp', $login_name, $uid);
&remove_files_from_dir('/var/tmp', $login_name, $uid);
&remove_files_from_dir('/var/tmp/vi.recover', $login_name, $uid)
if (-e '/var/tmp/vi.recover');
#
# All done!
@ -296,7 +341,7 @@ sub update_passwd_file {
open(NEW_PW, ">$new_passwd_file") ||
die "\n${whoami}: Error: Couldn't open file ${new_passwd_file}:\n $!\n";
chmod(0600, $new_passwd_file) ||
print STDERR "\n${whoami}: warning: couldn't set mode of $new_passwd_file to 0600 ($!)\n\tcontinuing, but please check mode of /etc/master.passwd!\n";
print STDERR "\n${whoami}: Warning: couldn't set mode of $new_passwd_file to 0600 ($!)\n\tcontinuing, but please check mode of /etc/master.passwd!\n";
$skipped = 0;
while ($i = <MASTER_PW>) {
if ($i =~ /\n$/) {
@ -315,7 +360,7 @@ sub update_passwd_file {
if ($skipped == 0) {
print STDERR "\n${whoami}: Whoops! Didn't find ${login_name}'s entry second time around!\n";
unlink($new_passwd_file) ||
print STDERR "\n${whoami}: warning: couldn't unlink $new_passwd_file ($!)\n\tPlease investigate, as this file should not be left in the filesystem\n";
print STDERR "\n${whoami}: Warning: couldn't unlink $new_passwd_file ($!)\n\tPlease investigate, as this file should not be left in the filesystem\n";
&unlockpw;
exit 1;
}
@ -333,9 +378,10 @@ sub update_passwd_file {
sub update_group_file {
local($login_name) = @_;
local($i, $j, $grmember_list, $new_grent);
local($i, $j, $grmember_list, $new_grent, $changes);
local($grname, $grpass, $grgid, $grmember_list, @grmembers);
$changes = 0;
print STDERR "Updating group file:";
open(GROUP, $group_file) ||
die "\n${whoami}: Error: couldn't open ${group_file}: $!\n";
@ -348,9 +394,9 @@ sub update_group_file {
open(NEW_GROUP, ">$new_group_file") ||
die "\n${whoami}: Error: couldn't open ${new_group_file}: $!\n";
chmod($group_perms, $new_group_file) ||
printf STDERR "\n${whoami}: warning: could not set permissions of new group file to %o ($!)\n\tContinuing, but please check permissions of $group_file!\n", $group_perms;
printf STDERR "\n${whoami}: Warning: could not set permissions of new group file to %o ($!)\n\tContinuing, but please check permissions of $group_file!\n", $group_perms;
chown($group_uid, $group_gid, $new_group_file) ||
print STDERR "\n${whoami}: warning: could not set owner/group of new group file to ${group_uid}/${group_gid} ($!)\n\rContinuing, but please check ownership of $group_file!\n";
print STDERR "\n${whoami}: Warning: could not set owner/group of new group file to ${group_uid}/${group_gid} ($!)\n\rContinuing, but please check ownership of $group_file!\n";
while ($i = <GROUP>) {
if (!($i =~ /$login_name/)) {
# Line doesn't contain any references to the user, so just add it
@ -368,14 +414,16 @@ sub update_group_file {
local(@new_grmembers);
foreach $j (@grmembers) {
if ($j ne $login_name) {
push(new_grmembers, $j);
} elsif ($debug) {
print STDERR "Removing $login_name from group $grname\n";
push(@new_grmembers, $j);
} else {
print STDERR " $grname";
$changes = 1;
}
}
if ($grname eq $login_name && $#new_grmembers == -1) {
# Remove a user's personal group if empty
print STDERR "Removing group $grname -- personal group is empty\n";
print STDERR " (removing group $grname -- personal group is empty)";
$changes = 1;
} else {
$grmember_list = join(',', @new_grmembers);
$new_grent = join(':', $grname, $grpass, $grgid, $grmember_list);
@ -385,8 +433,9 @@ sub update_group_file {
}
close(NEW_GROUP);
rename($new_group_file, $group_file) || # Replace old group file with new
die "\n${whoami}: error: couldn't rename $new_group_file to $group_file ($!)\n";
die "\n${whoami}: Error: couldn't rename $new_group_file to $group_file ($!)\n";
close(GROUP); # File handle is worthless now
print STDERR " (no changes)" if (! $changes);
print STDERR " done.\n";
}
@ -416,6 +465,36 @@ sub remove_dir {
system('/bin/rm', '-rf', $dir);
}
sub remove_files_from_dir {
local($dir, $login_name, $uid) = @_;
local($path, $i, $owner);
print STDERR "Removing files belonging to ${login_name} from ${dir}:";
if (!opendir(DELDIR, $dir)) {
print STDERR "\n${whoami}: Warning: couldn't open directory ${dir} ($!)\n";
return;
}
while ($i = readdir(DELDIR)) {
next if $i eq '.';
next if $i eq '..';
$owner = (stat("$dir/$i"))[4]; # UID
if ($uid == $owner) {
if (-f "$dir/$i") {
print STDERR " $i";
unlink "$dir/$i" ||
print STDERR "\n${whoami}: Warning: unlink on ${dir}/${i} failed ($!) - continuing\n";
} else {
print STDERR " ($i not a regular file - skipped)";
}
}
}
closedir(DELDIR);
printf STDERR " done.\n";
}
sub remove_at_jobs {
local($login_name, $uid) = @_;
local($i, $owner, $found);
@ -462,3 +541,36 @@ sub resolvelink {
}
return $path;
}
sub kill_users_processes {
local($login_name, $uid) = @_;
local($pid, $result);
#
# Do something a little complex: fork a child that changes its
# real and effective UID to that of the removed user, then issues
# a "kill(9, -1)" to kill all processes of the same uid as the sender
# (see kill(2) for details).
# The parent waits for the exit of the child and then returns.
if ($pid = fork) {
# Parent process
waitpid($pid, 0);
} elsif (defined $pid) {
# Child process
$< = $uid;
$> = $uid;
if ($< != $uid || $> != $uid) {
print STDERR "${whoami}: Error (kill_users_processes):\n" .
"\tCouldn't reset uid/euid to ${uid}: current uid/euid's are $< and $>\n";
exit 1;
}
$result = kill(9, -1);
print STDERR "Killed process(es) belonging to $login_name.\n"
if $result;
exit 0;
} else {
# Couldn't fork!
print STDERR "${whoami}: Error: couldn't fork to kill ${login_name}'s processes - continuing\n";
}
}