diff options
-rw-r--r-- | adduser/rmuser.8 | 107 | ||||
-rw-r--r-- | adduser/rmuser.perl | 170 |
2 files changed, 227 insertions, 50 deletions
diff --git a/adduser/rmuser.8 b/adduser/rmuser.8 index 88195cd..6f45458 100644 --- a/adduser/rmuser.8 +++ b/adduser/rmuser.8 @@ -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. diff --git a/adduser/rmuser.perl b/adduser/rmuser.perl index 13b78e6..fcc21d3 100644 --- a/adduser/rmuser.perl +++ b/adduser/rmuser.perl @@ -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; @@ -189,6 +205,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 &update_passwd_file; @@ -208,16 +229,40 @@ if ($remove_directory) { } # -# Remove the user's incoming mail file +# Remove files related to the user from the mail directory + +#&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"; +} -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 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! exit 0; @@ -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"; + } +} |