GeniTicket.pm.in 23.8 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
18
	      TICKET_RELEASED TICKET_DELETED TICKET_NOSTATS);
Leigh B. Stoller's avatar
Leigh B. Stoller committed
19 20

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

# 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
43
my $VERIFYCRED	   = "$TB/sbin/verifygenicred";
Leigh B. Stoller's avatar
Leigh B. Stoller committed
44
my $NFREE	   = "$TB/bin/nfree";
Leigh B. Stoller's avatar
Leigh B. Stoller committed
45
my $CMCERT	   = "$TB/etc/genicm.pem";
Leigh B. Stoller's avatar
Leigh B. Stoller committed
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; }
52
sub TICKET_DELETED()	{ return 5; }
53
sub TICKET_NOSTATS()	{ return 0x1; }
54

Leigh B. Stoller's avatar
Leigh B. Stoller committed
55 56
# Cache of tickets.
my %tickets = ();
57 58 59 60 61 62 63 64
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
65 66 67 68 69 70

#
# Lookup by local idx.
#
sub Lookup($$)
{
71 72 73
    my ($class, $token) = @_;
    my $idx;
    my $query_result;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
74

75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
    if ($token =~ /^\d+$/) {
	$idx = $token;
    }
    elsif ($token =~ /^\w+\-\w+\-\w+\-\w+\-\w+$/) {
	$query_result =
	    DBQueryWarn("select idx from geni_tickets ".
			"where ticket_uuid='$token'");
	    return undef
		if (! $query_result || !$query_result->numrows);

	    ($idx) = $query_result->fetchrow_array();
    }
    else {
	return undef;
    }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
90 91 92
    return $tickets{"$idx"}
        if (exists($tickets{"$idx"}));

93
    $query_result =
Leigh B. Stoller's avatar
Leigh B. Stoller committed
94 95 96 97 98 99 100 101 102
	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;
103 104
    if ($row->{'component_uuid'}) {
	$component = GeniComponent->Lookup($row->{'component_uuid'});
Leigh B. Stoller's avatar
Leigh B. Stoller committed
105 106 107 108 109 110 111 112
	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
113 114 115 116
    # 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'};

117 118 119
    # Mark as coming from the DB.
    $ticket->{'idx'}    = $idx;
    $ticket->{'stored'} = 1;
120
    $ticket->{'LOCKED'}   = 0;
121 122

    # Cache.
Leigh B. Stoller's avatar
Leigh B. Stoller committed
123 124 125 126
    $tickets{"$idx"} = $ticket;
    return $ticket;
}

Leigh B. Stoller's avatar
Leigh B. Stoller committed
127
#
Leigh B. Stoller's avatar
Leigh B. Stoller committed
128
# Create an unsigned ticket object, to be populated and signed and returned.
Leigh B. Stoller's avatar
Leigh B. Stoller committed
129
#
Leigh B. Stoller's avatar
Leigh B. Stoller committed
130
sub Create($$$$)
Leigh B. Stoller's avatar
Leigh B. Stoller committed
131
{
132
    my ($class, $target, $owner, $rspec) = @_;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
133

Leigh B. Stoller's avatar
Leigh B. Stoller committed
134 135
    # Every Ticket gets a new unique index (sequence number).
    my $seqno = TBGetUniqueIndex('next_ticket', 1);
136

Leigh B. Stoller's avatar
Leigh B. Stoller committed
137
    my $self = {};
Leigh B. Stoller's avatar
Leigh B. Stoller committed
138
    $self->{'rspec'}         = $rspec;
139
    $self->{'ticket_uuid'}   = undef;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
140
    $self->{'owner_uuid'}    = $owner->uuid();
141
    $self->{'owner_hrn'}     = $owner->hrn();
142
    $self->{'owner_cert'}    = $owner->GetCertificate();
143 144 145
    $self->{'target_uuid'}   = $target->uuid();
    $self->{'target_hrn'}    = $target->hrn();
    $self->{'target_cert'}   = $target->GetCertificate();
Leigh B. Stoller's avatar
Leigh B. Stoller committed
146
    $self->{'seqno'}         = $seqno;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
147 148
    $self->{'ticket_string'} = undef;
    $self->{'component'}     = undef;
149
    $self->{'slice_uuid'}    = undef;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
150
    $self->{'stored'}        = 0;	# Stored to the DB.
151
    $self->{'LOCKED'}        = 0;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
152

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

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

    return $self;
}
# accessors
sub field($$)           { return ($_[0]->{$_[1]}); }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
173
sub idx($)		{ return field($_[0], "idx"); }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
174
sub seqno($)		{ return field($_[0], "seqno"); }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
175
sub rspec($)		{ return field($_[0], "rspec"); }
176
sub target_uuid($)	{ return field($_[0], "target_uuid"); }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
177
sub owner_uuid($)	{ return field($_[0], "owner_uuid"); }
178
sub target_hrn($)	{ return field($_[0], "target_hrn"); }
179
sub owner_hrn($)	{ return field($_[0], "owner_hrn"); }
180
sub target_cert($)	{ return field($_[0], "target_cert"); }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
181
sub owner_cert($)	{ return field($_[0], "owner_cert"); }
182
sub uuid($)		{ return field($_[0], "ticket_uuid"); }
183
sub ticket_uuid($)	{ return field($_[0], "ticket_uuid"); }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
184
sub ticket($)		{ return field($_[0], "ticket"); }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
185
sub asString($)		{ return field($_[0], "ticket_string"); }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
186
sub ticket_string($)	{ return field($_[0], "ticket_string"); }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
187
sub redeem_before($)	{ return field($_[0], "redeem_before"); }
188
sub expires($)	        { return field($_[0], "expires"); }
189
sub redeemed($   )	{ return field($_[0], "redeemed"); }
190
sub component_uuid($)	{ return field($_[0], "component_uuid"); }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
191
sub component($)	{ return field($_[0], "component"); }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
192
sub stored($)		{ return field($_[0], "stored"); }
193
sub slice_uuid($)	{ return field($_[0], "slice_uuid"); }
194
sub LOCKED($)           { return $_[0]->{'LOCKED'}; }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
195 196 197 198 199 200 201 202 203 204 205 206 207

#
# Stringify for output.
#
sub Stringify($)
{
    my ($self) = @_;
    
    my $idx = $self->idx();
    if (!defined($idx)) {
	my $seqno = $self->seqno();
	$idx = "S$seqno";
    }
208 209
    my $owner_hrn   = $self->owner_hrn();
    my $target_uuid = $self->target_uuid();
210
    
211
    return "[GeniTicket: $idx, owner:$owner_hrn, target_uuid:$target_uuid]";
Leigh B. Stoller's avatar
Leigh B. Stoller committed
212
}
Leigh B. Stoller's avatar
Leigh B. Stoller committed
213

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

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

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

    # Dig out the rspec.
    my ($rspec_node) = $doc->getElementsByTagName("rspec");
266 267 268 269 270
    if (!defined($rspec_node)) {
	print STDERR "Ticket is missing rspec node\n";
	return undef;
    }
    
271 272 273 274 275 276 277 278
    my $rspec =
	eval { XMLin($rspec_node->toString(), KeyAttr => [],
		     ForceArray => ["node", "link", "interface",
				    "interface_ref", "linkendpoints"]) };
    if ($@) {
	print STDERR "XMLin error on ticket rspec: $@\n";
	return undef;
    }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
279

280 281
    # Dig out the ticket uuid.
    my ($uuid_node) = $doc->getElementsByTagName("uuid");
282 283 284 285
    if (!defined($uuid_node)) {
	print STDERR "Ticket is missing uuid node\n";
	return undef;
    }
286
    my $ticket_uuid = $uuid_node->to_literal();
287 288 289 290 291 292

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

293 294
    # Dig out the target certificate.
    my ($cert_node) = $doc->getElementsByTagName("target_gid");
295 296 297 298
    if (!defined($cert_node)) {
	print STDERR "Ticket is missing target gid node\n";
	return undef;
    }
299 300
    my $target_certificate =
	GeniCertificate->LoadFromString($cert_node->to_literal());
301 302 303 304
    if (!defined($target_certificate)) {
	print STDERR "Could not get target certificate from string\n";
	return undef;
    }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
305

306 307 308 309 310 311
    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
312 313 314
	return undef;
    }

315 316
    # Dig out the owner certificate.
    ($cert_node) = $doc->getElementsByTagName("owner_gid");
317 318 319 320
    if (!defined($cert_node)) {
	print STDERR "Ticket is missing owner gid node\n";
	return undef;
    }
321 322
    my $owner_certificate =
	GeniCertificate->LoadFromString($cert_node->to_literal());
323 324 325 326
    if (!defined($target_certificate)) {
	print STDERR "Could not get owner certificate from string\n";
	return undef;
    }
327 328 329 330 331 332
    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
333 334 335
	return undef;
    }

336
    # Sequence number
Leigh B. Stoller's avatar
Leigh B. Stoller committed
337
    my ($seqno_node) = $doc->getElementsByTagName("serial");
338 339 340 341
    if (!defined($seqno_node)) {
	print STDERR "Ticket is missing seqno node\n";
	return undef;
    }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
342
    my $seqno = $seqno_node->to_literal();
Leigh B. Stoller's avatar
Leigh B. Stoller committed
343

Leigh B. Stoller's avatar
Leigh B. Stoller committed
344 345 346
    if (! ($seqno =~ /^\w+$/)) {
	print STDERR "Invalid sequence number in ticket\n";
	return undef;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
347 348
    }

349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367
    # Expiration
    my ($expires_node) = $doc->getElementsByTagName("expires");
    if (!defined($expires_node)) {
	print STDERR "Ticket is missing expires node\n";
	return undef;
    }
    my $expires = $expires_node->to_literal();

    if (! ($expires =~ /^[-\w:.\/]+/)) {
	print STDERR "Invalid expires date in ticket\n";
	return undef;
    }
    # Convert to a localtime.
    my $when = timegm(strptime($expires));
    if (!defined($when)) {
	print STDERR "Could not parse expires: '$expires'\n";
	return undef;
    }
    
Leigh B. Stoller's avatar
Leigh B. Stoller committed
368
    my $self = {};
Leigh B. Stoller's avatar
Leigh B. Stoller committed
369
    $self->{'idx'}           = undef;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
370
    $self->{'rspec'}         = $rspec;
371
    $self->{'ticket_uuid'}   = $ticket_uuid;
372
    $self->{'target_uuid'}   = $target_certificate->uuid();
373
    $self->{'owner_uuid'}    = $owner_certificate->uuid();
374
    $self->{'target_hrn'}    = $target_certificate->hrn();
375
    $self->{'owner_hrn'}     = $owner_certificate->hrn();
376
    $self->{'target_cert'}   = $target_certificate;
377
    $self->{'owner_cert'}    = $owner_certificate;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
378 379
    $self->{'ticket_string'} = $ticket_string;
    $self->{'xmlref'}        = $doc;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
380
    $self->{'component'}     = $component;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
381
    $self->{'seqno'}         = $seqno;
382
    $self->{'expires'}       = $expires;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
383
    $self->{'stored'}        = 0;
384
    $self->{'slice_uuid'}    = undef;
385
    $self->{'LOCKED'}        = 0;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
386 387
    
    #
388 389
    # We save copies of the tickets we hand out, but delete them
    # when redeemed. If we still have it, mark it.
Leigh B. Stoller's avatar
Leigh B. Stoller committed
390
    #
Leigh B. Stoller's avatar
Leigh B. Stoller committed
391 392 393 394 395 396 397 398 399
    my $query_result =
	DBQueryWarn("select * from geni_tickets where idx='$seqno'");

    if ($query_result && $query_result->numrows) {
	my $row = $query_result->fetchrow_hashref();
	$self->{'redeem_before'} = $row->{'redeem_before'};
	$self->{'slice_uuid'}    = $row->{'slice_uuid'};
	$self->{'idx'}    = $seqno;
	$self->{'stored'} = 1;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
400
    }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
401 402 403 404
    bless($self, $class);

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

Leigh B. Stoller's avatar
Leigh B. Stoller committed
406
#
Leigh B. Stoller's avatar
Leigh B. Stoller committed
407 408
# 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
409
#
410
sub Delete($$)
Leigh B. Stoller's avatar
Leigh B. Stoller committed
411
{
412
    my ($self, $flag) = @_;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
413 414 415 416

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

Leigh B. Stoller's avatar
Leigh B. Stoller committed
417
    if ($self->stored()) {
418 419
	my $idx  = $self->idx();
	my $uuid = $self->ticket_uuid();
Leigh B. Stoller's avatar
Leigh B. Stoller committed
420 421 422
	
	DBQueryWarn("delete from geni_tickets where idx='$idx'")
	    or return -1;
423

424 425 426 427 428 429 430 431 432 433 434 435 436 437 438
	if ($flag == TICKET_PURGED) {
	    GeniUsage->DeleteTicket($self) == 0 
		or print STDERR "GeniTicket::Delete: ".
		    "GeniUsage->DeleteTicket($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";
	}
439 440 441 442 443 444 445 446 447
	elsif ($flag == TICKET_REDEEMED) {
	    GeniUsage->RedeemTicket($self) == 0 
		or print STDERR "GeniTicket::Delete: ".
		"GeniUsage->RedeemTicket($self) failed\n";
	}
	elsif ($flag == TICKET_DELETED) {
	    # Do nothing for this. Just removing from the tickets table,
	    # but want to leave it in the history.
	}
Leigh B. Stoller's avatar
Leigh B. Stoller committed
448
	delete($tickets{"$idx"});
Leigh B. Stoller's avatar
Leigh B. Stoller committed
449 450 451 452
    }
    return 0;
}

453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502
#
# We lock at a very coarse grain, mostly in the CM. 
#
sub Lock($)
{
    my ($self) = @_;
    my $idx    = $self->idx();

    # We already have it locked.
    return 0
	if ($self->LOCKED());

    # Not in the DB, so does not matter.
    return 0
	if (!$self->stored());

    DBQueryWarn("lock tables geni_tickets write")
	or return -1;

    my $query_result =
	DBQueryWarn("select locked from geni_tickets ".
		    "where idx='$idx' and locked is null");
    if (!$query_result || !$query_result->numrows) {
	DBQueryWarn("unlock tables");
	return 1;
    }
    $query_result =
	DBQueryWarn("update geni_tickets set locked=now() where idx='$idx'");
    DBQueryWarn("unlock tables");

    return 1
	if (!$query_result);
    $self->{'LOCKED'} = $$;
    return 0;
}
sub UnLock($)
{
    my ($self) = @_;
    my $idx    = $self->idx();

    return 1
	if (!$self->LOCKED());

    DBQueryWarn("update geni_tickets set locked=NULL where idx='$idx'")
	or return -1;
    
    $self->{'LOCKED'} = 0;
    return 0;
}

503 504 505 506 507 508 509 510
sub SetSlice($$)
{
    my ($self, $slice_uuid) = @_;
    
    $self->{'slice_uuid'} = $slice_uuid;
    return 0;
}

511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528
sub SliceTicket($$)
{
    my ($class, $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);
    return undef
	if ($query_result->numrows != 1);

    my ($idx) = $query_result->fetchrow_array();
    return GeniTicket->Lookup($idx);
}

529 530 531 532 533 534 535 536 537 538 539 540
#
# Return the rspec in XML for the ticket.
#
sub rspecXML($)
{
    my ($self) = @_;

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

541 542 543 544 545 546 547
    my $rspec_xml =
	eval { XMLout($self->rspec(),
		      "NoAttr" => 1, RootName => "rspec") };
    if ($@) {
	print STDERR "XMLout error on rspec: $@\n";
	return undef;
    }
548 549 550
    return $rspec_xml;
}

Leigh B. Stoller's avatar
Leigh B. Stoller committed
551 552 553 554 555 556 557 558
#
# 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
559
    return -1
Leigh B. Stoller's avatar
Leigh B. Stoller committed
560 561 562 563 564 565
	if (! ref($self));

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

Leigh B. Stoller's avatar
Leigh B. Stoller committed
566
#
Leigh B. Stoller's avatar
Leigh B. Stoller committed
567 568 569
# 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
570
#
571
sub Store($;$)
Leigh B. Stoller's avatar
Leigh B. Stoller committed
572
{
573
    my ($self, $flags) = @_;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
574 575
    my @insert_data  = ();

576 577 578
    $flags = 0
	if (!defined($flags));

579 580 581 582 583
    my $idx         = $self->idx();
    my $seqno       = $self->seqno();
    my $target_uuid = $self->target_uuid();
    my $owner_uuid  = $self->owner_uuid();
    my $ticket_uuid = $self->ticket_uuid();
584 585
    my $expires     = $self->redeem_before() || $self->expires();
    my $slice_uuid  = $self->slice_uuid();
Leigh B. Stoller's avatar
Leigh B. Stoller committed
586

Leigh B. Stoller's avatar
Leigh B. Stoller committed
587 588 589 590 591
    #
    # 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
592 593 594 595
    if (!defined($idx)) {
	$idx = TBGetUniqueIndex('next_ticket', 1);
	$self->{'idx'} = $idx;
    }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
596
    # A locally generated ticket will not have a component. Might change that.
597
    push(@insert_data, "component_uuid='" . $self->component()->uuid() . "'")
Leigh B. Stoller's avatar
Leigh B. Stoller committed
598 599
	if (defined($self->component()));

Leigh B. Stoller's avatar
Leigh B. Stoller committed
600 601 602
    # 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
603
    push(@insert_data, "seqno='$seqno'");
604
    push(@insert_data, "ticket_uuid='$ticket_uuid'");
605
    push(@insert_data, "target_uuid='$target_uuid'");
Leigh B. Stoller's avatar
Leigh B. Stoller committed
606
    push(@insert_data, "owner_uuid='$owner_uuid'");
607
    push(@insert_data, "slice_uuid='$slice_uuid'");
Leigh B. Stoller's avatar
Leigh B. Stoller committed
608
    push(@insert_data, "redeem_before='$expires'");
Leigh B. Stoller's avatar
Leigh B. Stoller committed
609 610 611 612 613 614 615 616
    
    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;

617 618 619 620 621
    if (! ($flags & TICKET_NOSTATS)) {
	if (GeniUsage->NewTicket($self)) {
	    print STDERR
		"GeniTicket::Store: GeniUsage->NewTicket($self) failed\n";
	}
622 623
    }

Leigh B. Stoller's avatar
Leigh B. Stoller committed
624 625
    $tickets{"$idx"}  = $self;
    $self->{'stored'} = 1;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
626 627 628
    return 0;
}

Leigh B. Stoller's avatar
Leigh B. Stoller committed
629
#
630
# When we redeem a ticket, we update the history file and delete it.
Leigh B. Stoller's avatar
Leigh B. Stoller committed
631
#
632
sub Redeem($)
Leigh B. Stoller's avatar
Leigh B. Stoller committed
633 634 635 636 637
{
    my ($self) = @_;

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

639 640 641 642 643 644 645 646 647 648 649 650
    return $self->Delete(TICKET_REDEEMED);
}

#
# Sign the ticket before returning it.
#
sub Sign($)
{
    my ($self) = @_;

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

652
    #
653
    # Every ticket/credential gets its own uuid.
654
    #
655
    my $ticket_uuid = NewUUID();
656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683
    $self->{'ticket_uuid'} = GeniUtil::NewUUID();

    $self->RunSigner() == 0
	or return -1;

    return 0;
}

#
# Sign the ticket before returning it. We capture the output, which is
# in XML. 
#
sub RunSigner($$)
{
    my ($self) = @_;

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

    my $idx         = $self->seqno();
    my $expires     = $self->redeem_before();
    my $target_cert = $self->target_cert()->cert();
    my $owner_cert  = $self->owner_cert()->cert();
    my $ticket_uuid = $self->{'ticket_uuid'};
    my $rspec_xml   = $self->rspec();

    # Allow for the rspec to be in XML already.
    if (ref($rspec_xml)) {
684 685 686 687 688 689 690
	$rspec_xml =
	    eval { XMLout($rspec_xml,
			  "NoAttr" => 1, RootName => "rspec") };
	if ($@) {
	    print STDERR "XMLout error on ticket rspec: $@\n";
	    return -1;
	}
691
    }
692

Leigh B. Stoller's avatar
Leigh B. Stoller committed
693 694 695 696
    # 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
697 698 699 700 701
    #
    # 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
702
	"<credential xml:id=\"ref1\">\n".
Leigh B. Stoller's avatar
Leigh B. Stoller committed
703
	" <type>ticket</type>\n".
Leigh B. Stoller's avatar
Leigh B. Stoller committed
704
	" <serial>$idx</serial>\n".
705
	" <owner_gid>$owner_cert</owner_gid>\n".
706
	" <target_gid>$target_cert</target_gid>\n".
707
	" <uuid>$ticket_uuid</uuid>\n".
Leigh B. Stoller's avatar
Leigh B. Stoller committed
708
	" <expires>$expires</expires>\n".
Leigh B. Stoller's avatar
Leigh B. Stoller committed
709 710
	" <ticket>\n".
	"  <can_delegate>1</can_delegate>\n".
Leigh B. Stoller's avatar
Leigh B. Stoller committed
711
	"  <redeem_before>$expires</redeem_before>\n".
Leigh B. Stoller's avatar
Leigh B. Stoller committed
712
	"  $rspec_xml\n".
Leigh B. Stoller's avatar
Leigh B. Stoller committed
713 714 715 716 717 718 719 720 721 722 723 724 725 726
	" </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
727
    if (! open(SIGNER, "$SIGNCRED -c $CMCERT $filename |")) {
Leigh B. Stoller's avatar
Leigh B. Stoller committed
728 729 730 731 732 733 734
	print STDERR "Could not sign $filename\n";
	return -1;
    }
    my $ticket = "";
    while (<SIGNER>) {
	$ticket .= $_;
    }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
735 736 737 738
    if (!close(SIGNER)) {
	print STDERR "Could not sign $filename\n";
	return -1;
    }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
739
    $self->{'ticket_string'} = $ticket;
740 741 742
    return 0;
}

Leigh B. Stoller's avatar
Leigh B. Stoller committed
743 744
#
# Release a ticket. Need to release the nodes ...
745
# Used by the CM.
Leigh B. Stoller's avatar
Leigh B. Stoller committed
746
#
747
sub Release($$)
Leigh B. Stoller's avatar
Leigh B. Stoller committed
748
{
749
    my ($self, $flag) = @_;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
750

751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771
    return -1
	if (! ref($self));

    $self->ReleaseHolding($flag) == 0
	or return -1;

    # Older tickets do not have this, but handled in ReleaseHolding.
    return 0
	if (!defined($self->slice_uuid()) || $self->slice_uuid() eq "");

    my $experiment = Experiment->Lookup($self->slice_uuid());
    if (!defined($experiment)) {
	$self->Delete($flag);
	return 0;
    }
    my $pid     = $experiment->pid();
    my $eid     = $experiment->eid();
    my @nodeids = ();
    my @nodes   = ();

    foreach my $ref (@{$self->rspec()->{'node'}}) {
772 773 774 775 776 777
	# Skip lan nodes; they are fake.
	next
	    if (exists($ref->{'node_type'}) &&
		exists($ref->{'node_type'}->{'type_name'}) &&
		$ref->{'node_type'}->{'type_name'} eq "lan");
	
778 779 780 781 782 783 784
	# Skip remote nodes.
	my $manager_uuid  = $ref->{'component_manager_uuid'};
	next
	    if (defined($manager_uuid) &&
		!GeniHRN::Equal($manager_uuid, $ENV{'MYURN'}) &&
		$manager_uuid ne $ENV{'MYUUID'});
	
785
	my $resource_uuid = $ref->{'component_uuid'} || $ref->{'uuid'};
786 787 788 789 790
	if (!defined($resource_uuid)) {
	    print STDERR "No resource id for node in ticket\n";
	    print Dumper($ref);
	    return -1;
	}
791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834

	# Virtual nodes not created until ticket redeemed.
	my $node = Node->Lookup($resource_uuid);
	next
	    if (!defined($node));

	my $reservation = $node->Reservation();
	next
	    if (!defined($reservation));

	# Watch for duplicates, as in multiple vnodes on a pnode.
	next
	    if (grep {$_ eq $node->node_id()} @nodeids);

	#
	# If the node is still in the experiment and not incorporated,
	# release it. genisliver_idx is not defined until ticket redeemed.
	#
	if ($reservation->SameExperiment($experiment)) {
	    my $restable = $node->ReservedTableEntry();
	    if (defined($restable) &&
		!defined($restable->{'genisliver_idx'})) {
		push(@nodeids, $node->node_id());
		push(@nodes, $node);
	    }
	}
    }
    if (@nodeids) {
	system("export NORELOAD=1; $NFREE -x -q $pid $eid @nodeids");
    }
    foreach my $node (@nodes) {
	$node->Refresh();
    }
    $self->Delete($flag);
    return 0;
}

#
# Retained for backwards compatability.
#
sub ReleaseHolding($$)
{
    my ($self, $flag) = @_;

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

838
    my $experiment = Experiment->Lookup("GeniSlices", "reservations");
839 840
    if (!defined($experiment)) {
	#
841
	print STDERR "Could not find Geni reservations experiment!";
842
	return 0;
843
    }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
844 845 846
    my $pid     = $experiment->pid();
    my $eid     = $experiment->eid();
    my @nodeids = ();
Leigh B. Stoller's avatar
Leigh B. Stoller committed
847
    my @nodes   = ();
Leigh B. Stoller's avatar
Leigh B. Stoller committed
848

849
    foreach my $ref (@{$self->rspec()->{'node'}}) {
850 851 852 853 854 855
	# Skip lan nodes; they are fake.
	next
	    if (exists($ref->{'node_type'}) &&
		exists($ref->{'node_type'}->{'type_name'}) &&
		$ref->{'node_type'}->{'type_name'} eq "lan");
	
856 857 858 859 860 861 862
	# Skip remote nodes.
	my $manager_uuid  = $ref->{'component_manager_uuid'};
	next
	    if (defined($manager_uuid) &&
		!GeniHRN::Equal($manager_uuid, $ENV{'MYURN'}) &&
		$manager_uuid ne $ENV{'MYUUID'});
	
863
	my $resource_uuid = $ref->{'component_uuid'} || $ref->{'uuid'};
864 865 866 867 868
	if (!defined($resource_uuid)) {
	    print STDERR "No resource id for node in ticket\n";
	    print Dumper($ref);
	    return -1;
	}
Leigh B. Stoller's avatar
Leigh B. Stoller committed
869 870 871 872 873
	my $node = Node->Lookup($resource_uuid);
	next
	    if (!defined($node));

	my $reservation = $node->Reservation();
Leigh B. Stoller's avatar
Leigh B. Stoller committed
874 875
	next
	    if (!defined($reservation));
876 877 878 879

	# Watch for duplicates, as in multiple vnodes on a pnode.
	next
	    if (grep {$_ eq $node->node_id()} @nodeids);
880 881 882 883 884

	#
	# If the node is still in the reservations experiment, it
	# is by definition, not in use by a slice. 
	#
Leigh B. Stoller's avatar
Leigh B. Stoller committed
885
	if ($reservation->SameExperiment($experiment)) {
Leigh B. Stoller's avatar
Leigh B. Stoller committed
886
	    push(@nodeids, $node->node_id());
Leigh B. Stoller's avatar
Leigh B. Stoller committed
887
	    push(@nodes, $node);
Leigh B. Stoller's avatar
Leigh B. Stoller committed
888 889 890
	}
    }
    if (@nodeids) {
Leigh B. Stoller's avatar
Leigh B. Stoller committed
891
	system("export NORELOAD=1; $NFREE -x -q $pid $eid @nodeids");
Leigh B. Stoller's avatar
Leigh B. Stoller committed
892
    }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
893 894 895
    foreach my $node (@nodes) {
	$node->Refresh();
    }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
896 897 898
    return 0;
}

899 900 901 902 903 904 905 906 907 908 909 910 911 912
#
# 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
913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929
#
# 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;
}

930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954
#
# List all tickets for a user.
#
sub ListUserTickets($$$)
{
    my ($class, $user, $pref) = @_;
    my @result = ();

    my $user_uuid = $user->uuid();
    my $query_result = DBQueryWarn("select idx from geni_tickets ".
				   "where owner_uuid='$user_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
955 956
# _Always_ make sure that this 1 is at the end of the file...
1;