GeniTicket.pm.in 17.3 KB
Newer Older
Leigh B. Stoller's avatar
Leigh B. Stoller committed
1 2 3
#!/usr/bin/perl -wT
#
# EMULAB-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 20 21

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

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

49 50 51 52 53 54
# 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
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
# Cache of tickets.
my %tickets = ();

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

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

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

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

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

    # Map the component
    my $component;
80 81
    if ($row->{'component_uuid'}) {
	$component = GeniComponent->Lookup($row->{'component_uuid'});
Leigh B. Stoller's avatar
Leigh B. Stoller committed
82 83 84 85 86 87 88 89
	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
90 91 92 93
    # 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'};

94 95 96 97 98
    # Mark as coming from the DB.
    $ticket->{'idx'}    = $idx;
    $ticket->{'stored'} = 1;

    # Cache.
Leigh B. Stoller's avatar
Leigh B. Stoller committed
99 100 101 102
    $tickets{"$idx"} = $ticket;
    return $ticket;
}

103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127
#
# 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
128
#
Leigh B. Stoller's avatar
Leigh B. Stoller committed
129
# Create an unsigned ticket object, to be populated and signed and returned.
Leigh B. Stoller's avatar
Leigh B. Stoller committed
130
#
Leigh B. Stoller's avatar
Leigh B. Stoller committed
131
sub Create($$$$)
Leigh B. Stoller's avatar
Leigh B. Stoller committed
132
{
Leigh B. Stoller's avatar
Leigh B. Stoller committed
133
    my ($class, $slice, $owner, $rspec) = @_;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
134

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

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

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

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

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

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

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

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

267 268 269 270
    # Dig out the ticket uuid.
    my ($uuid_node) = $doc->getElementsByTagName("uuid");
    return undef
	if (!defined($uuid_node));
271
    my $ticket_uuid = $uuid_node->to_literal();
272 273 274 275 276 277

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

278 279
    # Dig out the target certificate.
    my ($cert_node) = $doc->getElementsByTagName("target_gid");
Leigh B. Stoller's avatar
Leigh B. Stoller committed
280
    return undef
281 282 283 284 285
	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
286

287 288 289 290 291 292
    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
293 294 295
	return undef;
    }

296 297
    # Dig out the owner certificate.
    ($cert_node) = $doc->getElementsByTagName("owner_gid");
Leigh B. Stoller's avatar
Leigh B. Stoller committed
298
    return undef
299 300 301 302 303 304
	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
305

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

315
    # Sequence number
Leigh B. Stoller's avatar
Leigh B. Stoller committed
316 317 318 319
    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
320

Leigh B. Stoller's avatar
Leigh B. Stoller committed
321 322 323
    if (! ($seqno =~ /^\w+$/)) {
	print STDERR "Invalid sequence number in ticket\n";
	return undef;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
324 325
    }

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

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

Leigh B. Stoller's avatar
Leigh B. Stoller committed
363
#
Leigh B. Stoller's avatar
Leigh B. Stoller committed
364 365
# 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
366
#
367
sub Delete($$)
Leigh B. Stoller's avatar
Leigh B. Stoller committed
368
{
369
    my ($self, $flag) = @_;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
370 371 372 373

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

Leigh B. Stoller's avatar
Leigh B. Stoller committed
374
    if ($self->stored()) {
375 376
	my $idx  = $self->idx();
	my $uuid = $self->ticket_uuid();
Leigh B. Stoller's avatar
Leigh B. Stoller committed
377 378 379
	
	DBQueryWarn("delete from geni_tickets where idx='$idx'")
	    or return -1;
380

381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401
	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
402
	delete($tickets{"$idx"});
Leigh B. Stoller's avatar
Leigh B. Stoller committed
403 404 405 406
    }
    return 0;
}

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

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

Leigh B. Stoller's avatar
Leigh B. Stoller committed
439
#
Leigh B. Stoller's avatar
Leigh B. Stoller committed
440 441 442
# 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
443
#
Leigh B. Stoller's avatar
Leigh B. Stoller committed
444
sub Store($)
Leigh B. Stoller's avatar
Leigh B. Stoller committed
445
{
Leigh B. Stoller's avatar
Leigh B. Stoller committed
446
    my ($self) = @_;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
447 448
    my @insert_data  = ();

Leigh B. Stoller's avatar
Leigh B. Stoller committed
449 450
    my $idx        = $self->idx();
    my $seqno      = $self->seqno();
Leigh B. Stoller's avatar
Leigh B. Stoller committed
451 452
    my $slice_uuid = $self->slice_uuid();
    my $owner_uuid = $self->owner_uuid();
453
    my $ticket_uuid= $self->ticket_uuid();
Leigh B. Stoller's avatar
Leigh B. Stoller committed
454
    my $expires    = $self->redeem_before();
Leigh B. Stoller's avatar
Leigh B. Stoller committed
455

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

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

485 486 487 488 489
    if (GeniUsage->NewTicket($self)) {
	print STDERR
	    "GeniTicket::Store: GeniUsage->NewTicket($self) failed\n";
    }

Leigh B. Stoller's avatar
Leigh B. Stoller committed
490 491
    $tickets{"$idx"}  = $self;
    $self->{'stored'} = 1;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
492 493 494
    return 0;
}

Leigh B. Stoller's avatar
Leigh B. Stoller committed
495 496
#
# Sign the ticket before returning it. We capture the output, which is
Leigh B. Stoller's avatar
Leigh B. Stoller committed
497
# in XML. 
Leigh B. Stoller's avatar
Leigh B. Stoller committed
498 499 500 501 502 503 504
#
sub Sign($)
{
    my ($self) = @_;

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

Leigh B. Stoller's avatar
Leigh B. Stoller committed
506
    my $idx        = $self->seqno();
Leigh B. Stoller's avatar
Leigh B. Stoller committed
507
    my $expires    = $self->redeem_before();
508 509
    my $slice_cert = $self->slice_cert()->cert();
    my $owner_cert = $self->owner_cert()->cert();
Leigh B. Stoller's avatar
Leigh B. Stoller committed
510 511
    my $rspec_xml  = XMLout($self->rspec(), "NoAttr" => 1);
    $rspec_xml =~ s/opt\>/rspec\>/g;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
512

513
    #
514
    # Every ticket/credential gets its own uuid.
515
    #
516 517
    my $ticket_uuid = NewUUID();
    $self->{'ticket_uuid'} = $ticket_uuid;
518

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

Leigh B. Stoller's avatar
Leigh B. Stoller committed
567
    $self->Store() == 0
Leigh B. Stoller's avatar
Leigh B. Stoller committed
568 569
	or return -1;

Leigh B. Stoller's avatar
Leigh B. Stoller committed
570
    unlink($filename);
Leigh B. Stoller's avatar
Leigh B. Stoller committed
571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586
    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());
}

587 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
#
# 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
614 615
#
# Release a ticket. Need to release the nodes ...
616
# Used by the CM.
Leigh B. Stoller's avatar
Leigh B. Stoller committed
617
#
618
sub Release($$)
Leigh B. Stoller's avatar
Leigh B. Stoller committed
619
{
620
    my ($self, $flag) = @_;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
621

622
    return -1
Leigh B. Stoller's avatar
Leigh B. Stoller committed
623 624 625
	if (! ref($self));

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

637 638
    foreach my $ref (@{$self->rspec()->{'node'}}) {
	my $resource_uuid = $ref->{'uuid'};
Leigh B. Stoller's avatar
Leigh B. Stoller committed
639 640 641 642 643
	my $node = Node->Lookup($resource_uuid);
	next
	    if (!defined($node));

	my $reservation = $node->Reservation();
Leigh B. Stoller's avatar
Leigh B. Stoller committed
644 645 646 647
	next
	    if (!defined($reservation));
	
	if ($reservation->SameExperiment($experiment)) {
Leigh B. Stoller's avatar
Leigh B. Stoller committed
648
	    push(@nodeids, $node->node_id());
Leigh B. Stoller's avatar
Leigh B. Stoller committed
649
	    push(@nodes, $node);
Leigh B. Stoller's avatar
Leigh B. Stoller committed
650 651 652
	}
    }
    if (@nodeids) {
Leigh B. Stoller's avatar
Leigh B. Stoller committed
653
	system("export NORELOAD=1; $NFREE -x -q $pid $eid @nodeids");
Leigh B. Stoller's avatar
Leigh B. Stoller committed
654
    }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
655 656 657
    foreach my $node (@nodes) {
	$node->Refresh();
    }
658
    $self->Delete($flag);
Leigh B. Stoller's avatar
Leigh B. Stoller committed
659 660 661
    return 0;
}

662 663 664 665 666 667 668 669 670 671 672 673 674 675
#
# 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
676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692
#
# 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
693 694
# _Always_ make sure that this 1 is at the end of the file...
1;