mkproj.in 14.6 KB
Newer Older
1
#!/usr/bin/perl -wT
Leigh Stoller's avatar
Leigh Stoller committed
2
#
3
# Copyright (c) 2000-2015 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/>.
# 
# }}}
Leigh Stoller's avatar
Leigh Stoller committed
23
#
24
use English;
25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
use Getopt::Std;

#
# Perform project approval. Does a lot of stuff, see below!
#
sub usage()
{
    print(STDERR
	  "Usage: mkproj [-s] [-h leader_uid] [-m <message> | -f <file>] ".
	  "<pid>\n".
	  "switches and arguments:\n".
	  "-s         - silent; do not send approval email to leader\n".
	  "-h <uid>   - switch project leader to specified uid\n".
	  "-m <text>  - Include text in approval email message\n".
	  "-f <file>  - Include text from file in approval email message\n".
	  "<pid>      - project to approve.\n");
    exit(-1);
}
my $optlist    = "qsh:m:f:";
my $quiet      = 0;
my $silent     = 0;
46
my $newleader_uid;
47 48 49 50 51
my $message;
my $mfilename;
my $pid;

# Protos
52 53
sub fatal($);

54 55 56
#
# Configure variables
#
57 58
my $TB            = "@prefix@";
my $TBOPS         = "@TBOPSEMAIL@";
59
my $TBAPPROVAL    = "@TBAPPROVALEMAIL@";
60
my $TBAUDIT	  = "@TBAUDITEMAIL@";
61
my $TBBASE        = "@TBBASE@";
62
my $TBWWW         = "@TBWWW@";
63 64 65 66 67 68
my $MKGROUP       = "$TB/sbin/mkgroup";
my $MODGROUPS     = "$TB/sbin/modgroups";
my $MKACCT        = "$TB/sbin/tbacct add";
my $CVSBIN        = "/usr/bin/cvs";
my $CHOWN         = "/usr/sbin/chown";
my $GRANTTYPE     = "$TB/sbin/grantnodetype -d";
69
my $UPDATEPERMS   = "$TB/sbin/update_permissions";
70
my $ELABINELAB    = @ELABINELAB@;
71
my $MAINSITE      = @TBMAINSITE@;
72 73
my $WIKISUPPORT   = @WIKISUPPORT@;
my $BUGDBSUPPORT  = @BUGDBSUPPORT@;
74
my $OPSDBSUPPORT  = @OPSDBSUPPORT@;
75 76
my $CVSSUPPORT    = @CVSSUPPORT@;
my $MAILMANSUPPORT= @MAILMANSUPPORT@;
77 78
my $WITHZFS       = @WITHZFS@;
my $ZFS_NOEXPORT  = @ZFS_NOEXPORT@;
79 80 81 82
my $ADDWIKIPROJ   = "$TB/sbin/addwikiproj";
my $ADDBUGDBPROJ  = "$TB/sbin/addbugdbproj";
my $ADDMMLIST     = "$TB/sbin/addmmlist";
my $OPSDBCONTROL  = "$TB/sbin/opsdb_control";
83
my $CLOSEPROJADMINLIST = "$TB/sbin/closeprojadminlist";
84
	  
85
my @DIRLIST  = ("exp", "images", "logs", "deltas", "tarfiles", "rpms",
86
		"groups", "tiplogs", "images/sigs", "templates");
87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102

#
# 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";
103
use libaudit;
104 105
use libdb;
use libtestbed;
106 107
use User;
use Project;
108
use emutil;
109

110 111 112 113 114 115 116 117 118 119
my $PROJROOT     = PROJROOT();
my $GRPROOT      = GROUPROOT();
my $SCRATCHROOT  = SCRATCHROOT();

#
# XXX semi-hardwired, oddball paths
#
my $TFTPDIR  = "/tftpboot/$PROJROOT";
my $CVSREPOS = "$PROJROOT/cvsrepos";

120 121
# Locals
my $leader;
122
my $oldleader;
123
my $isnonlocal;
124

125
#
126
# We do not want to run this script unless its the real version.
127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
#
if ($EUID != 0) {
    die("*** $0:\n".
	"    Must be setuid! 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");
}

#
# Check args.
#
145 146 147 148 149 150
my %options = ();
if (! getopts($optlist, \%options)) {
    usage();
}
if (defined($options{"q"})) {
    $quiet = 1;
151
}
152 153 154 155 156 157 158 159 160 161 162 163
if (defined($options{"s"})) {
    $silent = 1;
}
if (defined($options{"m"})) {
    $message = $options{"m"};
}
if (defined($options{"f"})) {
    $mfilename = $options{"f"};
    fatal("$mfilename does not exist!")
	if (! -e $mfilename);
}
if (defined($options{"h"})) {
164
    $newleader_uid = $options{"h"};
165 166 167 168 169
}
usage()
    if (! @ARGV);

$pid = $ARGV[0];
170 171 172 173

#
# Untaint the argument.
#
174
if ($pid =~ /^([-\w]+)$/) {
175 176 177 178 179 180
    $pid = $1;
}
else {
    die("Invalid pid '$pid' contains illegal characters.\n");
}

181 182 183 184
# Map invoking user to object.
my $this_user = User->ThisUser();
if (! defined($this_user)) {
    fatal("You ($UID) do not exist!");
185 186 187
}

#
188
# Figure out who called us. Must have admin status to do this.
189
#
190 191
if (!TBAdmin()) {
    fatal("You must be a TB administrator to run this script!");
192 193
}

194 195 196 197 198 199 200 201 202 203
#
# This script is always audited. Mail is sent automatically upon exit.
#
if (AuditStart(0)) {
    #
    # Parent exits normally
    #
    exit(0);
}

204 205 206 207 208 209 210
#
# Map project name to object.
#
my $target_project = Project->Lookup($pid);
if (! defined($target_project)) {
    fatal("Could not map project $pid to its object!");
}
211 212
my $pid_idx = $target_project->pid_idx();
my $gid_idx = $target_project->gid_idx();
213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232

#
# The welcome message ...
#
if (defined($mfilename)) {
    open(MFILE, $mfilename) or
	fatal("Could not open $mfilename");

    $message = "";
    while (<MFILE>) {
	$message .= $_;
    }
    close(MFILE);
}

#
# If a leader uid was provided on the command line, we are changing the
# leader. Note that this is allowed *only* for projects that have not
# been approved yet. 
#
233 234
if (defined($newleader_uid)) {
    $leader = User->Lookup($newleader_uid);
235
    if (!defined($leader)) {
236
	fatal("Could not map user $newleader_uid to its object!");
237 238 239 240 241 242 243 244 245 246
    }
    # See if already did this; is so skip the following checks.
    my $curleader = $target_project->GetLeader();
    if (!defined($curleader)) {
	fatal("Could not map current leader of project $pid to its object!");
    }
    if (! $curleader->SameUser($leader)) {
	fatal("Not allowed to change the leader of an approved project!")
	    if ($target_project->approved());

247 248 249
	# Save for email below.
	$oldleader = $curleader;

250 251 252 253
	# Update the project structure with the new leader. We are going
	# to set the approved bit below, so this is the last chance to do
	# this until we have code in place to change it later.
	$target_project->ChangeLeader($leader) == 0 or
254
	    fatal("Could not change leader for $pid to $newleader_uid!");
255 256 257 258 259 260 261 262 263 264
    }
}
else {
    $leader = $target_project->GetLeader();
    if (!defined($leader)) {
	fatal("Could not map current leader of project $pid to its object!");
    }
}
# Avoid taint check problem.
$leader_uid = $leader->uid();
265 266
$isnonlocal = $target_project->IsNonLocal();
$wasactive  = ($leader->status() eq USERSTATUS_ACTIVE() ? 1 : 0);
267
$silent     = 1 if ($isnonlocal && !$MAINSITE);
268 269 270 271 272 273 274 275 276 277 278 279

# Approve the project; we are committed to the leader.
$target_project->SetApproved(1) == 0 or
    fatal("Could not set the approval bit on project $target_project!");

#
# Leader needs to have his approved bit set. Eventually this should be done
# in mkaccount when that code moves from the web interface.
#
$leader->SetStatus(USERSTATUS_ACTIVE()) == 0 or
    fatal("Could not change $leader to active!");

280 281 282 283
#
# Before we can proceed, we need to create the project (unix) group
# and then create an account for the project leader. We pass this off
# to sub scripts, but because they are also setuid, we need to flip
284
# our UID (perl sillyness).
285
#
286
$EUID = $UID;
287

288
system("$MKGROUP $gid_idx") == 0 or
289
    fatal("$MKGROUP $pid failed!");
290

291
if ($WIKISUPPORT && !$isnonlocal) {
292 293 294
    system("$ADDWIKIPROJ $pid") == 0 or
	fatal("$ADDWIKIPROJ $pid failed!");
}
295
if ($BUGDBSUPPORT && !$isnonlocal) {
296 297 298
    system("$ADDBUGDBPROJ $pid") == 0 or
	fatal("$ADDBUGDBPROJ $pid failed!");
}
299
if ($OPSDBSUPPORT && !$isnonlocal) {
300 301 302
    system("$OPSDBCONTROL addproj $pid") == 0 or
	fatal("$OPSDBCONTROL addproj $pid failed!");
}
303
if ($MAILMANSUPPORT && !$isnonlocal) {
304 305 306
    system("$ADDMMLIST -a ${pid}-users") == 0 or
	fatal("$ADDMMLIST -a ${pid}-users failed!");
}
307

308
#
309
# Skip if user was already an active user; lots of work avoided.
310 311 312 313 314 315 316 317
#
# XXX cannot do this for elabinelab since DB has been pre-loaded with
# state that will make it appear that the initial project leader is active
# when in fact the account has not been created. This is only during
# elabinelab setup and when the swapper is the project leader, so a more
# precise fix is possible.
#
if ($ELABINELAB || !$wasactive) {
318 319 320
    system("$MKACCT $leader_uid") == 0 or
	fatal("$MKACCT $leader_uid failed!");
}
321

322 323
system("$MODGROUPS -a $pid:$pid:project_root $leader_uid") == 0 or
    fatal("$MODGROUPS -a $pid:$pid:project_root $leader_uid failed!");
324

325
$EUID = 0;
326 327 328

#
# This acts as check (and we need the numeric uid) in case mkacct failed!
329 330 331
#
my $unix_name = $target_project->unix_name();

332 333
my (undef,undef,$uid) = getpwnam($leader_uid)
    or fatal("$leader_uid not in passwd file");
334

335
my (undef,undef,$gid) = getgrnam($unix_name)
336 337 338
    or fatal("$pid not in group file");

#
339
# Sanity check that directories got made
340
#
341 342 343 344 345 346
if (! -e "$PROJROOT/$pid") {
    if ($WITHZFS) {
	# Wait for mountd to finish
	if (emutil::waitForMount("$PROJROOT/$pid") < 0) {
	    fatal("Could not access directory $PROJROOT/$pid");
	}
347
    }
348 349 350
    else {
	fatal("Could not access directory $PROJROOT/$pid");
    }
351
}
352 353 354
foreach my $dir (@DIRLIST) {
    if (! -e "$PROJROOT/$pid/$dir") {
	fatal("Could not access directory $PROJROOT/$pid/$dir");
355
    }
356
}
357

358
if (! -e "$GRPROOT/$pid") {
359 360 361 362 363 364 365 366 367
    if ($WITHZFS) {
	# Wait for mountd to finish
	if (emutil::waitForMount("$GRPROOT/$pid") < 0) {
	    fatal("Could not access directory $GRPROOT/$pid");
	}
    }
    else {
	fatal("Could not access directory $GRPROOT/$pid");
    }
368
}
369

370 371
if ($SCRATCHROOT && ! -e "$SCRATCHROOT/$pid") {
    fatal("Could not access directory $SCRATCHROOT/$pid");
372 373 374
}

#
375 376
# XXX LEGACY STUFF NO LONGER SUPPORTED
# Needs to go away or be converted to not use NFS.
377
#
378 379 380 381 382 383 384 385 386 387 388 389 390 391
if (1) {
    #
    # Create a tftp directory for oskit kernels.
    #
    if (-e "$TFTPDIR" && ! -e "$TFTPDIR/$pid" && !$isnonlocal) {
	if (! mkdir("$TFTPDIR/$pid", 0770)) {
	    fatal("Could not make directory $TFTPDIR/$pid: $!");
	}
	if (! chmod(0777, "$TFTPDIR/$pid")) {
	    fatal("Could not chmod directory $TFTPDIR/$pid: $!");
	}
	if (! chown($uid, $gid, "$TFTPDIR/$pid")) {
	    fatal("Could not chown $TFTPDIR/$pid to $uid/$gid: $!");
	}
392
    }
393

394 395 396 397 398
    #
    # Do the CVS stuff if its turned on.
    #
    if ($CVSSUPPORT && !$isnonlocal) {
	my $CVSDIR = "$CVSREPOS/$pid";
399

400 401 402 403
	if (! -e "$CVSDIR") {
	    if (! mkdir("$CVSDIR", 0770)) {
		fatal("Could not make directory $CVSDIR: $!");
	    }
404
	}
405 406 407 408 409 410 411 412 413 414 415 416 417 418
	if (! chmod(0770, "$CVSDIR")) {
	    fatal("Could not chmod directory $CVSDIR: $!");
	}
	if (! chown($uid, $gid, "$CVSDIR")) {
	    fatal("Could not chown $CVSDIR to $uid/$gid: $!");
	}
	if (! -e "$CVSDIR/CVSROOT") {
	    system("$CVSBIN -d $CVSDIR init");
	    if ($?) {
		fatal("Could not cvs init $CVSDIR!");
	    }
	}
	# Chown the tree.
	system("$CHOWN -R ${uid}:${gid} $CVSDIR");
419
	if ($?) {
420
	    fatal("Could not chown ${uid}:${gid} $CVSDIR!");
421
	}
422
    }
423 424
}

425 426 427
#
# Create experiment working directory.
#
Leigh Stoller's avatar
Leigh Stoller committed
428
my $workdir = TBDB_EXPT_WORKDIR() . "/$pid";
429

430
if (! -e $workdir) {
431
    if (! mkdir("$workdir", 0775)) {
432 433
	fatal("Could not make directory $workdir: $!");
    }
434
    if (! chmod(0775, "$workdir")) {
435 436 437 438 439
	fatal("Could not chmod directory $workdir: $!");
    }
    if (! chown($uid, $gid, "$workdir")) {
	fatal("Could not chown $workdir to $uid/$gid: $!");
    }
440 441
}

442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458
#
# Create experiment info (long term archive) directory.
#
my $infodir = "$TB/expinfo/$pid";

if (! -e $infodir) {
    if (! mkdir("$infodir", 0775)) {
	fatal("Could not make directory $infodir: $!");
    }
    if (! chmod(0775, "$infodir")) {
	fatal("Could not chmod directory $infodir: $!");
    }
    if (! chown($uid, $gid, "$infodir")) {
	fatal("Could not chown $infodir to $uid/$gid: $!");
    }
}

459 460 461 462 463 464 465 466
#
# If approved to use remote nodes, then grant permission to use the
# specific types of virtual nodes on those remote physical nodes.
# Unfortunately, the node_types table does not store a relationship
# between the phys type and the virtual types that are hosted on them.
# Need to add that I guess, but in the meantime we have just 3 remote
# phys types to worry about. 
#
467
if (! ($ELABINELAB || $isnonlocal)) {
468 469 470 471
    my $query_result =
	DBQueryFatal("select pcremote_ok from projects where pid='$pid'");
    if ($query_result->num_rows) {
	my ($pcremote) = $query_result->fetchrow_array();
472

473 474
	if (defined($pcremote)) {
	    print "$pcremote\n";
475
	
476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498
	    foreach my $type (split(",", $pcremote)) {
		print "$type\n";
		
		if ($type eq "pcplabphys") {
		    $type = "pcplab";
		}
		elsif ($type eq "pcron") {
		    $type = "pcvwa";
		}
		elsif ($type eq "pcwa") {
		    $type = "pcvwa";
		}
		else {
		    fatal("Unknown remote type $type!");
		}
		print "$type\n";

		$EUID = $UID;

		system("$GRANTTYPE -p $pid $type") == 0 or
		    fatal("Could not grant permission to use type $type!");
		
		$EUID = 0;
499 500 501
	    }
	}
    }
502 503 504 505 506 507 508 509

    #
    # Always need to update the permissions table on new projects.
    #
    $EUID = $UID;

    system("$UPDATEPERMS") == 0 or
	fatal("Could not update permissions table!");
510
		
511
    $EUID = 0;
512 513
}

514 515 516 517 518 519 520 521 522 523 524 525 526
#
# Close proj admin list and remove testbed-approval as a member
#
#
# XXX: DISABLED FOR NOW.
#
#if ($MAILMANSUPPORT) {
#    $EUID = $UID;
#    system("$CLOSEPROJADMINLIST $pid") == 0 or
#        fatal("$CLOSEPROJADMINLIST failed");
#    $EUID = 0;
#}

527 528 529 530
# Send email, unless silent option given.
if (!$silent) {
    my $leader_name  = $leader->name();
    my $leader_email = $leader->email();
531 532
    my $wwwbase      = $target_project->wwwBase();
    my $signupurl    = $target_project->SignupURL();
533
    
534
    SendProjAdminMail
535
	($target_project, "ADMIN", "$leader_name <$leader_email>",
536 537 538 539 540
	 "Project '$pid' Approval",
	 "\n".
	 "This message is to notify you that your project '$pid'\n".
	 "has been approved.  We recommend that you save this link so that\n".
	 "you can send it to people you wish to have join your project.\n".
541
	 "Otherwise, tell them to go to ${wwwbase} and join it.\n".
542
	 "\n".
543
	 "    $signupurl\n".
544 545
	 (defined($message) ? "\n${message}\n" : "") .
	 "\n".
546
	 "Thanks!\n",
547
	 "Bcc: $TBAPPROVAL");
548 549 550 551 552 553

    #
    # If the leader was switched, then generate a second message to the
    # new leader telling him to approve the original leader to the project.
    #
    if (defined($oldleader)) {
554 555 556
	my $oldleader_uid   = $oldleader->uid();
	my $oldleader_name  = $oldleader->name();
	my $oldleader_email = $oldleader->email();
557
	
558 559
	SENDMAIL
	    ("$leader_name <$leader_email>",
560 561 562 563 564 565 566 567
	     "$oldleader_uid $pid Project Join Request",
	     "$oldleader_name wants to join project $pid.\n".
	     "\n".
	     "Please return to $TBWWW,\n".
	     "log in, select the 'New User Approval' page, and enter\n".
	     "your decision regarding ${oldleader_name}'s membership.\n".
	     "\n".
	     "Thanks,\n".
568 569
	     "Testbed Operations\n",
	     "$oldleader_name <$oldleader_email>");
570
    }
571 572
}

573
print "Project Creation Completed!\n";
574 575
exit(0);

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

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