rmuser.in 7.53 KB
Newer Older
1 2 3
#!/usr/bin/perl -wT
#
# EMULAB-COPYRIGHT
4
# Copyright (c) 2000-2007 University of Utah and the Flux Group.
5 6
# All rights reserved.
#
7
use strict;
8 9 10 11 12
use English;
use Getopt::Std;

sub usage()
{
13
    print STDOUT "Usage: rmuser [-p <pid> [-n]] uid\n" .
14 15 16
	"Use the -p option to remove user from a specific project\n";
    exit(-1);
}
17 18 19
my $optlist = "p:n";
my $nuke    = 0;
my $pid;
20 21 22 23 24 25 26 27 28 29

#
# Configure variables
#
my $TB      = "@prefix@";
my $TBOPS   = "@TBOPSEMAIL@";
my $TBLOGS  = "@TBLOGSEMAIL@";
my $CONTROL = "@USERNODE@";
my $BOSSNODE= "@BOSSNODE@";

30
my $MODGROUPS	= "$TB/sbin/modgroups";
31
my $DELACCT	= "$TB/sbin/tbacct del";
32

33
# Locals
34
my $user;
35 36 37 38
my $project;

# Protos
sub fatal($);
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72

#
# We don't want to run this script unless its the real version.
#
if ($EUID != 0) {
    die("*** $0:\n".
	"    Must be root! Maybe its a development version?\n");
}

#
# This script is setuid, so please do not run it as root. Hard to track
# what has happened.
# 
if ($UID == 0) {
    die("*** $0:\n".
	"    Please do not run this as root! Its already setuid!\n");
}

#
# Untaint the path
# 
$ENV{'PATH'} = "/bin:/usr/bin";
delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};

#
# Turn off line buffering on output
#
$| = 1;

# Load the Testbed support stuff. 
use lib "@prefix@/lib";
use libaudit;
use libdb;
use libtestbed;
73
use User;
74 75
use Project;
use Experiment;
76

77 78
my $HOMEDIR	= USERROOT();

79 80 81 82 83 84 85
#
# Check args.
#
#
# Parse command arguments. Once we return from getopts, all that should
# left are the required arguments.
#
86
my %options = ();
87 88 89 90 91 92
if (! getopts($optlist, \%options)) {
    usage();
}
if (defined($options{"p"})) {
    $pid     = $options{"p"};
}
93 94 95
if (defined($options{"n"})) {
    $nuke = 1;
}
96
if ($nuke && !defined($pid)) {
97 98
    usage();
}
99 100 101 102 103
if (@ARGV != 1) {
    usage();
}
$user = $ARGV[0];

104 105 106 107 108 109 110 111 112 113 114 115
# Map target user to object.
my $target_user = User->Lookup($user);
if (! defined($target_user)) {
    fatal("$user does not exist!");
}
my $target_dbid  = $target_user->dbid();
my $target_uid   = $target_user->uid();

# Map invoking user to object.
my $this_user = User->ThisUser();
if (! defined($this_user)) {
    fatal("You ($UID) do not exist!");
116 117
}

118 119 120 121 122 123 124 125 126
# And map pid.
if (defined($pid)) {
    $project = Project->Lookup($pid);
    if (!defined($project)) {
	fatal("No such project $pid!");
    }
    $pid = $project->pid();
}

127 128 129 130 131
#
# Only TB admins are allowed to do this. At some point permit project
# leaders to delete accounts in their projects, but for now lets not.
# There are issues of people in multiple projects.
#
132 133 134 135
if (! $this_user->IsAdmin()) {
    if (! defined($project) ||
	! $project->AccessCheck($this_user, TB_PROJECT_DELUSER)) {
	fatal("You do not have permission to remove user $target_user!");
136 137 138 139 140 141
    }
}

#
# Sanity check. Must not be the head of any experiments (in the project).
#
142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
my @explist;
if (Experiment->UserExperimentList($target_user, \@explist) != 0) {
    fatal("Could not get experiment list for $target_user");
}
if (@explist) {
    if (defined($project)) {
	foreach my $experiment (@explist) {
	    if ($experiment->pid_idx() == $project->pid_idx()) {
		fatal("$target_user is still heading up experiments in ".
		      "project $project!");
	    }
	}
    }
    else {
	fatal("$target_uid is still heading up experiments!");
    }
158 159 160 161 162 163
}

#
# Must not be the head of the project being removed from, or any projects
# if being completely removed.
#
164 165 166
if (defined($project)) {
    if ($target_user->SameUser($project->GetLeader())) {
	fatal("$target_user is the leader of project $project!");
167 168 169
    }
}
else {
170 171 172 173 174 175 176
    my @leaderlist;
    
    if ($target_user->ProjectLeaderList(\@leaderlist) != 0) {
	fatal("Could not get project leader list for $target_user");
    }
    if (@leaderlist) {
	fatal("$target_user is still heading up projects!");
177 178 179
    }
}

180 181 182 183 184 185
#
# If nuke mode is also specified, then the account is being nuked from
# web page because of a project join denial. Check to make sure user
# is not an active user (must be newuser or unapproved).
#
if ($nuke) {
186 187 188
    if ($target_user->status() ne $User::USERSTATUS_NEWUSER &&
	$target_user->status() ne $User::USERSTATUS_UNAPPROVED) {
	fatal("$target_user is not an unapproved user in project $project!");
189 190
    }

191 192 193 194
    # Extra sanity check.
    my @grouplist;
    if ($target_user->GroupMembershipList(\@grouplist, "") != 0) {
	fatal("Could not get group membership list for $target_user");
195
    }
196 197 198 199 200
    foreach my $group (@grouplist) {
	if ($group->pid_idx() != $project->pid_idx()) {
	    fatal("$target_user is a member of other projects!");
	}
    }	
201 202
}

203 204 205 206 207 208 209 210 211 212 213 214 215 216
#
# This script is always audited. Mail is sent automatically upon exit.
#
if (AuditStart(0)) {
    #
    # Parent exits normally
    #
    exit(0);
}

#
# Set the status to frozen if user being removed completely. This
# prevents possible race conditions since the user is no longer able
# to access the web interface and tmcd will no longer return account
217 218
# info for the user.
#
219
# In pidmode (removed from a project), hand the operation off to modgroups
220
# to do the rest.
221
#
222
if (defined($project)) {
223 224 225 226
    #
    # Drop root for calling modgroups.
    #
    $EUID = $UID;
227
    system("$MODGROUPS -r $pid:$pid $target_uid");
228 229 230 231 232

    exit($? >> 8)
	if (!$nuke);
    
    $EUID = 0;
233 234
}
else {
235 236 237 238 239 240 241 242 243 244 245 246
    #
    # Remove the user from all projects.
    #
    my @projectlist;
    if ($target_user->ProjectMembershipList(\@projectlist, "") != 0) {
	fatal("Could not get project membership list for $target_user");
    }
    $EUID = $UID;
    foreach my $project (@projectlist) {
	my $this_pid = $project->pid();
	
	system("$MODGROUPS -r $this_pid:$this_pid $target_uid");
247

248 249 250 251
	exit($? >> 8)
	    if ($?);
    }
    $EUID = 0;
252

253 254 255
    # The freeze is for the update that follows.
    $target_user->Update({'status' => $User::USERSTATUS_FROZEN}) == 0
	or fatal("Could not update status for $target_user!");
256 257

    #
258 259
    # Now schedule account updates; Once the user is frozen, all accounts
    # will be terminated.
260
    #
261
    TBNodeUpdateAccountsByUID($target_uid);
262 263 264 265 266 267
}

#
# Not in pidmode. Kill the user's entire group membership.
# Must be done *after* the account update!
#
268 269
$target_user->Purge() == 0
    or fatal("Could not purge $target_user from the DB!");
270 271

#
272
# Remove user acount from both local and control node. No need to do this in
273
# nukemode (not allowed anyway) since the account never existed.
274
#
275 276
if (! $nuke) {
    $EUID = $UID;
277

278 279
    system("$DELACCT $target_uid") == 0 or
	fatal("$DELACCT $target_uid failed!");
280

281 282
    $EUID = 0;
}
283 284 285 286

#
# Rename the users home dir if its there.
#
287 288
if (-d "$HOMEDIR/$target_uid") {
    my $newname = "$HOMEDIR/$target_uid-" . TBDateTimeFSSafe();
289

290
    if (rename("$HOMEDIR/$target_uid", $newname)) {
291 292
	print "Renamed homedir to $newname. Remember to delete it!\n";
	
293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308
	#
	# Chown the owner/group to root and set the permissions so no
	# one is allowed to look inside.
	#
	if (! chmod(0700, $newname)) {
	    fatal("Could not chmod directory $newname to 0700: $!");
	}
	if (! chown(0, 0, $newname)) {
	    fatal("Could not chown directory $newname to 0/0: $!");
	}
    }
    else {
	fatal("Could not rename user directory to $newname: $!");
    }
}

309 310 311 312 313 314 315 316
#
# In nuke mode, we really do kill the account, since its from a denied
# project join request and there is no reason to keep the account around
# to pollute the table.
#
# Otherwise, the user is "archived" by setting his status accordingly.
#
if ($nuke) {
317 318 319 320
    $target_user->Delete() == 0
	or fatal("Could not delete $target_user from the DB!");
    
    print "$target_user has been removed!\n";
321 322
}
else {
323 324 325 326
    $target_user->SetStatus($User::USERSTATUS_ARCHIVED) == 0
	or fatal("Could not archive $target_user");
    
    print "$target_user has been archived!\n";
327
}
328 329 330 331 332 333 334 335
exit(0);

sub fatal($) {
    my($mesg) = $_[0];

    die("*** $0:\n".
	"    $mesg\n");
}