rmuser.in 10.6 KB
Newer Older
1 2
#!/usr/bin/perl -wT
#
3
# Copyright (c) 2000-2018 University of Utah and the Flux Group.
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
# 
# {{{EMULAB-LICENSE
# 
# This file is part of the Emulab network testbed software.
# 
# This file is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or (at
# your option) any later version.
# 
# This file is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public
# License for more details.
# 
# You should have received a copy of the GNU Affero General Public License
# along with this file.  If not, see <http://www.gnu.org/licenses/>.
# 
# }}}
23
#
24
use strict;
25 26 27
use English;
use Getopt::Std;

28 29 30 31 32 33 34 35
#
# 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.
#
36 37
sub usage()
{
38
    print STDOUT "Usage: rmuser [-p <pid> [-n]] uid\n" .
39 40 41
	"Use the -p option to remove user from a specific project\n";
    exit(-1);
}
42 43 44
my $optlist = "p:n";
my $nuke    = 0;
my $pid;
45 46 47 48 49 50 51 52 53

#
# Configure variables
#
my $TB      = "@prefix@";
my $TBOPS   = "@TBOPSEMAIL@";
my $TBLOGS  = "@TBLOGSEMAIL@";
my $CONTROL = "@USERNODE@";
my $BOSSNODE= "@BOSSNODE@";
54 55 56 57 58
my $OURDOMAIN      = "@OURDOMAIN@";
my $PGENISUPPORT   = @PROTOGENI_SUPPORT@;
my $PORTAL_ENABLE  = @PORTAL_ENABLE@;
my $PORTAL_PRIMARY = @PORTAL_ISPRIMARY@;
my $MODGROUPS	   = "$TB/sbin/modgroups";
59
my $TBACCT	   = "$TB/sbin/tbacct";
60
my $POSTCRL	   = "$TB/sbin/protogeni/postcrl";
61

62
# Locals
63
my $user;
64 65 66 67
my $project;

# Protos
sub fatal($);
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101

#
# 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;
102
use User;
103 104
use Project;
use Experiment;
105
use EmulabFeatures;
106 107 108 109 110 111 112 113

#
# Check args.
#
#
# Parse command arguments. Once we return from getopts, all that should
# left are the required arguments.
#
114
my %options = ();
115 116 117 118 119 120
if (! getopts($optlist, \%options)) {
    usage();
}
if (defined($options{"p"})) {
    $pid     = $options{"p"};
}
121 122 123
if (defined($options{"n"})) {
    $nuke = 1;
}
124
if ($nuke && !defined($pid)) {
125 126
    usage();
}
127 128 129 130 131
if (@ARGV != 1) {
    usage();
}
$user = $ARGV[0];

132 133 134 135 136 137 138
# 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();
139
my $reactivate   = $target_user->status() eq $User::USERSTATUS_INACTIVE;
140 141 142 143 144

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

147 148 149 150 151 152 153 154 155
# And map pid.
if (defined($pid)) {
    $project = Project->Lookup($pid);
    if (!defined($project)) {
	fatal("No such project $pid!");
    }
    $pid = $project->pid();
}

156 157 158 159 160
#
# 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.
#
161 162
if (! $this_user->IsAdmin()) {
    if (! defined($project) ||
163 164 165
	! $project->AccessCheck($this_user, TB_PROJECT_DELUSER) ||
	# This prevents group_root from deleting other group_roots or leader.
	$project->Trust($target_user) >= $project->Trust($this_user)) {
166
	fatal("You do not have permission to remove user $target_user!");
167 168 169
    }
}

170 171 172 173 174 175 176 177 178 179 180 181 182 183
#
# Check to see if user has created slices (SA).
#
if ($PGENISUPPORT) {
    require GeniDB;
    require GeniUser;
    require GeniSlice;
    require GeniHRN;
    
    # Connect to the proper DB.
    GeniDB::DBConnect(GeniDB::GENISA_DBNAME());

    my $urn = GeniHRN::Generate("@OURDOMAIN@", "user", $target_uid);
    my $geniuser = GeniUser->Lookup($urn, 1);
184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
    if (defined($geniuser)) {
	my @slices = GeniSlice->LookupByCreator($geniuser);
	if (@slices) {
	    if (defined($project)) {
		foreach my $slice (@slices) {
		    my $slice_urn = $slice->urn();
		    if (defined($slice_urn->project()) &&
			lc($slice_urn->project()) eq lc($project->pid())) {
			fatal("$target_user is still heading up ".
			      "ProtoGENI Slices in project $pid");
		    }
		}
	    }
	    else {
		fatal("$target_user is still heading up ProtoGENI Slices");
	    }
	}
201 202
    }
}
203 204 205 206 207 208 209 210 211 212 213 214
#
# Cannot delete from Portal if exported.
#
if ($PORTAL_ENABLE && $PORTAL_PRIMARY) {
    my $exports;
    if ($target_user->PeerExports(\$exports) != 0) {
	fatal("Could not get peer exports list for $target_user");
    }
    if (keys(%{ $exports })) {
	fatal("Cannot delete user; still exported to peers");
    }
}
215

216 217 218
#
# Sanity check. Must not be the head of any experiments (in the project).
#
219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234
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!");
    }
235 236 237 238 239 240
}

#
# Must not be the head of the project being removed from, or any projects
# if being completely removed.
#
241 242 243 244 245
if (!$nuke) {
    if (defined($project)) {
	if ($target_user->SameUser($project->GetLeader())) {
	    fatal("$target_user is the leader of project $project!");
	}
246
    }
247 248 249 250 251 252 253 254 255
    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!");
	}
256 257
    }
}
258 259 260 261 262 263
#
# 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) {
264 265 266
    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!");
267 268
    }

269 270 271 272
    # Extra sanity check.
    my @grouplist;
    if ($target_user->GroupMembershipList(\@grouplist, "") != 0) {
	fatal("Could not get group membership list for $target_user");
273
    }
274 275 276 277 278
    foreach my $group (@grouplist) {
	if ($group->pid_idx() != $project->pid_idx()) {
	    fatal("$target_user is a member of other projects!");
	}
    }	
279 280
}

281 282 283 284 285 286 287 288 289 290 291 292 293 294
#
# 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
295 296
# info for the user.
#
297
# In pidmode (removed from a project), hand the operation off to modgroups
298
# to do the rest.
299
#
300
if (defined($project)) {
301 302 303 304
    #
    # Drop root for calling modgroups.
    #
    $EUID = $UID;
305
    system("$MODGROUPS -r $pid:$pid $target_uid");
306
    my $estatus = ($? >> 8);
307

308 309 310 311 312 313 314
    #
    # Update all nodes in the project so that account will be removed
    # and homedir unmounted.
    #
    if (!$estatus) {
	TBNodeUpdateAccountsByPid($pid)
    }
315
    $EUID = 0;
316

317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332
    #
    # Lets check to see if any projects left. If not, revoke their
    # certificate.
    #
    my @projlist;
    if ($target_user->ProjectMembershipList(\@projlist, "") == 0) {
	if (! @projlist) {
	    $target_user->RevokeSSLCerts();
	    if ($PGENISUPPORT) {
		my $SAVEUID = $UID;
		$UID = 0;
		system("$POSTCRL");
		$UID = $SAVEUID;
	    }
	}
    }
333
    exit($estatus)
334
	if (!$nuke);
335 336
}
else {
337 338 339 340 341 342 343 344 345 346 347 348
    #
    # 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");
349

350 351 352 353
	exit($? >> 8)
	    if ($?);
    }
    $EUID = 0;
354

355 356 357
    # 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!");
358 359

    #
360 361
    # Now schedule account updates; Once the user is frozen, all accounts
    # will be terminated.
362
    #
363
    TBNodeUpdateAccountsByUID($target_uid);
364 365 366 367 368 369
}

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

#
374
# Remove user account from both local and control node. No need to do this in
375
# nukemode (not allowed anyway) since the account never existed.
376
#
377 378
if (! $nuke) {
    $EUID = $UID;
379

380 381 382 383 384 385 386 387 388
    #
    # Before we can do anything, we have to reactivate.
    #
    if ($reactivate &&
	system("$TBACCT -f reactivate $target_uid")) {
	fatal("$TBACCT -f reactivate $target_uid failed!");
    }
    system("$TBACCT del $target_uid") == 0 or
	fatal("$TBACCT del $target_uid failed!");
389

390 391
    $EUID = 0;
}
392

393 394 395 396 397 398
#
# Kill any features
#
EmulabFeatures->DeleteAll($target_user) == 0 or
    fatal("Could not delete all features for $target_user");

399 400
#
# Rename the users home dir if its there.
401
# XXX this is now handled by $DELACCT call.
402 403
#

404 405 406 407 408 409 410 411
#
# 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) {
412 413 414 415
    $target_user->Delete() == 0
	or fatal("Could not delete $target_user from the DB!");
    
    print "$target_user has been removed!\n";
416 417
}
else {
418
    $target_user->Update({'admin' => 0, "stud" => 0});
419 420 421 422
    $target_user->SetStatus($User::USERSTATUS_ARCHIVED) == 0
	or fatal("Could not archive $target_user");
    
    print "$target_user has been archived!\n";
423
}
424 425 426 427 428 429 430 431
exit(0);

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

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