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

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

@ISA    = "Exporter";
17
@EXPORT = qw (TICKET_PURGED TICKET_EXPIRED TICKET_REDEEMED TICKET_RELEASED);
Leigh B. Stoller's avatar
Leigh B. Stoller committed
18 19

use GeniDB;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
20
use GeniCredential;
21
use GeniCertificate;
22 23
use emutil qw(TBGetUniqueIndex);
use GeniUtil;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
24
use English;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
25 26
use XML::Simple;
use XML::LibXML;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
27
use Data::Dumper;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
28 29 30
use Date::Parse;
use POSIX qw(strftime);
use Time::Local;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
31
use File::Temp qw(tempfile);
Leigh B. Stoller's avatar
Leigh B. Stoller committed
32
use overload ('""' => 'Stringify');
Leigh B. Stoller's avatar
Leigh B. Stoller committed
33 34 35 36 37 38 39 40 41

# 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
42
my $VERIFYCRED	   = "$TB/sbin/verifygenicred";
Leigh B. Stoller's avatar
Leigh B. Stoller committed
43
my $NFREE	   = "$TB/bin/nfree";
Leigh B. Stoller's avatar
Leigh B. Stoller committed
44
my $CMCERT	   = "$TB/etc/genicm.pem";
Leigh B. Stoller's avatar
Leigh B. Stoller committed
45

46 47 48 49 50 51
# Ticket release flags
sub TICKET_PURGED()	{ return 1; }
sub TICKET_REDEEMED()	{ return 2; }
sub TICKET_EXPIRED()	{ return 3; }
sub TICKET_RELEASED()	{ return 4; }

Leigh B. Stoller's avatar
Leigh B. Stoller committed
52 53
# Cache of tickets.
my %tickets = ();
54 55 56 57 58 59 60 61
BEGIN { use GeniUtil; GeniUtil::AddCache(\%tickets); }

# Do not load this for the Clearinghouse XML server.
BEGIN { 
    if (! defined($main::GENI_ISCLRHOUSE)) {
	require Experiment;
    }
}
Leigh B. Stoller's avatar
Leigh B. Stoller committed
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84

#
# 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;
85 86
    if ($row->{'component_uuid'}) {
	$component = GeniComponent->Lookup($row->{'component_uuid'});
Leigh B. Stoller's avatar
Leigh B. Stoller committed
87 88 89 90 91 92 93 94
	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
95 96 97 98
    # 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'};

99 100 101 102 103
    # Mark as coming from the DB.
    $ticket->{'idx'}    = $idx;
    $ticket->{'stored'} = 1;

    # Cache.
Leigh B. Stoller's avatar
Leigh B. Stoller committed
104 105 106 107
    $tickets{"$idx"} = $ticket;
    return $ticket;
}

108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
#
# Lookup a ticket for a slice. This assumes only a single slice ticket.
#
sub LookupForSlice($$)
{
    my ($class, $slice) = @_;

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

    my $slice_uuid = $slice->uuid();
    my $query_result =
	DBQueryWarn("select idx from geni_tickets ".
		    "where slice_uuid='$slice_uuid'");
    return undef
	if (!$query_result || !$query_result->numrows);

    if ($query_result->numrows != 1) {
	print STDERR "Too many tickets stored for $slice\n";
	return undef;
    }
    my ($idx) = $query_result->fetchrow_array();
    return GeniTicket->Lookup($idx);
}

Leigh B. Stoller's avatar
Leigh B. Stoller committed
133
#
Leigh B. Stoller's avatar
Leigh B. Stoller committed
134
# Create an unsigned ticket object, to be populated and signed and returned.
Leigh B. Stoller's avatar
Leigh B. Stoller committed
135
#
Leigh B. Stoller's avatar
Leigh B. Stoller committed
136
sub Create($$$$)
Leigh B. Stoller's avatar
Leigh B. Stoller committed
137
{
Leigh B. Stoller's avatar
Leigh B. Stoller committed
138
    my ($class, $slice, $owner, $rspec) = @_;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
139

Leigh B. Stoller's avatar
Leigh B. Stoller committed
140 141 142
    # Every Ticket gets a new unique index (sequence number).
    my $seqno = TBGetUniqueIndex('next_ticket', 1);
    
Leigh B. Stoller's avatar
Leigh B. Stoller committed
143
    my $self = {};
Leigh B. Stoller's avatar
Leigh B. Stoller committed
144
    $self->{'rspec'}         = $rspec;
145
    $self->{'ticket_uuid'}   = undef;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
146 147
    $self->{'slice_uuid'}    = $slice->uuid();
    $self->{'owner_uuid'}    = $owner->uuid();
148 149
    $self->{'slice_hrn'}     = $slice->hrn();
    $self->{'owner_hrn'}     = $owner->hrn();
150 151
    $self->{'slice_cert'}    = $slice->GetCertificate();
    $self->{'owner_cert'}    = $owner->GetCertificate();
Leigh B. Stoller's avatar
Leigh B. Stoller committed
152
    $self->{'seqno'}         = $seqno;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
153 154
    $self->{'ticket_string'} = undef;
    $self->{'component'}     = undef;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
155 156
    $self->{'stored'}        = 0;	# Stored to the DB.

Leigh B. Stoller's avatar
Leigh B. Stoller committed
157 158 159 160
    #
    # For now, all tickets expire very quickly ...
    #
    $self->{'redeem_before'} =
161
	POSIX::strftime("20%y-%m-%dT%H:%M:%S", localtime(time() + (5*60)));
Leigh B. Stoller's avatar
Leigh B. Stoller committed
162

Leigh B. Stoller's avatar
Leigh B. Stoller committed
163 164 165 166 167 168 169 170
    #
    # 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
171 172 173 174 175 176
    bless($self, $class);

    return $self;
}
# accessors
sub field($$)           { return ($_[0]->{$_[1]}); }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
177
sub idx($)		{ return field($_[0], "idx"); }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
178
sub seqno($)		{ return field($_[0], "seqno"); }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
179
sub rspec($)		{ return field($_[0], "rspec"); }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
180
sub slice_uuid($)	{ return field($_[0], "slice_uuid"); }
181
sub target_uuid($)	{ return field($_[0], "slice_uuid"); }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
182
sub owner_uuid($)	{ return field($_[0], "owner_uuid"); }
183 184 185
sub slice_hrn($)	{ return field($_[0], "slice_hrn"); }
sub target_hrn($)	{ return field($_[0], "slice_hrn"); }
sub owner_hrn($)	{ return field($_[0], "owner_hrn"); }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
186 187
sub slice_cert($)	{ return field($_[0], "slice_cert"); }
sub owner_cert($)	{ return field($_[0], "owner_cert"); }
188
sub uuid($)		{ return field($_[0], "ticket_uuid"); }
189
sub ticket_uuid($)	{ return field($_[0], "ticket_uuid"); }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
190
sub ticket($)		{ return field($_[0], "ticket"); }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
191
sub asString($)		{ return field($_[0], "ticket_string"); }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
192
sub ticket_string($)	{ return field($_[0], "ticket_string"); }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
193
sub redeem_before($)	{ return field($_[0], "redeem_before"); }
194
sub component_uuid($)	{ return field($_[0], "component_uuid"); }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
195
sub component($)	{ return field($_[0], "component"); }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
196 197 198 199 200 201 202 203 204 205 206 207 208 209
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";
    }
210 211 212
    my $slice_uuid = $self->slice_uuid();
    
    return "[GeniTicket: $idx, slice:$slice_uuid]";
Leigh B. Stoller's avatar
Leigh B. Stoller committed
213
}
Leigh B. Stoller's avatar
Leigh B. Stoller committed
214

Leigh B. Stoller's avatar
Leigh B. Stoller committed
215 216 217 218 219 220 221 222 223 224
#
# 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
225 226 227
#
# Create a ticket object from a signed ticket string.
#
Leigh B. Stoller's avatar
Leigh B. Stoller committed
228
sub CreateFromSignedTicket($$;$$)
Leigh B. Stoller's avatar
Leigh B. Stoller committed
229
{
Leigh B. Stoller's avatar
Leigh B. Stoller committed
230
    my ($class, $ticket_string, $component, $nosig) = @_;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
231

Leigh B. Stoller's avatar
Leigh B. Stoller committed
232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251
    #
    # 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
252 253
    }

Leigh B. Stoller's avatar
Leigh B. Stoller committed
254 255
    # Use XML::Simple to convert to something we can mess with.
    my $parser = XML::LibXML->new;
256 257 258 259 260 261 262 263
    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
264 265 266 267 268

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

272 273 274 275
    # Dig out the ticket uuid.
    my ($uuid_node) = $doc->getElementsByTagName("uuid");
    return undef
	if (!defined($uuid_node));
276
    my $ticket_uuid = $uuid_node->to_literal();
277 278 279 280 281 282

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

283 284
    # Dig out the target certificate.
    my ($cert_node) = $doc->getElementsByTagName("target_gid");
Leigh B. Stoller's avatar
Leigh B. Stoller committed
285
    return undef
286 287 288 289 290
	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
291

292 293 294 295 296 297
    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
298 299 300
	return undef;
    }

301 302
    # Dig out the owner certificate.
    ($cert_node) = $doc->getElementsByTagName("owner_gid");
Leigh B. Stoller's avatar
Leigh B. Stoller committed
303
    return undef
304 305 306 307 308 309
	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
310

311 312 313 314 315 316
    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
317 318 319
	return undef;
    }

320
    # Sequence number
Leigh B. Stoller's avatar
Leigh B. Stoller committed
321 322 323 324
    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
325

Leigh B. Stoller's avatar
Leigh B. Stoller committed
326 327 328
    if (! ($seqno =~ /^\w+$/)) {
	print STDERR "Invalid sequence number in ticket\n";
	return undef;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
329 330
    }

Leigh B. Stoller's avatar
Leigh B. Stoller committed
331
    my $self = {};
Leigh B. Stoller's avatar
Leigh B. Stoller committed
332
    $self->{'idx'}           = undef;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
333
    $self->{'rspec'}         = $rspec;
334
    $self->{'ticket_uuid'}   = $ticket_uuid;
335 336
    $self->{'slice_uuid'}    = $target_certificate->uuid();
    $self->{'owner_uuid'}    = $owner_certificate->uuid();
337 338
    $self->{'slice_hrn'}     = $target_certificate->hrn();
    $self->{'owner_hrn'}     = $owner_certificate->hrn();
339 340
    $self->{'slice_cert'}    = $target_certificate;
    $self->{'owner_cert'}    = $owner_certificate;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
341 342
    $self->{'ticket_string'} = $ticket_string;
    $self->{'xmlref'}        = $doc;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
343
    $self->{'component'}     = $component;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
344 345 346 347 348 349 350 351 352 353 354 355 356 357
    $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
358 359
	my $row = $query_result->fetchrow_hashref();
	$self->{'redeem_before'} = $row->{'redeem_before'};
Leigh B. Stoller's avatar
Leigh B. Stoller committed
360 361 362
	$self->{'idx'}    = $seqno;
	$self->{'stored'} = 1;
    }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
363 364 365 366
    bless($self, $class);

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

Leigh B. Stoller's avatar
Leigh B. Stoller committed
368
#
Leigh B. Stoller's avatar
Leigh B. Stoller committed
369 370
# 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
371
#
372
sub Delete($$)
Leigh B. Stoller's avatar
Leigh B. Stoller committed
373
{
374
    my ($self, $flag) = @_;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
375 376 377 378

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

Leigh B. Stoller's avatar
Leigh B. Stoller committed
379
    if ($self->stored()) {
380 381
	my $idx  = $self->idx();
	my $uuid = $self->ticket_uuid();
Leigh B. Stoller's avatar
Leigh B. Stoller committed
382 383 384
	
	DBQueryWarn("delete from geni_tickets where idx='$idx'")
	    or return -1;
385

386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406
	if ($flag == TICKET_PURGED) {
	    GeniUsage->DeleteTicket($self) == 0 
		or print STDERR "GeniTicket::Delete: ".
		    "GeniUsage->DeleteTicket($self) failed\n";
	}
	elsif ($flag == TICKET_REDEEMED) {
	    GeniUsage->RedeemTicket($self) == 0 
		or print STDERR "GeniTicket::Delete: ".
		    "GeniUsage->RedeemTicket($self) failed\n";
	}
	elsif ($flag == TICKET_RELEASED) {
	    GeniUsage->ReleaseTicket($self) == 0 
		or print STDERR "GeniTicket::Delete: ".
		    "GeniUsage->ReleaseTicket($self) failed\n";
	}
	elsif ($flag == TICKET_EXPIRED) {
	    GeniUsage->ExpireTicket($self) == 0 
		or print STDERR "GeniTicket::Delete: ".
		    "GeniUsage->ExpireTicket($self) failed\n";
	}

Leigh B. Stoller's avatar
Leigh B. Stoller committed
407
	delete($tickets{"$idx"});
Leigh B. Stoller's avatar
Leigh B. Stoller committed
408 409 410 411
    }
    return 0;
}

412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428
#
# 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
429 430 431 432 433 434 435 436
#
# 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
437
    return -1
Leigh B. Stoller's avatar
Leigh B. Stoller committed
438 439 440 441 442 443
	if (! ref($self));

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

Leigh B. Stoller's avatar
Leigh B. Stoller committed
444
#
Leigh B. Stoller's avatar
Leigh B. Stoller committed
445 446 447
# 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
448
#
Leigh B. Stoller's avatar
Leigh B. Stoller committed
449
sub Store($)
Leigh B. Stoller's avatar
Leigh B. Stoller committed
450
{
Leigh B. Stoller's avatar
Leigh B. Stoller committed
451
    my ($self) = @_;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
452 453
    my @insert_data  = ();

Leigh B. Stoller's avatar
Leigh B. Stoller committed
454 455
    my $idx        = $self->idx();
    my $seqno      = $self->seqno();
Leigh B. Stoller's avatar
Leigh B. Stoller committed
456 457
    my $slice_uuid = $self->slice_uuid();
    my $owner_uuid = $self->owner_uuid();
458
    my $ticket_uuid= $self->ticket_uuid();
Leigh B. Stoller's avatar
Leigh B. Stoller committed
459
    my $expires    = $self->redeem_before();
Leigh B. Stoller's avatar
Leigh B. Stoller committed
460

Leigh B. Stoller's avatar
Leigh B. Stoller committed
461 462 463 464 465
    #
    # 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
466 467 468 469
    if (!defined($idx)) {
	$idx = TBGetUniqueIndex('next_ticket', 1);
	$self->{'idx'} = $idx;
    }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
470
    # A locally generated ticket will not have a component. Might change that.
471
    push(@insert_data, "component_uuid='" . $self->component()->uuid() . "'")
Leigh B. Stoller's avatar
Leigh B. Stoller committed
472 473
	if (defined($self->component()));

Leigh B. Stoller's avatar
Leigh B. Stoller committed
474 475 476
    # 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
477
    push(@insert_data, "seqno='$seqno'");
478
    push(@insert_data, "ticket_uuid='$ticket_uuid'");
Leigh B. Stoller's avatar
Leigh B. Stoller committed
479 480
    push(@insert_data, "slice_uuid='$slice_uuid'");
    push(@insert_data, "owner_uuid='$owner_uuid'");
Leigh B. Stoller's avatar
Leigh B. Stoller committed
481
    push(@insert_data, "redeem_before='$expires'");
Leigh B. Stoller's avatar
Leigh B. Stoller committed
482 483 484 485 486 487 488 489
    
    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;

490 491 492 493 494
    if (GeniUsage->NewTicket($self)) {
	print STDERR
	    "GeniTicket::Store: GeniUsage->NewTicket($self) failed\n";
    }

Leigh B. Stoller's avatar
Leigh B. Stoller committed
495 496
    $tickets{"$idx"}  = $self;
    $self->{'stored'} = 1;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
497 498 499
    return 0;
}

Leigh B. Stoller's avatar
Leigh B. Stoller committed
500 501
#
# Sign the ticket before returning it. We capture the output, which is
Leigh B. Stoller's avatar
Leigh B. Stoller committed
502
# in XML. 
Leigh B. Stoller's avatar
Leigh B. Stoller committed
503 504 505 506 507 508 509
#
sub Sign($)
{
    my ($self) = @_;

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

Leigh B. Stoller's avatar
Leigh B. Stoller committed
511
    my $idx        = $self->seqno();
Leigh B. Stoller's avatar
Leigh B. Stoller committed
512
    my $expires    = $self->redeem_before();
513 514
    my $slice_cert = $self->slice_cert()->cert();
    my $owner_cert = $self->owner_cert()->cert();
Leigh B. Stoller's avatar
Leigh B. Stoller committed
515 516
    my $rspec_xml  = XMLout($self->rspec(), "NoAttr" => 1);
    $rspec_xml =~ s/opt\>/rspec\>/g;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
517

518
    #
519
    # Every ticket/credential gets its own uuid.
520
    #
521 522
    my $ticket_uuid = NewUUID();
    $self->{'ticket_uuid'} = $ticket_uuid;
523

Leigh B. Stoller's avatar
Leigh B. Stoller committed
524 525 526 527
    # 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
528 529 530 531 532
    #
    # 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
533
	"<credential xml:id=\"ref1\">\n".
Leigh B. Stoller's avatar
Leigh B. Stoller committed
534
	" <type>ticket</type>\n".
Leigh B. Stoller's avatar
Leigh B. Stoller committed
535
	" <serial>$idx</serial>\n".
536 537 538
	" <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
539
	" <expires>$expires</expires>\n".
Leigh B. Stoller's avatar
Leigh B. Stoller committed
540 541
	" <ticket>\n".
	"  <can_delegate>1</can_delegate>\n".
Leigh B. Stoller's avatar
Leigh B. Stoller committed
542
	"  <redeem_before>$expires</redeem_before>\n".
Leigh B. Stoller's avatar
Leigh B. Stoller committed
543
	"  $rspec_xml\n".
Leigh B. Stoller's avatar
Leigh B. Stoller committed
544 545 546 547 548 549 550 551 552 553 554 555 556 557
	" </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
558
    if (! open(SIGNER, "$SIGNCRED -c $CMCERT $filename |")) {
Leigh B. Stoller's avatar
Leigh B. Stoller committed
559 560 561 562 563 564 565
	print STDERR "Could not sign $filename\n";
	return -1;
    }
    my $ticket = "";
    while (<SIGNER>) {
	$ticket .= $_;
    }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
566 567 568 569
    if (!close(SIGNER)) {
	print STDERR "Could not sign $filename\n";
	return -1;
    }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
570
    $self->{'ticket_string'} = $ticket;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
571

Leigh B. Stoller's avatar
Leigh B. Stoller committed
572
    $self->Store() == 0
Leigh B. Stoller's avatar
Leigh B. Stoller committed
573 574
	or return -1;

Leigh B. Stoller's avatar
Leigh B. Stoller committed
575
    unlink($filename);
Leigh B. Stoller's avatar
Leigh B. Stoller committed
576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591
    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());
}

592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618
#
# 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
619 620
#
# Release a ticket. Need to release the nodes ...
621
# Used by the CM.
Leigh B. Stoller's avatar
Leigh B. Stoller committed
622
#
623
sub Release($$)
Leigh B. Stoller's avatar
Leigh B. Stoller committed
624
{
625
    my ($self, $flag) = @_;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
626

627
    return -1
Leigh B. Stoller's avatar
Leigh B. Stoller committed
628 629 630
	if (! ref($self));

    my $experiment = Experiment->Lookup($self->slice_uuid());
631 632 633 634
    if (!defined($experiment)) {
	#
	# Cannot be any nodes if no experiment.
	#
635
	return $self->Delete($flag);
636
    }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
637 638 639
    my $pid     = $experiment->pid();
    my $eid     = $experiment->eid();
    my @nodeids = ();
Leigh B. Stoller's avatar
Leigh B. Stoller committed
640
    my @nodes   = ();
Leigh B. Stoller's avatar
Leigh B. Stoller committed
641

642 643
    foreach my $ref (@{$self->rspec()->{'node'}}) {
	my $resource_uuid = $ref->{'uuid'};
Leigh B. Stoller's avatar
Leigh B. Stoller committed
644 645 646 647 648
	my $node = Node->Lookup($resource_uuid);
	next
	    if (!defined($node));

	my $reservation = $node->Reservation();
Leigh B. Stoller's avatar
Leigh B. Stoller committed
649 650
	next
	    if (!defined($reservation));
651 652 653 654 655

	# Watch for duplicates, as in multiple vnodes on a pnode.
	next
	    if (grep {$_ eq $node->node_id()} @nodeids);
	    
Leigh B. Stoller's avatar
Leigh B. Stoller committed
656
	if ($reservation->SameExperiment($experiment)) {
Leigh B. Stoller's avatar
Leigh B. Stoller committed
657
	    push(@nodeids, $node->node_id());
Leigh B. Stoller's avatar
Leigh B. Stoller committed
658
	    push(@nodes, $node);
Leigh B. Stoller's avatar
Leigh B. Stoller committed
659 660 661
	}
    }
    if (@nodeids) {
Leigh B. Stoller's avatar
Leigh B. Stoller committed
662
	system("export NORELOAD=1; $NFREE -x -q $pid $eid @nodeids");
Leigh B. Stoller's avatar
Leigh B. Stoller committed
663
    }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
664 665 666
    foreach my $node (@nodes) {
	$node->Refresh();
    }
667
    $self->Delete($flag);
Leigh B. Stoller's avatar
Leigh B. Stoller committed
668 669 670
    return 0;
}

671 672 673 674 675 676 677 678 679 680 681 682 683 684
#
# 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
685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701
#
# 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
702 703
# _Always_ make sure that this 1 is at the end of the file...
1;