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

11 12 13 14 15 16 17 18
#
# Users are deleted by removing all of the table entries except the
# entry in the "users" table; this is entry is modified, setting the
# status to "archived". This is causes the entry to be ignored in
# most cases, but allows the stats tables to refer to deleted users.
# That is, when looking at an old experiment record, you can still see
# info about the user that created the experiment.
#
19 20
sub usage()
{
21
    print STDOUT "Usage: rmuser [-p <pid> [-n]] uid\n" .
22 23 24
	"Use the -p option to remove user from a specific project\n";
    exit(-1);
}
25 26 27
my $optlist = "p:n";
my $nuke    = 0;
my $pid;
28 29 30 31 32 33 34 35 36 37

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

38
my $MODGROUPS	= "$TB/sbin/modgroups";
39
my $DELACCT	= "$TB/sbin/tbacct del";
40

41
# Locals
42
my $user;
43 44 45 46
my $project;

# Protos
sub fatal($);
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 73 74 75 76 77 78 79 80

#
# 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;
81
use User;
82 83
use Project;
use Experiment;
84

85 86
my $HOMEDIR	= USERROOT();

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

112 113 114 115 116 117 118 119 120 121 122 123
# 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!");
124 125
}

126 127 128 129 130 131 132 133 134
# And map pid.
if (defined($pid)) {
    $project = Project->Lookup($pid);
    if (!defined($project)) {
	fatal("No such project $pid!");
    }
    $pid = $project->pid();
}

135 136 137 138 139
#
# 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.
#
140 141 142 143
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!");
144 145 146 147 148 149
    }
}

#
# Sanity check. Must not be the head of any experiments (in the project).
#
150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165
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!");
    }
166 167 168 169 170 171
}

#
# Must not be the head of the project being removed from, or any projects
# if being completely removed.
#
172 173 174 175 176
if (!$nuke) {
    if (defined($project)) {
	if ($target_user->SameUser($project->GetLeader())) {
	    fatal("$target_user is the leader of project $project!");
	}
177
    }
178 179 180 181 182 183 184 185 186
    else {
	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!");
	}
187 188
    }
}
189 190 191 192 193 194
#
# 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) {
195 196 197
    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!");
198 199
    }

200 201 202 203
    # Extra sanity check.
    my @grouplist;
    if ($target_user->GroupMembershipList(\@grouplist, "") != 0) {
	fatal("Could not get group membership list for $target_user");
204
    }
205 206 207 208 209
    foreach my $group (@grouplist) {
	if ($group->pid_idx() != $project->pid_idx()) {
	    fatal("$target_user is a member of other projects!");
	}
    }	
210 211
}

212 213 214 215 216 217 218 219 220 221 222 223 224 225
#
# 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
226 227
# info for the user.
#
228
# In pidmode (removed from a project), hand the operation off to modgroups
229
# to do the rest.
230
#
231
if (defined($project)) {
232 233 234 235
    #
    # Drop root for calling modgroups.
    #
    $EUID = $UID;
236
    system("$MODGROUPS -r $pid:$pid $target_uid");
237
    my $estatus = ($? >> 8);
238

239 240 241 242 243 244 245 246 247
    #
    # Update all nodes in the project so that account will be removed
    # and homedir unmounted.
    #
    if (!$estatus) {
	TBNodeUpdateAccountsByPid($pid)
    }

    exit($estatus)
248 249 250
	if (!$nuke);
    
    $EUID = 0;
251 252
}
else {
253 254 255 256 257 258 259 260 261 262 263 264
    #
    # 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");
265

266 267 268 269
	exit($? >> 8)
	    if ($?);
    }
    $EUID = 0;
270

271 272 273
    # 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!");
274 275

    #
276 277
    # Now schedule account updates; Once the user is frozen, all accounts
    # will be terminated.
278
    #
279
    TBNodeUpdateAccountsByUID($target_uid);
280 281 282 283 284 285
}

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

#
290
# Remove user account from both local and control node. No need to do this in
291
# nukemode (not allowed anyway) since the account never existed.
292
#
293 294
if (! $nuke) {
    $EUID = $UID;
295

296 297
    system("$DELACCT $target_uid") == 0 or
	fatal("$DELACCT $target_uid failed!");
298

299 300
    $EUID = 0;
}
301 302 303 304

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

308
    if (rename("$HOMEDIR/$target_uid", $newname)) {
309 310
	print "Renamed homedir to $newname. Remember to delete it!\n";
	
311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326
	#
	# 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: $!");
    }
}

327 328 329 330 331 332 333 334
#
# 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) {
335 336 337 338
    $target_user->Delete() == 0
	or fatal("Could not delete $target_user from the DB!");
    
    print "$target_user has been removed!\n";
339 340
}
else {
341 342 343 344
    $target_user->SetStatus($User::USERSTATUS_ARCHIVED) == 0
	or fatal("Could not archive $target_user");
    
    print "$target_user has been archived!\n";
345
}
346 347 348 349 350 351 352 353
exit(0);

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

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