GeniTicket.pm.in 15.9 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 17 18 19 20 21
# All rights reserved.
#
package GeniTicket;

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

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

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

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

Leigh B. Stoller's avatar
Leigh B. Stoller committed
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
# Cache of tickets.
my %tickets = ();

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

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

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

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

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

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

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

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

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

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

Leigh B. Stoller's avatar
Leigh B. Stoller committed
129 130 131
    # Every Ticket gets a new unique index (sequence number).
    my $seqno = TBGetUniqueIndex('next_ticket', 1);
    
Leigh B. Stoller's avatar
Leigh B. Stoller committed
132
    my $self = {};
Leigh B. Stoller's avatar
Leigh B. Stoller committed
133
    $self->{'rspec'}         = $rspec;
134
    $self->{'ticket_uuid'}   = undef;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
135 136
    $self->{'slice_uuid'}    = $slice->uuid();
    $self->{'owner_uuid'}    = $owner->uuid();
137 138
    $self->{'slice_cert'}    = $slice->GetCertificate();
    $self->{'owner_cert'}    = $owner->GetCertificate();
Leigh B. Stoller's avatar
Leigh B. Stoller committed
139
    $self->{'seqno'}         = $seqno;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
140 141
    $self->{'ticket_string'} = undef;
    $self->{'component'}     = undef;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
142 143
    $self->{'stored'}        = 0;	# Stored to the DB.

Leigh B. Stoller's avatar
Leigh B. Stoller committed
144 145 146 147
    #
    # For now, all tickets expire very quickly ...
    #
    $self->{'redeem_before'} =
148
	POSIX::strftime("20%y-%m-%dT%H:%M:%S", localtime(time() + (5*60)));
Leigh B. Stoller's avatar
Leigh B. Stoller committed
149

Leigh B. Stoller's avatar
Leigh B. Stoller committed
150 151 152 153 154 155 156 157
    #
    # 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
158 159 160 161 162 163
    bless($self, $class);

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

Leigh B. Stoller's avatar
Leigh B. Stoller committed
199 200 201 202 203 204 205 206 207 208
#
# 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
209 210 211
#
# Create a ticket object from a signed ticket string.
#
Leigh B. Stoller's avatar
Leigh B. Stoller committed
212
sub CreateFromSignedTicket($$;$$)
Leigh B. Stoller's avatar
Leigh B. Stoller committed
213
{
Leigh B. Stoller's avatar
Leigh B. Stoller committed
214
    my ($class, $ticket_string, $component, $nosig) = @_;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
215

Leigh B. Stoller's avatar
Leigh B. Stoller committed
216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235
    #
    # 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
236 237
    }

Leigh B. Stoller's avatar
Leigh B. Stoller committed
238 239
    # Use XML::Simple to convert to something we can mess with.
    my $parser = XML::LibXML->new;
240 241 242 243 244 245 246 247
    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
248 249 250 251 252

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

256 257 258 259
    # Dig out the ticket uuid.
    my ($uuid_node) = $doc->getElementsByTagName("uuid");
    return undef
	if (!defined($uuid_node));
260
    my $ticket_uuid = $uuid_node->to_literal();
261 262 263 264 265 266

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

267 268
    # Dig out the target certificate.
    my ($cert_node) = $doc->getElementsByTagName("target_gid");
Leigh B. Stoller's avatar
Leigh B. Stoller committed
269
    return undef
270 271 272 273 274
	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
275

276 277 278 279 280 281
    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
282 283 284
	return undef;
    }

285 286
    # Dig out the owner certificate.
    ($cert_node) = $doc->getElementsByTagName("owner_gid");
Leigh B. Stoller's avatar
Leigh B. Stoller committed
287
    return undef
288 289 290 291 292 293
	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
294

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

304
    # Sequence number
Leigh B. Stoller's avatar
Leigh B. Stoller committed
305 306 307 308
    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
309

Leigh B. Stoller's avatar
Leigh B. Stoller committed
310 311 312
    if (! ($seqno =~ /^\w+$/)) {
	print STDERR "Invalid sequence number in ticket\n";
	return undef;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
313 314
    }

Leigh B. Stoller's avatar
Leigh B. Stoller committed
315
    my $self = {};
Leigh B. Stoller's avatar
Leigh B. Stoller committed
316
    $self->{'idx'}           = undef;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
317
    $self->{'rspec'}         = $rspec;
318
    $self->{'ticket_uuid'}   = $ticket_uuid;
319 320 321 322
    $self->{'slice_uuid'}    = $target_certificate->uuid();
    $self->{'owner_uuid'}    = $owner_certificate->uuid();
    $self->{'slice_cert'}    = $target_certificate;
    $self->{'owner_cert'}    = $owner_certificate;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
323 324
    $self->{'ticket_string'} = $ticket_string;
    $self->{'xmlref'}        = $doc;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
325
    $self->{'component'}     = $component;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
326 327 328 329 330 331 332 333 334 335 336 337 338 339
    $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
340 341
	my $row = $query_result->fetchrow_hashref();
	$self->{'redeem_before'} = $row->{'redeem_before'};
Leigh B. Stoller's avatar
Leigh B. Stoller committed
342 343 344
	$self->{'idx'}    = $seqno;
	$self->{'stored'} = 1;
    }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
345 346 347 348
    bless($self, $class);

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

Leigh B. Stoller's avatar
Leigh B. Stoller committed
350
#
Leigh B. Stoller's avatar
Leigh B. Stoller committed
351 352
# 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
353 354 355 356 357 358 359 360
#
sub Delete($)
{
    my ($self) = @_;

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

Leigh B. Stoller's avatar
Leigh B. Stoller committed
361
    if ($self->stored()) {
362 363
	my $idx  = $self->idx();
	my $uuid = $self->ticket_uuid();
Leigh B. Stoller's avatar
Leigh B. Stoller committed
364 365 366
	
	DBQueryWarn("delete from geni_tickets where idx='$idx'")
	    or return -1;
367

Leigh B. Stoller's avatar
Leigh B. Stoller committed
368
	delete($tickets{"$idx"});
Leigh B. Stoller's avatar
Leigh B. Stoller committed
369 370 371 372
    }
    return 0;
}

373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389
#
# 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
390 391 392 393 394 395 396 397
#
# 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
398
    return -1
Leigh B. Stoller's avatar
Leigh B. Stoller committed
399 400 401 402 403 404
	if (! ref($self));

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

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

Leigh B. Stoller's avatar
Leigh B. Stoller committed
415 416
    my $idx        = $self->idx();
    my $seqno      = $self->seqno();
Leigh B. Stoller's avatar
Leigh B. Stoller committed
417 418
    my $slice_uuid = $self->slice_uuid();
    my $owner_uuid = $self->owner_uuid();
419
    my $ticket_uuid= $self->ticket_uuid();
Leigh B. Stoller's avatar
Leigh B. Stoller committed
420
    my $expires    = $self->redeem_before();
Leigh B. Stoller's avatar
Leigh B. Stoller committed
421

Leigh B. Stoller's avatar
Leigh B. Stoller committed
422 423 424 425 426
    #
    # 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
427 428 429 430
    if (!defined($idx)) {
	$idx = TBGetUniqueIndex('next_ticket', 1);
	$self->{'idx'} = $idx;
    }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
431
    # A locally generated ticket will not have a component. Might change that.
432
    push(@insert_data, "component_uuid='" . $self->component()->uuid() . "'")
Leigh B. Stoller's avatar
Leigh B. Stoller committed
433 434
	if (defined($self->component()));

Leigh B. Stoller's avatar
Leigh B. Stoller committed
435 436 437
    # 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
438
    push(@insert_data, "seqno='$seqno'");
439
    push(@insert_data, "ticket_uuid='$ticket_uuid'");
Leigh B. Stoller's avatar
Leigh B. Stoller committed
440 441
    push(@insert_data, "slice_uuid='$slice_uuid'");
    push(@insert_data, "owner_uuid='$owner_uuid'");
Leigh B. Stoller's avatar
Leigh B. Stoller committed
442
    push(@insert_data, "redeem_before='$expires'");
Leigh B. Stoller's avatar
Leigh B. Stoller committed
443 444 445 446 447 448 449 450
    
    my $safe_ticket = DBQuoteSpecial($self->ticket_string());
    push(@insert_data, "ticket_string=$safe_ticket");

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

Leigh B. Stoller's avatar
Leigh B. Stoller committed
451 452
    $tickets{"$idx"}  = $self;
    $self->{'stored'} = 1;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
453 454 455
    return 0;
}

Leigh B. Stoller's avatar
Leigh B. Stoller committed
456 457
#
# Sign the ticket before returning it. We capture the output, which is
Leigh B. Stoller's avatar
Leigh B. Stoller committed
458
# in XML. 
Leigh B. Stoller's avatar
Leigh B. Stoller committed
459 460 461 462 463 464 465
#
sub Sign($)
{
    my ($self) = @_;

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

Leigh B. Stoller's avatar
Leigh B. Stoller committed
467
    my $idx        = $self->seqno();
Leigh B. Stoller's avatar
Leigh B. Stoller committed
468
    my $expires    = $self->redeem_before();
469 470
    my $slice_cert = $self->slice_cert()->cert();
    my $owner_cert = $self->owner_cert()->cert();
Leigh B. Stoller's avatar
Leigh B. Stoller committed
471 472
    my $rspec_xml  = XMLout($self->rspec(), "NoAttr" => 1);
    $rspec_xml =~ s/opt\>/rspec\>/g;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
473

474
    #
475
    # Every ticket/credential gets its own uuid.
476
    #
477 478
    my $ticket_uuid = NewUUID();
    $self->{'ticket_uuid'} = $ticket_uuid;
479

Leigh B. Stoller's avatar
Leigh B. Stoller committed
480 481 482 483
    # 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
484 485 486 487 488
    #
    # 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
489
	"<credential xml:id=\"ref1\">\n".
Leigh B. Stoller's avatar
Leigh B. Stoller committed
490
	" <type>ticket</type>\n".
Leigh B. Stoller's avatar
Leigh B. Stoller committed
491
	" <serial>$idx</serial>\n".
492 493 494
	" <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
495
	" <expires>$expires</expires>\n".
Leigh B. Stoller's avatar
Leigh B. Stoller committed
496 497
	" <ticket>\n".
	"  <can_delegate>1</can_delegate>\n".
Leigh B. Stoller's avatar
Leigh B. Stoller committed
498
	"  <redeem_before>$expires</redeem_before>\n".
Leigh B. Stoller's avatar
Leigh B. Stoller committed
499
	"  $rspec_xml\n".
Leigh B. Stoller's avatar
Leigh B. Stoller committed
500 501 502 503 504 505 506 507 508 509 510 511 512 513
	" </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
514
    if (! open(SIGNER, "$SIGNCRED -c $CMCERT $filename |")) {
Leigh B. Stoller's avatar
Leigh B. Stoller committed
515 516 517 518 519 520 521
	print STDERR "Could not sign $filename\n";
	return -1;
    }
    my $ticket = "";
    while (<SIGNER>) {
	$ticket .= $_;
    }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
522 523 524 525
    if (!close(SIGNER)) {
	print STDERR "Could not sign $filename\n";
	return -1;
    }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
526
    $self->{'ticket_string'} = $ticket;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
527

Leigh B. Stoller's avatar
Leigh B. Stoller committed
528
    $self->Store() == 0
Leigh B. Stoller's avatar
Leigh B. Stoller committed
529 530
	or return -1;

Leigh B. Stoller's avatar
Leigh B. Stoller committed
531
    unlink($filename);
Leigh B. Stoller's avatar
Leigh B. Stoller committed
532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547
    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());
}

548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574
#
# 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
575 576
#
# Release a ticket. Need to release the nodes ...
577
# Used by the CM.
Leigh B. Stoller's avatar
Leigh B. Stoller committed
578 579 580 581 582
#
sub Release($)
{
    my ($self) = @_;

583
    return -1
Leigh B. Stoller's avatar
Leigh B. Stoller committed
584 585 586
	if (! ref($self));

    my $experiment = Experiment->Lookup($self->slice_uuid());
587 588 589 590 591 592
    if (!defined($experiment)) {
	#
	# Cannot be any nodes if no experiment.
	#
	return $self->Delete();
    }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
593 594 595
    my $pid     = $experiment->pid();
    my $eid     = $experiment->eid();
    my @nodeids = ();
Leigh B. Stoller's avatar
Leigh B. Stoller committed
596
    my @nodes   = ();
Leigh B. Stoller's avatar
Leigh B. Stoller committed
597

598 599
    foreach my $ref (@{$self->rspec()->{'node'}}) {
	my $resource_uuid = $ref->{'uuid'};
Leigh B. Stoller's avatar
Leigh B. Stoller committed
600 601 602 603 604
	my $node = Node->Lookup($resource_uuid);
	next
	    if (!defined($node));

	my $reservation = $node->Reservation();
Leigh B. Stoller's avatar
Leigh B. Stoller committed
605 606 607 608
	next
	    if (!defined($reservation));
	
	if ($reservation->SameExperiment($experiment)) {
Leigh B. Stoller's avatar
Leigh B. Stoller committed
609
	    push(@nodeids, $node->node_id());
Leigh B. Stoller's avatar
Leigh B. Stoller committed
610
	    push(@nodes, $node);
Leigh B. Stoller's avatar
Leigh B. Stoller committed
611 612 613
	}
    }
    if (@nodeids) {
Leigh B. Stoller's avatar
Leigh B. Stoller committed
614
	system("export NORELOAD=1; $NFREE -x -q $pid $eid @nodeids");
Leigh B. Stoller's avatar
Leigh B. Stoller committed
615
    }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
616 617 618
    foreach my $node (@nodes) {
	$node->Refresh();
    }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
619
    $self->Delete();
Leigh B. Stoller's avatar
Leigh B. Stoller committed
620 621 622
    return 0;
}

623 624 625 626 627 628 629 630 631 632 633 634 635 636
#
# 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
637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653
#
# 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
654 655
# _Always_ make sure that this 1 is at the end of the file...
1;