#!/usr/bin/perl
# keymaster.pl
##::: Author stradust, wheakory
##::: Hostname ux5.isu.edu (development), ux7.isu.edu (production)
##::: Language Perl
##::: Libs/Deps perl libraries => DBI, CGI, Time::Local, CGI::Session
##::: in-house config library => PHQConf.pm
##:::
##::: Path/Filename /cwis/apache/cgi-bin/keymaster.pl
##::: Path/Exe
##:::
##::: Project PH Web Throttling
##::: SubProject
##::: Keywords queuing, PHWeb Throttling
##:::
##::: DateCreated 03/15/2003
##:::
##::: ExeByWho HTTP as a cgi script
##::: ExeByWhat
##:::
##::: Description: This script handles the "client side" of the PHWeb queuing process. When the user
##::: requests a web portal via a link, they are first directed through hp-redirect.pl
##::: which tests the current status of HP1. If HP1 is up, the request is then passed to
##::: this script, keymaster.pl
##:::
##::: Keymaster.pl handles session management and database record insertion for a new
##::: request being added to the queue, and reports the current queue position for requests
##::: still passing through the queue.
##:::
##::: Refer to its partner script, gatekeeper.pl, for the "server side" of this process.
##:::
##:::
##:::
##:::
##::: ModDate WhoBy Tag Description
##::: ======== ======== ==== ===============================================
##::: 00/00/02 NameAcct NA01 template
##:::
##::: END
use DBI;
use CGI; # Enables perl database abstraction layer - interface to MySQL database
use Time::Local; # Enables date and time manipulation functions
use CGI::Session qw/-api3/; # Use sessions
use PHQConf; # Configuration parameters stored here
#use Env qw(HTTP_REFERER CONTENT_LENGTH QUERY_STRING);
$CGI::POST_MAX=1024 * 100; # max 100K posts - Set to avoid Denial of Service attacks
$CGI::DISABLE_UPLOADS = 1; # no uploads - Set to avoid Denial of Service attacks
$CGI::Session::IP_MATCH=1; # IP address must match to access session
#use strict;
use vars qw(%PHQConf $targetApp $tableName $AR_FLAG); # Declaring global variables
do "$PHQConf{AR_FILE}"; # Populate $AR_FLAG with the current available resource state
#print "Content-type: text/html\n\n";
#foreach $var (keys(%ENV)) {
# print "$var = ".$ENV{$var}."
\n";
#}
#print $ENV{'HTTP_REFERER'}.":".$ENV{'CONTENT_LENGTH'}.":".$ENV{'QUERY_STRING'}."\n";
#exit;
$debug = "";
%custData = (); # Initializing container for customer database info.
($targetApp, $referrer) = &getParms; # Retrieve current targetApp
print "$targetApp, $referrer
\n";
exit;
# Determine that the page is being called either from hp-redirect or as a refresh of itself, otherwise die.
if (!($ENV{'HTTP_REFERER'} =~ /hp-redirect.pl/)) {
if ($referrer ne "keymaster") {
$error = "Keymaster was called from an unaccepted referring location.";
&generateError($error);
}
}
if ($targetApp eq "myISU") { # Set the working database table name.
$tableName = "myISU_queue";
}
elsif ($targetApp eq "fsTools") {
$tableName = "fsTools_queue";
}
else {
&genLinkErrorPage($debug);
exit;
}
$debug .= "My referring page is: ".$ENV{'HTTP_REFERER'}."
";
my $sessHandle = &getCookie(); # Retrieve cookie data from client machine
$debug .= "Call to getCookie on line 47 returned: \$sessHandle = $sessHandle
";
if ($sessHandle) {
$debug .= "\$sessHandle name is ".$sessHandle->name.". \$sessHandle id is ".$sessHandle->id."
";
}
if (! $sessHandle) {
$token = 0;
}
else {
$token = $sessHandle->id;
}
$debug .= "\$token is set to $token
";
$cID = &queConnect(); # Connect to database
if (! $cID) {
$error = "Unable to establish a connection with the MySQL Database: ".$DBI::errstr."\n";
PHQConf::generateError($error);
exit;
}
$check = &queueCheck($targetApp, $token, \%custData);
$debug .= "queueCheck() returned $check
";
if ($check == -1) { # the queue is currently not empty or resources are unavailable and
# the customer is not in the queue.
# Customer needs to be added to the queue. Start by creating the cookie.
my $sessionObject = &bakeCookie();
$debug .= "\$sessionObject is $sessionObject. \$sessionObject's session id is $sessionObject->id
";
if ($sessionObject) {
# Cookie was created correctly. Proceed with updating the database.
my $rv = &addCust($tableName, $targetApp, $sessionObject, \%custData);
if (! $rv) {
$error = "Unable to add customer to queue database: ".$DBI::errstr."\n";
PHQConf::generateError($error);
# Something else should happen here. . . if the user isn't added to the database
# this whole process fails. Determine what should happen in the error state.
}
# Customer is added to the queue. Populate the custData hash for the queue position.
#my $rc = &getCustData2($sessionObject->id, \%custData);
#if (! $rc) {
# $error = "Unable to retrieve customer data: ".$DBI::errstr."\n";
# PHQConf::generateError($error);
#}
# Push out the refresh page.
&genRefreshPage($sessionObject);
}
else {
# Error setting cookie
$error = "Unable to create cookie on client machine: $!\n";
PHQConf::generateError($error);
}
}
elsif ($check == 0) {
# Send customer to login page
if ($targetApp eq "myISU") {
$target = $PHQConf{MYISU};
}
elsif ($targetApp eq "fsTools") {
$target = $PHQConf{FSTOOLS};
}
if ($sessHandle) {
my $cookieHandle = &eatCookie($sessHandle);
if (! $cookieHandle) {
PHQConf::generateError($DBI::errstr);
}
}
my $cgi = new CGI();
print $cgi->redirect(-uri=>$target, -cookie=>$cookieHandle);
}
elsif ($check == 1) {
# Customer is in queue.
if (! $sessHandle) {
$error = "No session exists
";
PHQConf::generateError($error);
}
# Calculate last refresh and generate errors if refresh is happening before specified period
if (time - $custData{'lastRefresh'} < $PHQConf{REFRESH_PERIOD}) {
$refreshWarning = "This page will automatically refresh. Forcing a refresh will not affect your";
$refreshWarning .= " place in the queue. Please do not refresh this page.";
}
# Update expire times on cookie & session
# $sessHandle->param("expires", $PHQConf{EXPIRE_TIME});
$sessHandle->expires($PHQConf{EXPIRE_TIME});
my $cgi = new CGI();
$cookieHandle = $cgi->cookie(-name=>$PHQConf{COOKIE_NAME}, -value=>$sessHandle->id, -expires=>$PHQConf{EXPIRE_TIME});
print $cgi->header(-cookie=>$cookieHandle);
# Update last refresh time in database table
my $query = "UPDATE ".$tableName." SET lastRefresh=NOW() WHERE custID=".$custData{'custID'};
my $sth = $cID->prepare($query);
my $rv = $sth->execute;
if (! $rv) {
PHQConf::generateError("Failed to update the last refresh time in the MySQL databases: ".$DBI::errstr.":
".$debug);
}
# Generate refresh page
&genRefreshPage($sessHandle,$refreshWarning);
}
exit;
#************************************ SUBROUTINE DEFINITIONS *************************************
sub getParms() {
# This subroutine gathers the parameter data - the application the customer is tring to access,
# specified as targetApp. The targetApp will be passed from hp-redirect.pl or as POST data from the
# refresh form.
my %form = ();
# No arguments were passed, so check for POST data
if ($ENV{'REQUEST_METHOD'} eq "GET") {
return $ENV{'QUERY_STRING'};
}
else {
read(STDIN, $buffer, $ENV{'CONTENT_LENGTH'});
}
# Split out the name-value pairs by & seperator, and load into array
my @pairs = split(/&/, $buffer);
$debug .= "\$buffer is $buffer
";
foreach $pair (@pairs)
{
# split name-value pairs by = sign
my ($name, $value) = split(/=/, $pair);
# Un-Webify plus signs and %-encoding
$value =~ tr/+/ /;
$value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
$name =~ tr/+/ /;
$name =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
# load name-value pairs into associative arrays, indexed by name
$form{$name} = $value;
}
return $form{'targetApp'},$form{'referrer'};
} # end getParms()
sub queConnect() {
# This subroutine defines the login parameters for the queue database and establishes a connection
my $db = "queue";
my $dbUsername = "keymaster";
my $dbPasswd = "k3yzplz";
my $connectionID = &mysqlConnect($db, $dbUsername, $dbPasswd);
return $connectionID;
} # end queConnect()
sub mysqlConnect($$$) {
# This subroutine establishes a connection to a mysql database
my ($database_name, $database_user, $database_passwd) = @_;
my $dbh = DBI->connect("DBI:mysql:database=$database_name", $database_user, $database_passwd, { RaiseError => 0, AutoCommit => 0 });
return $dbh;
} # End dbconnect
sub getCookie() {
# This subroutine determines if a queue cookie is set in the client's browser
my $cgi = new CGI();
my $cookieHandle = $cgi->cookie($PHQConf{COOKIE_NAME}) || "undef";
$debug .= "getCookie checked for a cookie. \$cookieHandle is $cookieHandle
";
if ($cookieHandle eq "undef") {
return 0;
}
else {
my $session = new CGI::Session(undef, $cookieHandle, {Directory=>$PHQConf{SESSIONDIR}});
# Return the session handle
return $session;
}
} # End checkCookie()
sub bakeCookie() {
# Create a new session and cookie
my $cgi = new CGI();
my $session = new CGI::Session(undef, $cgi, {Directory=>$PHQConf{SESSIONDIR}});
$session->expire($PHQConf{EXPIRE_TIME});
#$session->param(expire,$STALE_PERIOD);
my $cookieHandle = $cgi->cookie(-name=>$PHQConf{COOKIE_NAME}, -value=>$session->id, -expires=>$PHQConf{EXPIRE_TIME});
print $cgi->header(-cookie=>$cookieHandle);
$debug .= "bakeCookie() set cookie $cookieHandle
";
return $session;
} # End bakeCookie()
sub queueCheck($$$) {
# Accepts the targetApp, client cookie token, and an array reference for hash to store customer data in
# This subroutine checks the current state of the queue. It returns -1 if the queue is currently
# not empty or resources are unavailable but the customer is not in the queue. It returns 0 if the
# queue is empty and resources are available (indicating the client should be sent straight to the
# login page). And last it returns 1 if the customer was found in the database. In this case, it
# stores the customer's data in the hash indicated by the passed array reference
my ($targetApp, $clientCookieTok, $custData_ref) = @_;
# Check the current queue state for the targetApp
my $query = "SELECT COUNT(*) FROM ".$tableName;
my $sth = $cID->prepare($query);
$sth->execute;
my $queueCount = "";
my $row = "";
$row = $sth->fetchrow_array;
$queueCount = $row;
$debug .= "\$queueCount is $queueCount.
";
if (! $queueCount) {
# The queue table is empty. Check for available resources.
$debug .= "Resource status is set to ".$PHQConf{AR_FLAG}."
";
if ($AR_FLAG) {
# Resources are available - Direct user to login page
return 0;
}
else {
# Resources are not available - Add user to queue
return -1;
}
}
else {
# The queue table is not empty.
if ($clientCookieTok) {
# Check to see if this customer is already in the queue.
my $rc = &getCustData2($clientCookieTok, $custData_ref);
$debug .= "getCustData2 returned $rc
";
if ($rc) {
# Determine the user's current accessState. If access has been granted and resources are available,
# send the user to the portal login page
if ($$custData_ref{'accessState'} eq "Y") {
return 0;
}
else {
return 1;
}
}
else {
# Customer data was not found in the queue.
return -1;
}
}
else {
# This client doesn't have a cookie set. Add them to the queue.
return -1;
}
}
} # End queueCheck()
sub addCust($$$$) {
# Adds a customer entry to the database.
# Accepts the targetApp and tableName and treats %cookieData as a global variable
my ($tableName, $targetApp, $sessionHandle, $custData_ref) = @_;
# Get the next available customer ID.
my $query = "SELECT MAX(custID) FROM ".$tableName;
my $sth = $cID->prepare($query);
$sth->execute;
my @row = $sth->fetchrow_array;
my $maxID = $row[0];
$query = "INSERT INTO ".$tableName." VALUES('".++$maxID."','".$sessionHandle->param("_SESSION_REMOTE_ADDR")."','";
$query .= $sessionHandle->id."','N',NOW(),NOW(),NULL)";
$sth = $cID->prepare($query);
my $rv = $sth->execute;
if (! $rv) {
PHQConf::generateError("Failed to return a \"true\" return code: ".$DBI::errstr);
return 0;
}
else {
#### Testing
$query = "INSERT INTO ".$tableName."_order VALUES(NULL,'".$maxID."')";
$sth = $cID->prepare($query);
$rv = $sth->execute;
if ($rv) {
# Add data to custData hash
$$custData_ref{'custID'} = $maxID;
$$custData_ref{'IP'} = $sessionHandle->param("_SESSION_REMOTE_ADDR");
$$custData_ref{'token'} = $sessionHandle->id;
$$custData_ref{'accessState'} = "N";
$$custData_ref{'quePos'} = $cID->{'mysql_insertid'} || undef;
}
return $rv;
}
} # end addCust()
sub genRefreshPage($$) {
# This subroutine generates the refresh page the customer sees while in the queue. It accepts any error
# messages that have been generated when the script checks refresh times. It builds a page that informs
# the customer of their current position in the queue.
my ($session, $refreshErrors) = @_;
my $refTime = $PHQConf{REFRESH_PERIOD} * 1000;
#print "Content-type: text/html\n\n";
print "\n";
print "\n";
print "