From 495533f172691d7c3cb333862a13765462a7039a Mon Sep 17 00:00:00 2001 From: Wolfram Schneider Date: Sun, 17 Nov 1996 03:51:33 +0000 Subject: install rmuser, addgroup, rmgroup in /usr/sbin --- adduser/Makefile | 6 +- adduser/adduser.8 | 5 +- adduser/rmuser.8 | 105 ++++++++++++ adduser/rmuser.perl | 462 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 574 insertions(+), 4 deletions(-) create mode 100644 adduser/rmuser.8 create mode 100644 adduser/rmuser.perl diff --git a/adduser/Makefile b/adduser/Makefile index 7c11524..e2014ba 100644 --- a/adduser/Makefile +++ b/adduser/Makefile @@ -1,7 +1,7 @@ -# $Id$ +# $Id: Makefile,v 1.8 1996/11/04 17:21:08 wosch Exp $ -SCRIPTS= adduser.perl #rmuser.perl addgroup.tcl rmgroup.sh -MAN8= adduser.8 #rmuser.8 addgroup.8 rmgroup.8 +SCRIPTS= adduser.perl rmuser.perl addgroup.tcl rmgroup.sh +MAN8= adduser.8 rmuser.8 addgroup.8 rmgroup.8 beforeinstall: .for script in ${SCRIPTS} diff --git a/adduser/adduser.8 b/adduser/adduser.8 index c012f7c..2d87f5b 100644 --- a/adduser/adduser.8 +++ b/adduser/adduser.8 @@ -22,7 +22,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $Id: adduser.8,v 1.11 1996/08/27 20:04:33 wosch Exp $ +.\" $Id: adduser.8,v 1.12 1996/08/28 17:54:13 adam Exp $ .Dd Jan, 9, 1995 .Dt ADDUSER 8 @@ -235,7 +235,10 @@ logfile for adduser .Xr passwd 5 , .Xr group 5 , .Xr shells 5 , +.Xr addgroup 8 , .Xr pwd_mkdb 8 , +.Xr rmgroup 8 , +.Xr rmuser 8 , .Xr vipw 8 .\" .Sh BUGS diff --git a/adduser/rmuser.8 b/adduser/rmuser.8 new file mode 100644 index 0000000..32dd2fa --- /dev/null +++ b/adduser/rmuser.8 @@ -0,0 +1,105 @@ +.\" Copyright 1995, 1996 +.\" Guy Helmer, Madison, South Dakota 57042. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer as +.\" the first lines of this file unmodified. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. The name of the author may not be used to endorse or promote products +.\" derived from this software without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY GUY HELMER ``AS IS'' AND ANY EXPRESS OR +.\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +.\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +.\" IN NO EVENT SHALL GUY HELMER BE LIABLE FOR ANY DIRECT, INDIRECT, +.\" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +.\" NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.\" +.\" $Id: rmuser.8,v 1.2 1996/08/11 13:03:24 wosch Exp $ +.\" +.Dd July 16, 1996 +.Dt RMUSER 8 +.Os +.Sh NAME +.Nm rmuser +.Nd remove users from the system +.Sh SYNOPSIS +.Nm rmuser +.Op Ar username +.Sh DESCRIPTION +The utility +.Nm rmuser +removes a user's +.Xr crontab 1 +entry (if any) and 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 +.Pa /etc/group . +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). +.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. +.Pp +.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), +.Nm rmuser +asks whether you wish to remove the user's home directory and everything +below. +.Pp +Available options: +.Pp +.Bl -tag -width username +.It Ar \&username +Identifies the user to be removed; if not present, +.Nm rmuser +interactively asks for the user to be removed. +.Sh FILES +.Bl -tag -width /etc/master.passwd -compact +.It Pa /etc/master.passwd +.It Pa /etc/passwd +.It Pa /etc/group +.It Pa /etc/spwd.db +.It Pa /etc/pwd.db +.El +.Sh SEE ALSO +.Xr at 1 , +.Xr chpass 1 , +.Xr crontab 1 , +.Xr finger 1 , +.Xr passwd 1 , +.Xr group 5 , +.Xr passwd 5 , +.Xr adduser 8 , +.Xr addgroup 8 , +.Xr pwd_mkdb 8 , +.Xr rmgroup 8 , +.Xr vipw 8 +.Sh HISTORY +The +.Nm +command appeared in +.Fx 2.1.5 . + +.\" .Sh AUTHOR +.\" Guy Helmer, Madison, South Dakota diff --git a/adduser/rmuser.perl b/adduser/rmuser.perl new file mode 100644 index 0000000..d556eff --- /dev/null +++ b/adduser/rmuser.perl @@ -0,0 +1,462 @@ +#!/usr/bin/perl +# -*- perl -*- +# Copyright 1995, 1996 Guy Helmer, Madison, South Dakota 57042. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer as +# the first lines of this file unmodified. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. The name of the author may not be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY GUY HELMER ``AS IS'' AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL GUY HELMER BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# rmuser - Perl script to remove users +# +# Guy Helmer , 07/17/96 +# +# $Id: removeuser.perl,v 1.2 1996/08/11 13:03:25 wosch Exp $ + +sub LOCK_SH {0x01;} +sub LOCK_EX {0x02;} +sub LOCK_NB {0x04;} +sub LOCK_UN {0x08;} +sub F_SETFD {2;} + +$ENV{"PATH"} = "/bin:/sbin:/usr/bin:/usr/sbin"; +umask(022); +$whoami = $0; +$passwd_file = "/etc/master.passwd"; +$new_passwd_file = "${passwd_file}.new.$$"; +$group_file = "/etc/group"; +$new_group_file = "${group_file}.new.$$"; +$mail_dir = "/var/mail"; +$crontab_dir = "/var/cron/tabs"; +$atjob_dir = "/var/at/jobs"; + +#$debug = 1; + +sub cleanup { + local($sig) = @_; + + print STDERR "Caught signal SIG$sig -- cleaning up.\n"; + &unlockpw; + if (-e $new_passwd_file) { + unlink $new_passwd_file; + } + exit(0); +} + +sub lockpw { + # Open the password file for reading + if (!open(MASTER_PW, "$passwd_file")) { + print STDERR "${whoami}: Error: Couldn't open ${passwd_file}: $!\n"; + exit(1); + } + # Set the close-on-exec flag just in case + 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"; + exit(1); + } +} + +sub unlockpw { + flock(MASTER_PW, &LOCK_UN); +} + +$SIG{'INT'} = 'cleanup'; +$SIG{'QUIT'} = 'cleanup'; +$SIG{'HUP'} = 'cleanup'; +$SIG{'TERM'} = 'cleanup'; + +if ($#ARGV > 0) { + print STDERR "usage: ${whoami} [username]\n"; + exit(1); +} + +if ($< != 0) { + print STDERR "${whoami}: Error: you must be root to use ${whoami}\n"; + exit(1); +} + +&lockpw; + +if ($#ARGV == 0) { + # Username was given as a parameter + $login_name = pop(@ARGV); +} else { + # Get the user name from the user + $login_name = &get_login_name; +} + +if (($pw_ent = &check_login_name($login_name)) eq '0') { + print STDERR "${whoami}: Error: User ${login_name} not in password database\n"; + &unlockpw; + exit 1; +} + +($name, $password, $uid, $gid, $class, $change, $expire, $gecos, $home_dir, + $shell) = split(/:/, $pw_ent); + +if ($uid == 0) { + print "${whoami}: Sorry, 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"; + +$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; +} + +# +# Get owner of user's home directory; don't remove home dir if not +# owned by $login_name + +$remove_directory = 1; + +if (-l $home_dir) { + $real_home_dir = &resolvelink($home_dir); +} else { + $real_home_dir = $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"; + $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"; + $remove_directory = 0; + } +} + +if ($remove_directory) { + $ans = &get_yn("Remove user's home directory ($home_dir)? "); + if ($ans eq 'N') { + $remove_directory = 0; + } +} + +#exit 0 if $debug; + +# +# Remove the user's crontab, if there is one +# (probably needs to be done before password databases are updated) + +if (-e "$crontab_dir/$login_name") { + print STDERR "Removing user's crontab:"; + system('/usr/bin/crontab', '-u', $login_name, '-r'); + print STDERR " done.\n"; +} + +# +# Remove the user's at jobs, if any +# (probably also needs to be done before password databases are updated) + +&remove_at_jobs($login_name, $uid); + +# +# Copy master password file to new file less removed user's entry + +&update_passwd_file; + +# +# Remove the user from all groups in /etc/group + +&update_group_file($login_name); + +# +# Remove the user's home directory + +if ($remove_directory) { + print STDERR "Removing user's home directory ($home_dir):"; + &remove_dir($home_dir); + print STDERR " done.\n"; +} + +# +# Remove the user's incoming mail file + +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"; + print STDERR " done.\n"; +} + +# +# All done! + +exit 0; + +sub get_login_name { + # + # Get new user's name + local($done, $login_name); + + for ($done = 0; ! $done; ) { + print "Enter login name for user to remove: "; + $login_name = <>; + chop $login_name; + if (!($login_name =~ /[A-Za-z0-9_]/)) { + print STDERR "Sorry, login name must contain alphanumeric characters only.\n"; + } elsif (length($login_name) > 8 || length($login_name) == 0) { + print STDERR "Sorry, login name must be eight characters or less.\n"; + } else { + $done = 1; + } + } + + print "User name is ${login_name}\n" if $debug; + return($login_name); +} + +sub check_login_name { + # + # Check to see whether login name is in password file + local($login_name) = @_; + local($Mname, $Mpassword, $Muid, $Mgid, $Mclass, $Mchange, $Mexpire, + $Mgecos, $Mhome_dir, $Mshell); + local($i); + + seek(MASTER_PW, 0, 0); + while ($i = ) { + chop $i; + ($Mname, $Mpassword, $Muid, $Mgid, $Mclass, $Mchange, $Mexpire, + $Mgecos, $Mhome_dir, $Mshell) = split(/:/, $i); + if ($Mname eq $login_name) { + seek(MASTER_PW, 0, 0); + return($i); # User is in password database + } + } + seek(MASTER_PW, 0, 0); + + return '0'; # User wasn't found +} + +sub get_yn { + # + # Get a yes or no answer; return 'Y' or 'N' + local($prompt) = @_; + local($done, $ans); + + for ($done = 0; ! $done; ) { + print $prompt; + $ans = <>; + chop $ans; + $ans =~ tr/a-z/A-Z/; + if (!($ans =~ /^[YN]/)) { + print STDERR "Please answer (y)es or (n)o.\n"; + } else { + $done = 1; + } + } + + return(substr($ans, 0, 1)); +} + +sub update_passwd_file { + local($skipped, $i); + + print STDERR "Updating password file,"; + seek(MASTER_PW, 0, 0); + 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"; + $skipped = 0; + while ($i = ) { + if ($i =~ /\n$/) { + chop $i; + } + if ($i ne $pw_ent) { + print NEW_PW "$i\n"; + } else { + print STDERR "Dropped entry for $login_name\n" if $debug; + $skipped = 1; + } + } + close(NEW_PW); + seek(MASTER_PW, 0, 0); + + 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"; + &unlockpw; + exit 1; + } + + # + # Run pwd_mkdb to install the updated password files and databases + + print STDERR " updating databases,"; + system('/usr/sbin/pwd_mkdb', '-p', ${new_passwd_file}); + print STDERR " done.\n"; + + close(MASTER_PW); # Not useful anymore +} + +sub update_group_file { + local($login_name) = @_; + + local($i, $j, $grmember_list, $new_grent); + local($grname, $grpass, $grgid, $grmember_list, @grmembers); + + print STDERR "Updating group file:"; + open(GROUP, $group_file) || + die "\n${whoami}: Error: couldn't open ${group_file}: $!\n"; + if (!flock(GROUP, &LOCK_EX|&LOCK_NB)) { + print STDERR "\n${whoami}: Error: couldn't lock ${group_file}: $!\n"; + exit 1; + } + local($group_perms, $group_uid, $group_gid) = + (stat(GROUP))[2, 4, 5]; # File Mode, uid, gid + 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; + 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"; + while ($i = ) { + if (!($i =~ /$login_name/)) { + # Line doesn't contain any references to the user, so just add it + # to the new file + print NEW_GROUP $i; + } else { + # + # Remove the user from the group + if ($i =~ /\n$/) { + chop $i; + } + ($grname, $grpass, $grgid, $grmember_list) = split(/:/, $i); + @grmembers = split(/,/, $grmember_list); + undef @new_grmembers; + 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"; + } + } + 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"; + } else { + $grmember_list = join(',', @new_grmembers); + $new_grent = join(':', $grname, $grpass, $grgid, $grmember_list); + print NEW_GROUP "$new_grent\n"; + } + } + } + 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"; + close(GROUP); # File handle is worthless now + print STDERR " done.\n"; +} + +sub remove_dir { + # Remove the user's home directory + local($dir) = @_; + local($linkdir); + + if (-l $dir) { + $linkdir = &resolvelink($dir); + # Remove the symbolic link + unlink($dir) || + warn "${whoami}: Warning: could not unlink symlink $dir: $!\n"; + if (!(-e $linkdir)) { + # + # Dangling symlink - just return now + return; + } + # Set dir to be the resolved pathname + $dir = $linkdir; + } + if (!(-d $dir)) { + print STDERR "${whoami}: Warning: $dir is not a directory\n"; + unlink($dir) || warn "${whoami}: Warning: could not unlink $dir: $!\n"; + return; + } + system('/bin/rm', '-rf', $dir); +} + +sub remove_at_jobs { + local($login_name, $uid) = @_; + local($i, $owner, $found); + + $found = 0; + opendir(ATDIR, $atjob_dir) || return; + while ($i = readdir(ATDIR)) { + next if $i eq '.'; + next if $i eq '..'; + next if $i eq '.lockfile'; + + $owner = (stat("$atjob_dir/$i"))[4]; # UID + if ($uid == $owner) { + if (!$found) { + print STDERR "Removing user's at jobs:"; + $found = 1; + } + # Use atrm to remove the job + print STDERR " $i"; + system('/usr/bin/atrm', $i); + } + } + closedir(ATDIR); + if ($found) { + print STDERR " done.\n"; + } +} + +sub resolvelink { + local($path) = @_; + local($l); + + while (-l $path && -e $path) { + if (!defined($l = readlink($path))) { + die "${whoami}: readlink on $path failed (but it should have worked!): $!\n"; + } + if ($l =~ /^\//) { + # Absolute link + $path = $l; + } else { + # Relative link + $path =~ s/\/[^\/]+\/?$/\/$l/; # Replace last component of path + } + } + return $path; +} -- cgit v1.2.3-56-ge451