GeniTicket.pm.in 19.4 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 18
@EXPORT = qw (TICKET_PURGED TICKET_EXPIRED TICKET_REDEEMED
	      TICKET_RELEASED TICKET_DELETED);
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

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

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

74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
    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
89 90 91
    return $tickets{"$idx"}
        if (exists($tickets{"$idx"}));

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

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

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

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

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

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

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

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

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

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

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

Leigh B. Stoller's avatar
Leigh B. Stoller committed
226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245
    #
    # 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
246 247
    }

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

    # Dig out the rspec.
    my ($rspec_node) = $doc->getElementsByTagName("rspec");
261 262 263 264 265 266 267 268
    if (!defined($rspec_node)) {
	print STDERR "Ticket is missing rspec node\n";
	return undef;
    }
    
    my $rspec = XMLin($rspec_node->toString(), KeyAttr => [],
		      ForceArray => ["node", "link", "interface",
				     "interface_ref", "linkendpoints"]);
Leigh B. Stoller's avatar
Leigh B. Stoller committed
269

270 271
    # Dig out the ticket uuid.
    my ($uuid_node) = $doc->getElementsByTagName("uuid");
272 273 274 275
    if (!defined($uuid_node)) {
	print STDERR "Ticket is missing uuid node\n";
	return undef;
    }
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");
285 286 287 288
    if (!defined($cert_node)) {
	print STDERR "Ticket is missing target gid node\n";
	return undef;
    }
289 290
    my $target_certificate =
	GeniCertificate->LoadFromString($cert_node->to_literal());
291 292 293 294
    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
295

296 297 298 299 300 301
    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
302 303 304
	return undef;
    }

305 306
    # Dig out the owner certificate.
    ($cert_node) = $doc->getElementsByTagName("owner_gid");
307 308 309 310
    if (!defined($cert_node)) {
	print STDERR "Ticket is missing owner gid node\n";
	return undef;
    }
311 312
    my $owner_certificate =
	GeniCertificate->LoadFromString($cert_node->to_literal());
313 314 315 316
    if (!defined($target_certificate)) {
	print STDERR "Could not get owner certificate from string\n";
	return undef;
    }
317 318 319 320 321 322
    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
323 324 325
	return undef;
    }

326
    # Sequence number
Leigh B. Stoller's avatar
Leigh B. Stoller committed
327
    my ($seqno_node) = $doc->getElementsByTagName("serial");
328 329 330 331
    if (!defined($seqno_node)) {
	print STDERR "Ticket is missing seqno node\n";
	return undef;
    }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
332
    my $seqno = $seqno_node->to_literal();
Leigh B. Stoller's avatar
Leigh B. Stoller committed
333

Leigh B. Stoller's avatar
Leigh B. Stoller committed
334 335 336
    if (! ($seqno =~ /^\w+$/)) {
	print STDERR "Invalid sequence number in ticket\n";
	return undef;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
337 338
    }

Leigh B. Stoller's avatar
Leigh B. Stoller committed
339
    my $self = {};
Leigh B. Stoller's avatar
Leigh B. Stoller committed
340
    $self->{'idx'}           = undef;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
341
    $self->{'rspec'}         = $rspec;
342
    $self->{'ticket_uuid'}   = $ticket_uuid;
343
    $self->{'target_uuid'}   = $target_certificate->uuid();
344
    $self->{'owner_uuid'}    = $owner_certificate->uuid();
345
    $self->{'target_hrn'}    = $target_certificate->hrn();
346
    $self->{'owner_hrn'}     = $owner_certificate->hrn();
347
    $self->{'target_cert'}   = $target_certificate;
348
    $self->{'owner_cert'}    = $owner_certificate;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
349 350
    $self->{'ticket_string'} = $ticket_string;
    $self->{'xmlref'}        = $doc;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
351
    $self->{'component'}     = $component;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
352 353
    $self->{'seqno'}         = $seqno;
    $self->{'stored'}        = 0;
354
    $self->{'LOCKED'}        = 0;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
355 356
    
    #
357 358
    # 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
359 360 361 362
    #
    if (! $nosig) {
	my $query_result =
	    DBQueryWarn("select * from geni_tickets where idx='$seqno'");
363 364 365 366 367 368

	if ($query_result && $query_result->numrows) {
	    my $row = $query_result->fetchrow_hashref();
	    $self->{'redeem_before'} = $row->{'redeem_before'};
	    $self->{'idx'}    = $seqno;
	    $self->{'stored'} = 1;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
369 370
	}
    }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
371 372 373 374
    bless($self, $class);

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

Leigh B. Stoller's avatar
Leigh B. Stoller committed
376
#
Leigh B. Stoller's avatar
Leigh B. Stoller committed
377 378
# 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
379
#
380
sub Delete($$)
Leigh B. Stoller's avatar
Leigh B. Stoller committed
381
{
382
    my ($self, $flag) = @_;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
383 384 385 386

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

Leigh B. Stoller's avatar
Leigh B. Stoller committed
387
    if ($self->stored()) {
388 389
	my $idx  = $self->idx();
	my $uuid = $self->ticket_uuid();
Leigh B. Stoller's avatar
Leigh B. Stoller committed
390 391 392
	
	DBQueryWarn("delete from geni_tickets where idx='$idx'")
	    or return -1;
393

394 395 396 397 398 399 400 401 402 403 404 405 406 407 408
	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";
	}
409 410 411 412 413 414 415 416 417
	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
418
	delete($tickets{"$idx"});
Leigh B. Stoller's avatar
Leigh B. Stoller committed
419 420 421 422
    }
    return 0;
}

423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472
#
# 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;
}

473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489
#
# 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
490 491 492 493 494 495 496 497
#
# 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
498
    return -1
Leigh B. Stoller's avatar
Leigh B. Stoller committed
499 500 501 502 503 504
	if (! ref($self));

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

Leigh B. Stoller's avatar
Leigh B. Stoller committed
505
#
Leigh B. Stoller's avatar
Leigh B. Stoller committed
506 507 508
# 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
509
#
Leigh B. Stoller's avatar
Leigh B. Stoller committed
510
sub Store($)
Leigh B. Stoller's avatar
Leigh B. Stoller committed
511
{
Leigh B. Stoller's avatar
Leigh B. Stoller committed
512
    my ($self) = @_;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
513 514
    my @insert_data  = ();

515 516 517 518 519 520
    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();
    my $expires     = $self->redeem_before();
Leigh B. Stoller's avatar
Leigh B. Stoller committed
521

Leigh B. Stoller's avatar
Leigh B. Stoller committed
522 523 524 525 526
    #
    # 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
527 528 529 530
    if (!defined($idx)) {
	$idx = TBGetUniqueIndex('next_ticket', 1);
	$self->{'idx'} = $idx;
    }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
531
    # A locally generated ticket will not have a component. Might change that.
532
    push(@insert_data, "component_uuid='" . $self->component()->uuid() . "'")
Leigh B. Stoller's avatar
Leigh B. Stoller committed
533 534
	if (defined($self->component()));

Leigh B. Stoller's avatar
Leigh B. Stoller committed
535 536 537
    # 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
538
    push(@insert_data, "seqno='$seqno'");
539
    push(@insert_data, "ticket_uuid='$ticket_uuid'");
540
    push(@insert_data, "target_uuid='$target_uuid'");
Leigh B. Stoller's avatar
Leigh B. Stoller committed
541
    push(@insert_data, "owner_uuid='$owner_uuid'");
Leigh B. Stoller's avatar
Leigh B. Stoller committed
542
    push(@insert_data, "redeem_before='$expires'");
Leigh B. Stoller's avatar
Leigh B. Stoller committed
543 544 545 546 547 548 549 550
    
    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;

551 552 553 554 555
    if (GeniUsage->NewTicket($self)) {
	print STDERR
	    "GeniTicket::Store: GeniUsage->NewTicket($self) failed\n";
    }

Leigh B. Stoller's avatar
Leigh B. Stoller committed
556 557
    $tickets{"$idx"}  = $self;
    $self->{'stored'} = 1;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
558 559 560
    return 0;
}

Leigh B. Stoller's avatar
Leigh B. Stoller committed
561
#
562
# When we redeem a ticket, we update the history file and delete it.
Leigh B. Stoller's avatar
Leigh B. Stoller committed
563
#
564
sub Redeem($)
Leigh B. Stoller's avatar
Leigh B. Stoller committed
565 566 567 568 569
{
    my ($self) = @_;

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

571 572 573 574 575 576 577 578 579 580 581 582
    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
583

584
    #
585
    # Every ticket/credential gets its own uuid.
586
    #
587
    my $ticket_uuid = NewUUID();
588 589 590 591 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
    $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)) {
	$rspec_xml = XMLout($rspec_xml, "NoAttr" => 1);
	$rspec_xml =~ s/opt\>/rspec\>/g;
    }
619

Leigh B. Stoller's avatar
Leigh B. Stoller committed
620 621 622 623
    # 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
624 625 626 627 628
    #
    # 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
629
	"<credential xml:id=\"ref1\">\n".
Leigh B. Stoller's avatar
Leigh B. Stoller committed
630
	" <type>ticket</type>\n".
Leigh B. Stoller's avatar
Leigh B. Stoller committed
631
	" <serial>$idx</serial>\n".
632
	" <owner_gid>$owner_cert</owner_gid>\n".
633
	" <target_gid>$target_cert</target_gid>\n".
634
	" <uuid>$ticket_uuid</uuid>\n".
Leigh B. Stoller's avatar
Leigh B. Stoller committed
635
	" <expires>$expires</expires>\n".
Leigh B. Stoller's avatar
Leigh B. Stoller committed
636 637
	" <ticket>\n".
	"  <can_delegate>1</can_delegate>\n".
Leigh B. Stoller's avatar
Leigh B. Stoller committed
638
	"  <redeem_before>$expires</redeem_before>\n".
Leigh B. Stoller's avatar
Leigh B. Stoller committed
639
	"  $rspec_xml\n".
Leigh B. Stoller's avatar
Leigh B. Stoller committed
640 641 642 643 644 645 646 647 648 649 650 651 652 653
	" </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
654
    if (! open(SIGNER, "$SIGNCRED -c $CMCERT $filename |")) {
Leigh B. Stoller's avatar
Leigh B. Stoller committed
655 656 657 658 659 660 661
	print STDERR "Could not sign $filename\n";
	return -1;
    }
    my $ticket = "";
    while (<SIGNER>) {
	$ticket .= $_;
    }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
662 663 664 665
    if (!close(SIGNER)) {
	print STDERR "Could not sign $filename\n";
	return -1;
    }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
666
    $self->{'ticket_string'} = $ticket;
667 668 669
    return 0;
}

Leigh B. Stoller's avatar
Leigh B. Stoller committed
670 671
#
# Release a ticket. Need to release the nodes ...
672
# Used by the CM.
Leigh B. Stoller's avatar
Leigh B. Stoller committed
673
#
674
sub Release($$)
Leigh B. Stoller's avatar
Leigh B. Stoller committed
675
{
676
    my ($self, $flag) = @_;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
677

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

681
    my $experiment = Experiment->Lookup("GeniSlices", "reservations");
682 683
    if (!defined($experiment)) {
	#
684
	# This experiment has to exist!
685
	#
686 687
	print STDERR "Could not find Geni reservations experiment!";
	return -1;
688
    }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
689 690 691
    my $pid     = $experiment->pid();
    my $eid     = $experiment->eid();
    my @nodeids = ();
Leigh B. Stoller's avatar
Leigh B. Stoller committed
692
    my @nodes   = ();
Leigh B. Stoller's avatar
Leigh B. Stoller committed
693

694
    foreach my $ref (@{$self->rspec()->{'node'}}) {
695
	my $resource_uuid = $ref->{'component_uuid'} || $ref->{'uuid'};
Leigh B. Stoller's avatar
Leigh B. Stoller committed
696 697 698 699 700
	my $node = Node->Lookup($resource_uuid);
	next
	    if (!defined($node));

	my $reservation = $node->Reservation();
Leigh B. Stoller's avatar
Leigh B. Stoller committed
701 702
	next
	    if (!defined($reservation));
703 704 705 706

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

	#
	# 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
712
	if ($reservation->SameExperiment($experiment)) {
Leigh B. Stoller's avatar
Leigh B. Stoller committed
713
	    push(@nodeids, $node->node_id());
Leigh B. Stoller's avatar
Leigh B. Stoller committed
714
	    push(@nodes, $node);
Leigh B. Stoller's avatar
Leigh B. Stoller committed
715 716 717
	}
    }
    if (@nodeids) {
Leigh B. Stoller's avatar
Leigh B. Stoller committed
718
	system("export NORELOAD=1; $NFREE -x -q $pid $eid @nodeids");
Leigh B. Stoller's avatar
Leigh B. Stoller committed
719
    }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
720 721 722
    foreach my $node (@nodes) {
	$node->Refresh();
    }
723
    $self->Delete($flag);
Leigh B. Stoller's avatar
Leigh B. Stoller committed
724 725 726
    return 0;
}

727 728 729 730 731 732 733 734 735 736 737 738 739 740
#
# 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
741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757
#
# 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;
}

758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782
#
# 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
783 784
# _Always_ make sure that this 1 is at the end of the file...
1;