GeniTicket.pm.in 15.2 KB
Newer Older
Leigh B. Stoller's avatar
Leigh B. Stoller committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
#!/usr/bin/perl -wT
#
# EMULAB-COPYRIGHT
# Copyright (c) 2008 University of Utah and the Flux Group.
# All rights reserved.
#
package GeniTicket;

#
# Some simple ticket stuff.
#
use strict;
use Exporter;
use vars qw(@ISA @EXPORT);

@ISA    = "Exporter";
@EXPORT = qw ( );

# Must come after package declaration!
use lib '@prefix@/lib';
use GeniDB;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
22
use GeniCredential;
23
use GeniCertificate;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
24 25
use Experiment;
use libdb qw(TBGetUniqueIndex);
26
use libtestbed qw(NewUUID);
Leigh B. Stoller's avatar
Leigh B. Stoller committed
27
use English;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
28 29
use XML::Simple;
use XML::LibXML;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
30
use Data::Dumper;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
31 32 33
use Date::Parse;
use POSIX qw(strftime);
use Time::Local;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
34
use File::Temp qw(tempfile);
Leigh B. Stoller's avatar
Leigh B. Stoller committed
35
use overload ('""' => 'Stringify');
Leigh B. Stoller's avatar
Leigh B. Stoller committed
36 37 38 39 40 41 42 43 44

# Configure variables
my $TB		   = "@prefix@";
my $TBOPS          = "@TBOPSEMAIL@";
my $TBAPPROVAL     = "@TBAPPROVALEMAIL@";
my $TBAUDIT   	   = "@TBAUDITEMAIL@";
my $BOSSNODE       = "@BOSSNODE@";
my $OURDOMAIN      = "@OURDOMAIN@";
my $SIGNCRED	   = "$TB/sbin/signgenicred";
Leigh B. Stoller's avatar
Leigh B. Stoller committed
45
my $VERIFYCRED	   = "$TB/sbin/verifygenicred";
Leigh B. Stoller's avatar
Leigh B. Stoller committed
46
my $NFREE	   = "$TB/bin/nfree";
Leigh B. Stoller's avatar
Leigh B. Stoller committed
47
my $CMCERT	   = "$TB/etc/genicm.pem";
Leigh B. Stoller's avatar
Leigh B. Stoller committed
48

Leigh B. Stoller's avatar
Leigh B. Stoller committed
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
# Cache of tickets.
my %tickets = ();

#
# Lookup by local idx.
#
sub Lookup($$)
{
    my ($class, $idx) = @_;

    return undef
	if (! ($idx =~ /^\d*$/));
    return $tickets{"$idx"}
        if (exists($tickets{"$idx"}));

    my $query_result =
	DBQueryWarn("select * from geni_tickets where idx='$idx'");

    return undef
	if (!defined($query_result) || !$query_result->numrows);

    my $row = $query_result->fetchrow_hashref();

    # Map the component
    my $component;
74 75
    if ($row->{'component_uuid'}) {
	$component = GeniComponent->Lookup($row->{'component_uuid'});
Leigh B. Stoller's avatar
Leigh B. Stoller committed
76 77 78 79 80 81 82 83
	return undef
	    if (!defined($component));
    }
    my $ticket = GeniTicket->CreateFromSignedTicket($row->{'ticket_string'},
						    $component, 1);
    return undef
	if (!defined($ticket));

Leigh B. Stoller's avatar
Leigh B. Stoller committed
84 85 86 87
    # We ignore this in the ticket. In fact, we need to change how we bring
    # tickets back into the system.
    $ticket->{'redeem_before'} = $row->{'redeem_before'};

88 89 90 91 92
    # Mark as coming from the DB.
    $ticket->{'idx'}    = $idx;
    $ticket->{'stored'} = 1;

    # Cache.
Leigh B. Stoller's avatar
Leigh B. Stoller committed
93 94 95 96
    $tickets{"$idx"} = $ticket;
    return $ticket;
}

Leigh B. Stoller's avatar
Leigh B. Stoller committed
97
#
Leigh B. Stoller's avatar
Leigh B. Stoller committed
98
# Create an unsigned ticket object, to be populated and signed and returned.
Leigh B. Stoller's avatar
Leigh B. Stoller committed
99
#
Leigh B. Stoller's avatar
Leigh B. Stoller committed
100
sub Create($$$$)
Leigh B. Stoller's avatar
Leigh B. Stoller committed
101
{
Leigh B. Stoller's avatar
Leigh B. Stoller committed
102
    my ($class, $slice, $owner, $rspec) = @_;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
103

Leigh B. Stoller's avatar
Leigh B. Stoller committed
104 105 106
    # Every Ticket gets a new unique index (sequence number).
    my $seqno = TBGetUniqueIndex('next_ticket', 1);
    
Leigh B. Stoller's avatar
Leigh B. Stoller committed
107
    my $self = {};
Leigh B. Stoller's avatar
Leigh B. Stoller committed
108
    $self->{'rspec'}         = $rspec;
109
    $self->{'ticket_uuid'}   = undef;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
110 111
    $self->{'slice_uuid'}    = $slice->uuid();
    $self->{'owner_uuid'}    = $owner->uuid();
112 113
    $self->{'slice_cert'}    = $slice->GetCertificate();
    $self->{'owner_cert'}    = $owner->GetCertificate();
Leigh B. Stoller's avatar
Leigh B. Stoller committed
114
    $self->{'seqno'}         = $seqno;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
115 116
    $self->{'ticket_string'} = undef;
    $self->{'component'}     = undef;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
117 118
    $self->{'stored'}        = 0;	# Stored to the DB.

Leigh B. Stoller's avatar
Leigh B. Stoller committed
119 120 121 122 123 124
    #
    # For now, all tickets expire very quickly ...
    #
    $self->{'redeem_before'} =
	POSIX::strftime("20%y-%m-%dT%H:%M:%S", localtime(time() + (2*60)));

Leigh B. Stoller's avatar
Leigh B. Stoller committed
125 126 127 128 129 130 131 132
    #
    # Locally generated tickets need a local DB index, which can be the
    # same as the sequence number. A ticket from a remote component will
    # have it own seqno, and so we will generate a locally valid idx for
    # those when when(if) we store them in the DB.
    #
    $self->{'idx'}           = $seqno;
    
Leigh B. Stoller's avatar
Leigh B. Stoller committed
133 134 135 136 137 138
    bless($self, $class);

    return $self;
}
# accessors
sub field($$)           { return ($_[0]->{$_[1]}); }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
139
sub idx($)		{ return field($_[0], "idx"); }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
140
sub seqno($)		{ return field($_[0], "seqno"); }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
141
sub rspec($)		{ return field($_[0], "rspec"); }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
142
sub slice_uuid($)	{ return field($_[0], "slice_uuid"); }
143
sub target_uuid($)	{ return field($_[0], "slice_uuid"); }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
144
sub owner_uuid($)	{ return field($_[0], "owner_uuid"); }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
145 146
sub slice_cert($)	{ return field($_[0], "slice_cert"); }
sub owner_cert($)	{ return field($_[0], "owner_cert"); }
147
sub uuid($)		{ return field($_[0], "ticket_uuid"); }
148
sub ticket_uuid($)	{ return field($_[0], "ticket_uuid"); }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
149
sub ticket($)		{ return field($_[0], "ticket"); }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
150
sub asString($)		{ return field($_[0], "ticket_string"); }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
151
sub ticket_string($)	{ return field($_[0], "ticket_string"); }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
152
sub redeem_before($)	{ return field($_[0], "redeem_before"); }
153
sub component_uuid($)	{ return field($_[0], "component_uuid"); }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
154
sub component($)	{ return field($_[0], "component"); }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
155 156 157 158 159 160 161 162 163 164 165 166 167 168
sub stored($)		{ return field($_[0], "stored"); }

#
# Stringify for output.
#
sub Stringify($)
{
    my ($self) = @_;
    
    my $idx = $self->idx();
    if (!defined($idx)) {
	my $seqno = $self->seqno();
	$idx = "S$seqno";
    }
169 170 171
    my $slice_uuid = $self->slice_uuid();
    
    return "[GeniTicket: $idx, slice:$slice_uuid]";
Leigh B. Stoller's avatar
Leigh B. Stoller committed
172
}
Leigh B. Stoller's avatar
Leigh B. Stoller committed
173

Leigh B. Stoller's avatar
Leigh B. Stoller committed
174 175 176 177 178 179 180 181 182 183
#
# Flush from our little cache, as for the expire daemon.
#
sub Flush($)
{
    my ($self) = @_;

    delete($tickets{$self->idx()});
}

Leigh B. Stoller's avatar
Leigh B. Stoller committed
184 185 186
#
# Create a ticket object from a signed ticket string.
#
Leigh B. Stoller's avatar
Leigh B. Stoller committed
187
sub CreateFromSignedTicket($$;$$)
Leigh B. Stoller's avatar
Leigh B. Stoller committed
188
{
Leigh B. Stoller's avatar
Leigh B. Stoller committed
189
    my ($class, $ticket_string, $component, $nosig) = @_;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
190

Leigh B. Stoller's avatar
Leigh B. Stoller committed
191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210
    #
    # This flag is used to avoid verifying the signature since I do not
    # really care if the component gives me a bad ticket; I am not using
    # it locally, just passing it back to the component at some point.
    #
    $nosig = 0
	if (!defined($nosig));

    if (! $nosig) {
	my ($fh, $filename) = tempfile(UNLINK => 0);
	return undef
	    if (!defined($fh));
	print $fh $ticket_string;
	close($fh);
	system("$VERIFYCRED $filename");
	if ($?) {
	    print STDERR "Ticket in $filename did not verify\n";
	    return undef;
	}
	unlink($filename);
Leigh B. Stoller's avatar
Leigh B. Stoller committed
211 212
    }

Leigh B. Stoller's avatar
Leigh B. Stoller committed
213 214
    # Use XML::Simple to convert to something we can mess with.
    my $parser = XML::LibXML->new;
215 216 217 218 219 220 221 222
    my $doc;
    eval {
	$doc = $parser->parse_string($ticket_string);
    };
    if ($@) {
	print STDERR "Failed to parse ticket string: $@\n";
	return undef;
    }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
223 224 225 226 227

    # Dig out the rspec.
    my ($rspec_node) = $doc->getElementsByTagName("rspec");
    return undef
	if (!defined($rspec_node));
228 229
    my $rspec = XMLin($rspec_node->toString(), ForceArray => ["node",
							      "link"]);
Leigh B. Stoller's avatar
Leigh B. Stoller committed
230

231 232 233 234
    # Dig out the ticket uuid.
    my ($uuid_node) = $doc->getElementsByTagName("uuid");
    return undef
	if (!defined($uuid_node));
235
    my $ticket_uuid = $uuid_node->to_literal();
236 237 238 239 240 241

    if (! ($ticket_uuid =~ /^\w+\-\w+\-\w+\-\w+\-\w+$/)) {
	print STDERR "Invalid uuid in ticket\n";
	return undef;
    }

242 243
    # Dig out the target certificate.
    my ($cert_node) = $doc->getElementsByTagName("target_gid");
Leigh B. Stoller's avatar
Leigh B. Stoller committed
244
    return undef
245 246 247 248 249
	if (!defined($cert_node));
    my $target_certificate =
	GeniCertificate->LoadFromString($cert_node->to_literal());
    return undef
	if (!defined($target_certificate));
Leigh B. Stoller's avatar
Leigh B. Stoller committed
250

251 252 253 254 255 256
    if (!($target_certificate->uuid() =~ /^\w+\-\w+\-\w+\-\w+\-\w+$/)) {
	print STDERR "Invalid target_uuid in credential\n";
	return undef;
    }
    if (!($target_certificate->hrn() =~ /^[\w\.]+$/)) {
	print STDERR "Invalid hrn in credential\n";
Leigh B. Stoller's avatar
Leigh B. Stoller committed
257 258 259
	return undef;
    }

260 261
    # Dig out the owner certificate.
    ($cert_node) = $doc->getElementsByTagName("owner_gid");
Leigh B. Stoller's avatar
Leigh B. Stoller committed
262
    return undef
263 264 265 266 267 268
	if (!defined($cert_node));

    my $owner_certificate =
	GeniCertificate->LoadFromString($cert_node->to_literal());
    return undef
	if (!defined($owner_certificate));
Leigh B. Stoller's avatar
Leigh B. Stoller committed
269

270 271 272 273 274 275
    if (!($owner_certificate->uuid() =~ /^\w+\-\w+\-\w+\-\w+\-\w+$/)) {
	print STDERR "Invalid target_uuid in credential\n";
	return undef;
    }
    if (!($owner_certificate->hrn() =~ /^[\w\.]+$/)) {
	print STDERR "Invalid hrn in credential\n";
Leigh B. Stoller's avatar
Leigh B. Stoller committed
276 277 278
	return undef;
    }

279
    # Sequence number
Leigh B. Stoller's avatar
Leigh B. Stoller committed
280 281 282 283
    my ($seqno_node) = $doc->getElementsByTagName("serial");
    return undef
	if (!defined($seqno_node));
    my $seqno = $seqno_node->to_literal();
Leigh B. Stoller's avatar
Leigh B. Stoller committed
284

Leigh B. Stoller's avatar
Leigh B. Stoller committed
285 286 287
    if (! ($seqno =~ /^\w+$/)) {
	print STDERR "Invalid sequence number in ticket\n";
	return undef;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
288 289
    }

Leigh B. Stoller's avatar
Leigh B. Stoller committed
290
    my $self = {};
Leigh B. Stoller's avatar
Leigh B. Stoller committed
291
    $self->{'idx'}           = undef;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
292
    $self->{'rspec'}         = $rspec;
293
    $self->{'ticket_uuid'}   = $ticket_uuid;
294 295 296 297
    $self->{'slice_uuid'}    = $target_certificate->uuid();
    $self->{'owner_uuid'}    = $owner_certificate->uuid();
    $self->{'slice_cert'}    = $target_certificate;
    $self->{'owner_cert'}    = $owner_certificate;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
298 299
    $self->{'ticket_string'} = $ticket_string;
    $self->{'xmlref'}        = $doc;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
300
    $self->{'component'}     = $component;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
301 302 303 304 305 306 307 308 309 310 311 312 313 314
    $self->{'seqno'}         = $seqno;
    $self->{'stored'}        = 0;
    
    #
    # We save copies of the tickets we hand out; lets find that record
    # in the DB, just to verify.
    #
    if (! $nosig) {
	my $query_result =
	    DBQueryWarn("select * from geni_tickets where idx='$seqno'");
	if (!$query_result || !$query_result->numrows) {
	    print STDERR "Could not find the ticket $seqno in the DB\n";
	    return undef;
	}
Leigh B. Stoller's avatar
Leigh B. Stoller committed
315 316
	my $row = $query_result->fetchrow_hashref();
	$self->{'redeem_before'} = $row->{'redeem_before'};
Leigh B. Stoller's avatar
Leigh B. Stoller committed
317 318 319
	$self->{'idx'}    = $seqno;
	$self->{'stored'} = 1;
    }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
320 321 322 323
    bless($self, $class);

    return $self;
}
Leigh B. Stoller's avatar
Leigh B. Stoller committed
324

Leigh B. Stoller's avatar
Leigh B. Stoller committed
325
#
Leigh B. Stoller's avatar
Leigh B. Stoller committed
326 327
# Might have to delete this from the DB, as with an error handing out
# a ticket.
Leigh B. Stoller's avatar
Leigh B. Stoller committed
328 329 330 331 332 333 334 335
#
sub Delete($)
{
    my ($self) = @_;

    return -1
	if (! ref($self));

Leigh B. Stoller's avatar
Leigh B. Stoller committed
336
    if ($self->stored()) {
337 338
	my $idx  = $self->idx();
	my $uuid = $self->ticket_uuid();
Leigh B. Stoller's avatar
Leigh B. Stoller committed
339 340 341
	
	DBQueryWarn("delete from geni_tickets where idx='$idx'")
	    or return -1;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
342 343
	
	delete($tickets{"$idx"});
Leigh B. Stoller's avatar
Leigh B. Stoller committed
344 345 346 347
    }
    return 0;
}

348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364
#
# Return the rspec in XML for the ticket.
#
sub rspecXML($)
{
    my ($self) = @_;

    return undef
	if (! ref($self));
    return undef
	if (!defined($self->rspec()));

    my $rspec_xml = XMLout($self->rspec(), "NoAttr" => 1);
    $rspec_xml =~ s/opt\>/rspec\>/g;
    return $rspec_xml;
}

Leigh B. Stoller's avatar
Leigh B. Stoller committed
365 366 367 368 369 370 371 372
#
# Populate the ticket with some stuff, which right now is just the
# number of node we are willing to grant.
#
sub Grant($$)
{
    my ($self, $count) = @_;

Leigh B. Stoller's avatar
Leigh B. Stoller committed
373
    return -1
Leigh B. Stoller's avatar
Leigh B. Stoller committed
374 375 376 377 378 379
	if (! ref($self));

    $self->{'count'} = $count;
    return 0;
}

Leigh B. Stoller's avatar
Leigh B. Stoller committed
380
#
Leigh B. Stoller's avatar
Leigh B. Stoller committed
381 382 383
# Store the given ticket in the DB. We only do this for signed tickets,
# so we have a record of them. We store them on the server and the client
# side.
Leigh B. Stoller's avatar
Leigh B. Stoller committed
384
#
Leigh B. Stoller's avatar
Leigh B. Stoller committed
385
sub Store($)
Leigh B. Stoller's avatar
Leigh B. Stoller committed
386
{
Leigh B. Stoller's avatar
Leigh B. Stoller committed
387
    my ($self) = @_;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
388 389
    my @insert_data  = ();

Leigh B. Stoller's avatar
Leigh B. Stoller committed
390 391
    my $idx        = $self->idx();
    my $seqno      = $self->seqno();
Leigh B. Stoller's avatar
Leigh B. Stoller committed
392 393
    my $slice_uuid = $self->slice_uuid();
    my $owner_uuid = $self->owner_uuid();
394
    my $ticket_uuid= $self->ticket_uuid();
Leigh B. Stoller's avatar
Leigh B. Stoller committed
395
    my $expires    = $self->redeem_before();
Leigh B. Stoller's avatar
Leigh B. Stoller committed
396

Leigh B. Stoller's avatar
Leigh B. Stoller committed
397 398 399 400 401
    #
    # For a locally created/signed ticket, seqno=idx. For a ticket from
    # another component, we have to generate a locally unique idx for
    # the DB insertion.
    #
Leigh B. Stoller's avatar
Leigh B. Stoller committed
402 403 404 405
    if (!defined($idx)) {
	$idx = TBGetUniqueIndex('next_ticket', 1);
	$self->{'idx'} = $idx;
    }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
406
    # A locally generated ticket will not have a component. Might change that.
407
    push(@insert_data, "component_uuid='" . $self->component()->uuid() . "'")
Leigh B. Stoller's avatar
Leigh B. Stoller committed
408 409
	if (defined($self->component()));

Leigh B. Stoller's avatar
Leigh B. Stoller committed
410 411 412
    # Now tack on other stuff we need.
    push(@insert_data, "created=now()");
    push(@insert_data, "idx='$idx'");
Leigh B. Stoller's avatar
Leigh B. Stoller committed
413
    push(@insert_data, "seqno='$seqno'");
414
    push(@insert_data, "ticket_uuid='$ticket_uuid'");
Leigh B. Stoller's avatar
Leigh B. Stoller committed
415 416
    push(@insert_data, "slice_uuid='$slice_uuid'");
    push(@insert_data, "owner_uuid='$owner_uuid'");
Leigh B. Stoller's avatar
Leigh B. Stoller committed
417
    push(@insert_data, "redeem_before='$expires'");
Leigh B. Stoller's avatar
Leigh B. Stoller committed
418 419 420 421 422 423 424 425
    
    my $safe_ticket = DBQuoteSpecial($self->ticket_string());
    push(@insert_data, "ticket_string=$safe_ticket");

    # Insert into DB.
    DBQueryWarn("insert into geni_tickets set " . join(",", @insert_data))
	or return -1;

Leigh B. Stoller's avatar
Leigh B. Stoller committed
426 427
    $tickets{"$idx"}  = $self;
    $self->{'stored'} = 1;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
428 429 430
    return 0;
}

Leigh B. Stoller's avatar
Leigh B. Stoller committed
431 432
#
# Sign the ticket before returning it. We capture the output, which is
Leigh B. Stoller's avatar
Leigh B. Stoller committed
433
# in XML. 
Leigh B. Stoller's avatar
Leigh B. Stoller committed
434 435 436 437 438 439 440
#
sub Sign($)
{
    my ($self) = @_;

    return -1
	if (!ref($self));
Leigh B. Stoller's avatar
Leigh B. Stoller committed
441

Leigh B. Stoller's avatar
Leigh B. Stoller committed
442
    my $idx        = $self->seqno();
Leigh B. Stoller's avatar
Leigh B. Stoller committed
443
    my $expires    = $self->redeem_before();
444 445
    my $slice_cert = $self->slice_cert()->cert();
    my $owner_cert = $self->owner_cert()->cert();
Leigh B. Stoller's avatar
Leigh B. Stoller committed
446 447
    my $rspec_xml  = XMLout($self->rspec(), "NoAttr" => 1);
    $rspec_xml =~ s/opt\>/rspec\>/g;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
448

449
    #
450
    # Every ticket/credential gets its own uuid.
451
    #
452 453
    my $ticket_uuid = NewUUID();
    $self->{'ticket_uuid'} = $ticket_uuid;
454

Leigh B. Stoller's avatar
Leigh B. Stoller committed
455 456 457 458
    # Convert to GMT.
    $expires = POSIX::strftime("20%y-%m-%dT%H:%M:%S",
			       gmtime(str2time($expires)));

Leigh B. Stoller's avatar
Leigh B. Stoller committed
459 460 461 462 463
    #
    # Create a template xml file to sign.
    #
    my $template =
	"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n".
Leigh B. Stoller's avatar
Leigh B. Stoller committed
464
	"<credential xml:id=\"ref1\">\n".
Leigh B. Stoller's avatar
Leigh B. Stoller committed
465
	" <type>ticket</type>\n".
Leigh B. Stoller's avatar
Leigh B. Stoller committed
466
	" <serial>$idx</serial>\n".
467 468 469
	" <owner_gid>$owner_cert</owner_gid>\n".
	" <target_gid>$slice_cert</target_gid>\n".
	" <uuid>$ticket_uuid</uuid>\n".
Leigh B. Stoller's avatar
Leigh B. Stoller committed
470
	" <expires>$expires</expires>\n".
Leigh B. Stoller's avatar
Leigh B. Stoller committed
471 472
	" <ticket>\n".
	"  <can_delegate>1</can_delegate>\n".
Leigh B. Stoller's avatar
Leigh B. Stoller committed
473
	"  <redeem_before>$expires</redeem_before>\n".
Leigh B. Stoller's avatar
Leigh B. Stoller committed
474
	"  $rspec_xml\n".
Leigh B. Stoller's avatar
Leigh B. Stoller committed
475 476 477 478 479 480 481 482 483 484 485 486 487 488
	" </ticket>\n".	
        "</credential>\n";

    my ($fh, $filename) = tempfile(UNLINK => 0);
    return -1
	if (!defined($fh));

    print $fh $template;
    close($fh);

    #
    # Fire up the signer and capture the output. This is the signed ticket
    # that is returned. 
    #
Leigh B. Stoller's avatar
Leigh B. Stoller committed
489
    if (! open(SIGNER, "$SIGNCRED -c $CMCERT $filename |")) {
Leigh B. Stoller's avatar
Leigh B. Stoller committed
490 491 492 493 494 495 496
	print STDERR "Could not sign $filename\n";
	return -1;
    }
    my $ticket = "";
    while (<SIGNER>) {
	$ticket .= $_;
    }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
497 498 499 500
    if (!close(SIGNER)) {
	print STDERR "Could not sign $filename\n";
	return -1;
    }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
501
    $self->{'ticket_string'} = $ticket;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
502

Leigh B. Stoller's avatar
Leigh B. Stoller committed
503
    $self->Store() == 0
Leigh B. Stoller's avatar
Leigh B. Stoller committed
504 505
	or return -1;

Leigh B. Stoller's avatar
Leigh B. Stoller committed
506
    unlink($filename);
Leigh B. Stoller's avatar
Leigh B. Stoller committed
507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522
    return 0;
}

#
# Get the experiment for the slice this sliver belongs to.
#
sub GetExperiment($)
{
    my ($self) = @_;

    return undef
	if (! ref($self));

    return Experiment->Lookup($self->slice_uuid());
}

523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549
#
# Look up a list of tickets for a locally instantiated slice. 
# Used by the CM.
#
sub SliceTickets($$$)
{
    my ($class, $slice, $pref) = @_;

    my $slice_uuid = $slice->uuid();
    my @result = ();

    my $query_result =
	DBQueryWarn("select idx from geni_tickets ".
		    "where slice_uuid='$slice_uuid'");
    return -1
	if (!$query_result);

    while (my ($idx) = $query_result->fetchrow_array()) {
	my $ticket = GeniTicket->Lookup($idx);
	return -1
	    if (!defined($ticket));
	push(@result, $ticket);
    }
    @$pref = @result;
    return 0;
}

Leigh B. Stoller's avatar
Leigh B. Stoller committed
550 551
#
# Release a ticket. Need to release the nodes ...
552
# Used by the CM.
Leigh B. Stoller's avatar
Leigh B. Stoller committed
553 554 555 556 557 558 559 560 561
#
sub Release($)
{
    my ($self) = @_;

    return undef
	if (! ref($self));

    my $experiment = Experiment->Lookup($self->slice_uuid());
Leigh B. Stoller's avatar
Leigh B. Stoller committed
562 563 564
    my $pid     = $experiment->pid();
    my $eid     = $experiment->eid();
    my @nodeids = ();
Leigh B. Stoller's avatar
Leigh B. Stoller committed
565
    my @nodes   = ();
Leigh B. Stoller's avatar
Leigh B. Stoller committed
566

567 568
    foreach my $ref (@{$self->rspec()->{'node'}}) {
	my $resource_uuid = $ref->{'uuid'};
Leigh B. Stoller's avatar
Leigh B. Stoller committed
569 570 571 572 573
	my $node = Node->Lookup($resource_uuid);
	next
	    if (!defined($node));

	my $reservation = $node->Reservation();
Leigh B. Stoller's avatar
Leigh B. Stoller committed
574 575 576 577
	next
	    if (!defined($reservation));
	
	if ($reservation->SameExperiment($experiment)) {
Leigh B. Stoller's avatar
Leigh B. Stoller committed
578
	    push(@nodeids, $node->node_id());
Leigh B. Stoller's avatar
Leigh B. Stoller committed
579
	    push(@nodes, $node);
Leigh B. Stoller's avatar
Leigh B. Stoller committed
580 581 582
	}
    }
    if (@nodeids) {
Leigh B. Stoller's avatar
Leigh B. Stoller committed
583
	system("export NORELOAD=1; $NFREE -x -q $pid $eid @nodeids");
Leigh B. Stoller's avatar
Leigh B. Stoller committed
584
    }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
585 586 587
    foreach my $node (@nodes) {
	$node->Refresh();
    }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
588
    $self->Delete();
Leigh B. Stoller's avatar
Leigh B. Stoller committed
589 590 591
    return 0;
}

592 593 594 595 596 597 598 599 600 601 602 603 604 605
#
# Equality test for two tickets.
#
sub SameTicket($$)
{
    my ($self, $other) = @_;

    # Must be a real reference. 
    return 0
	if (! (ref($self) && ref($other)));

    return $self->idx() == $other->idx();
}

Leigh B. Stoller's avatar
Leigh B. Stoller committed
606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622
#
# Check if ticket has expired. Use the DB directly.
#
sub Expired($)
{
    my ($self) = @_;
    my $idx    = $self->idx();

    my $query_result =
	DBQueryWarn("select idx from geni_tickets ".
		    "where idx='$idx' and ".
		    "      (UNIX_TIMESTAMP(now()) > ".
		    "       UNIX_TIMESTAMP(redeem_before))");
    
    return $query_result->numrows;
}

Leigh B. Stoller's avatar
Leigh B. Stoller committed
623 624
# _Always_ make sure that this 1 is at the end of the file...
1;