#!/usr/bin/perl -Tw -I. -I/usr/local/positive/Perl

use strict;
use Pos::Webtop::Web::ModuleLoader;
use Pos::Webtop::Web::OEMLoader;
use CGI qw/:standard/;
use CGI::Cookie ();

sub auxAuthenticate
{
	my ($tb, $auxAuthId, $username, $password, $expired) = 
		@{{@_}}{qw/tb auxAuthId username password expired/};
	
	if (!length($password))
	{
		$password = 'fakepassword';
	}

	my $client = $tb->getClient();
	my $userId = $client->getUserId();

	my $port   = Pos::defaults::getPosRadiusPort();
	my $server = Pos::defaults::getPosRadiusServer();
	my $secret = Pos::defaults::getPosRadiusSecret();

	my $rusername = "99:$userId:$auxAuthId:$username";

	$rusername =~ s/(\W)/\\$1/g;
	$password =~ s/(\W)/\\$1/g;
	$secret    =~ s/(\W)/\\$1/g;
	$server    =~ s/(\W)/\\$1/g;
	$port      =~ s/(\W)/\\$1/g;

	my $oldPath = $ENV{'PATH'};
	my $oldLd = $ENV{'LD_LIBRARY_PATH'};
	
	$ENV{'PATH'} = '/usr/local/bin:/usr/bin:/bin';
	$ENV{'LD_LIBRARY_PATH'} = "/usr/local/positive/Library";

	"/usr/local/positive/Binary/AuthenticateRadiusAttribute $rusername $password $secret $server $port 30 18|" =~ /^(.*)$/;
	open(RAD,$1);

	my $response = <RAD>;
	# FIXME: when we support binary (not treated as single line string) attributes like integers and octet streams, we
	# will need to change how we read the value from the stdout of the helper binary -- right now we are doing a simple line read.
	my $attributeValue = <RAD>;
	close(RAD);

	$response = "" unless defined $response;
	$attributeValue = "" unless defined $attributeValue;
	chomp $response;
	chomp $attributeValue;

	if ( $response eq 'GOOD' )
	{
		# Even if GOOD was returned, check to see if the attribute value is set to expired
		if ($attributeValue eq 'expired')
		{
			${$expired} = 1;
		}

		return 1;
	}

	return 0;
}

my $tb = Pos::Webtop::Toolbox->new();
my $cgi = $tb->getCgi();
my $db = $tb->getWebtopDbWriter();
my $client = $tb->getClient();

$client->checkSession(ignoreAuxAuth=> 1);

my $userId = $client->getUserId();
my $clientSessionId = $client->getSessionId();
my $loginQueryString = $cgi->param("loginQueryString");
my $method = $cgi->param("method");

my $err = undef;

#
# Process methods
#
if (defined($method))
{
	# breakable block for all methods
	#
	{
		my $auxAuthId = sprintf("\%lu", $cgi->param("auxAuthId"));
		my $ignored   = $cgi->param("ignoreButton");

		# 
		# if they ignored the auth request, check to make sure that
		# they are allowed to ignore this request.  if they are, set
		# their client session aux auth status to good (but ignored) 
		# and keep going
		#
		if (defined($ignored) && length($ignored))
		{
			# check that this aux auth is optional
			my ($allow_ignore) = $db->query(sql => "
				select 
					optional as OPTIONAL
				from
					user_aux_auths
				where
					aux_auth_id = $auxAuthId 
					and user_id = $userId
			");

			if (defined($allow_ignore) && $allow_ignore)
			{
				$db->do(sql=> "
					update 
						client_session_aux_auths
					set 
						ignored = true,
						passed = true
					where 
						client_session_id = $clientSessionId
						and aux_auth_id = $auxAuthId
				");
			}
			else
			{
				# they weren't allowed to ignore this request. decrement their
				# tries left
				$db->do(sql=> "
					update 
						client_session_aux_auths
					set 
						tries_left = tries_left - 1
					where 
						client_session_id = $clientSessionId
						and aux_auth_id = $auxAuthId
				");

				$client->logger(
					severity=> "NORMAL",
					logMessage=> "User $userId aux auth $auxAuthId tried to ignore a non-optional aux auth");

				$err = 'This Additional Authentication is not optional.';
			}
		}
		elsif ($method eq "change")
		{
			my $ntlmExec = "/usr/local/positive/Binary/ChangeAuxAuthNtlmPassword";
			my $cmd;
			my $rv;
			
			my $oldPassword = $cgi->param("opw");
			my $newPassword = $cgi->param("pw1");
			my $confirmPassword = $cgi->param("pw2");

			if ($newPassword ne $confirmPassword)
			{
				$err = "The new passwords did not match.";
				$client->logger(severity=> "NORMAL",
					logMessage=> "User $userId aux auth $auxAuthId password change FAILED"
						. " due to password confirmation differences.");

				last;
			}

			if ( ($oldPassword eq "") || ($confirmPassword eq "") )
			{
				$err = "One or more passwords were blank.";
				$client->logger(severity=> "NORMAL",
					logMessage=> "User $userId aux auth $auxAuthId password change FAILED"
						. " due to blank passwords.");

				last;
			}
			
			$oldPassword =~ s/(\W)/\\$1/g;
			$confirmPassword =~ s/(\W)/\\$1/g;

			my $oldPath = $ENV{'PATH'};
			my $oldLd   = $ENV{'LD_LIBRARY_PATH'};

			$ENV{'PATH'} = '/usr/local/bin:/usr/bin:/bin';
			$ENV{'LD_LIBRARY_PATH'} = "/usr/local/positive/Library";

			"$ntlmExec $userId $auxAuthId $oldPassword $confirmPassword|" =~ /^(.*)$/;
			
			$client->logger(severity=> "NORMAL",
				logMessage=> "Trying to change password for aux auth $auxAuthId for user $userId...");

			open(P, $1);

			while (<P>)
			{
				$rv = $_;
			}

			close(P);
			chomp($rv);
			$rv = sprintf("\%lu", $rv);

			$ENV{'PATH'} = $oldPath;
			$ENV{'LD_LIBRARY_PATH'} = $oldLd;

			# If the return value was not zero, then something failed...
			if ($rv != 0)
			{
				$err = "The password change was not successful.";
				$client->logger(severity=> "NORMAL",
					logMessage=> "User $userId aux auth $auxAuthId password change FAILED.");

				last;
			}

			$db->do(sql=> "
				update client_session_aux_auths
				set expired = false
				where client_session_id = $clientSessionId
					and aux_auth_id = $auxAuthId");

			$client->logger(severity=> "NORMAL",
				logMessage=> "User $userId aux auth $auxAuthId password change SUCCESSFUL.");
		}
		elsif ($method eq "try")
		{
			my $password = $cgi->param("pw");
			my $username = $cgi->param("un");
			my $save = $cgi->param("save");
			
			my $worked = 0;
			my $expired = 0;
			
			$client->logger(severity=> "NORMAL",
				logMessage=> "Trying aux auth $auxAuthId for user $userId...");

			$worked = auxAuthenticate(
				tb=> $tb,
				auxAuthId=> $auxAuthId,
				username=> $username,
				password=> $password,
				expired=> \$expired);
			
			if (! $worked)
			{
				$client->logger(severity=> "NORMAL",
					logMessage=> "User $userId aux auth $auxAuthId FAILED attempt.");

				$db->do(sql=> "
					update client_session_aux_auths
					set tries_left = tries_left - 1
					where client_session_id = $clientSessionId
						and aux_auth_id = $auxAuthId");

				$err = "The supplied username and password were incorrect.";

				last;
			}

			$client->logger(severity=> "NORMAL",
				logMessage=> "User $userId aux auth $auxAuthId SUCCESSFUL.");

			if ($expired)
			{
				$client->logger(severity=> "NORMAL",
					logMessage=> "User $userId aux auth $auxAuthId is EXPIRED.");
			}

			$expired = ($expired ? "true" : "false");

			$db->do(sql=> "
				update client_session_aux_auths 
				set passed = true, expired = $expired
				where client_session_id = $clientSessionId
					and aux_auth_id = $auxAuthId");

			$db->do(sql=> "
				update user_aux_auths 
				set username = '" . $db->escape(dirty=> $username) . "'
				where user_id = $userId
					and aux_auth_id = $auxAuthId");
			
			if (defined($save) && length($save))
			{
				$db->do(sql=> "
					update user_aux_auths 
					set password = '" . $db->escape(dirty=> $password) . "'
					where user_id = $userId
						and aux_auth_id = $auxAuthId");
			}
		}
	}
}

#
# Check for max tries being exceeded
#
{
	my @denied = $db->query(sql=> "
		select aux_auth_id
		from client_session_aux_auths
		where client_session_id = $clientSessionId
			and tries_left < 1");
	
	if (@denied)
	{
		#print "Content-type: text/html\n\n";

		print $tb->getRefreshFormHtml(
			refreshUrl=> "login.pl?logout=1&desc=Additional authentication not completed");

		exit;
	}
}

#
# Check for remaining requirements
#
my @outstanding = ();
my @expired = ();

# breakable block for finding requirements
{
	@outstanding = $db->query(sql=> "
		select 
		  uaa.aux_auth_id as ID,
		  uaa.description as DESC,
		  uaa.username as USER,
		  uaa.password as PASS,
		  uaa.can_save_credentials as CAN_SAVE,
		  uaa.optional as OPTIONAL,
		  at.is_phone_factor as IS_PHONE_FACTOR
		from
		  client_session_aux_auths csaa
		  inner join user_aux_auths uaa
			  on (csaa.aux_auth_id = uaa.aux_auth_id and user_id = $userId)
		  inner join auth_types at on
		      uaa.auth_type_id = at.auth_type_id
		where
		  csaa.client_session_id = $clientSessionId
		  and passed = false
		order by uaa.aux_auth_id");

	if (! defined($method))
	{
		# We only run with no method on the initial run from main.pl -- this
		# means we need to try cached auths now.
		#
		my @try = @outstanding; # copy;
		@outstanding = (); # empty;

		foreach (@try)
		{
			my ($id, $desc, $user, $pass, $canSave, $optional) = @{$_};
			
			my $worked = 0;
			my $expired = 0;
			
			# breakable block
			{
				$canSave or last;
				defined($user) or last;
				length($user) or last;
				defined($pass) or last;
				length($pass) or last;

				# Try it out...
				#
				$client->logger(severity=> "NORMAL",
					logMessage=> "Trying cached aux auth $id for user $userId...");

				$worked = auxAuthenticate(
					tb=> $tb,
					auxAuthId=> $id,
					username=> $user,
					password=> $pass,
					expired=> \$expired);
			}
			
			if (! $worked)
			{
				$client->logger(severity=> "NORMAL",
					logMessage=> "Cached aux auth $id for user $userId FAILED.");

				push @outstanding, $_;
				next;
			}

			$client->logger(severity=> "NORMAL",
				logMessage=> "Cached aux auth $id for user $userId SUCCESSFUL.");

			if ($expired)
			{
				$client->logger(severity=> "NORMAL",
					logMessage=> "Cached aux auth $id for user $userId EXPIRED.");
			}

			$expired = ($expired ? "true" : "false");

			$db->do(sql=> "
				update client_session_aux_auths 
				set passed = true, expired = $expired
				where client_session_id = $clientSessionId
					and aux_auth_id = $id");
		}
	}

	@outstanding and last; # no need to find expired on this round

	@expired = $db->query(sql=> "
		select 
		  uaa.aux_auth_id as ID,
		  uaa.description as DESC,
		  uaa.username as USER
		from
		  client_session_aux_auths csaa
		  inner join user_aux_auths uaa
			  on (csaa.aux_auth_id = uaa.aux_auth_id and user_id = $userId)
		where
		  csaa.client_session_id = $clientSessionId
		  and expired = true
		order by uaa.aux_auth_id");
}

if (! (@outstanding || @expired))
{
	# Redirect to original query string on main.pl
	# We escape the " and ' for uri so it won't hurt the js.
	# We leave the rest unescaped as it is a real query string.
	#
	print $tb->getRefreshFormHtml(
		refreshUrl=> "main.pl$loginQueryString");

	exit();
}


#
# If we are here, we have remaining requirements and need to draw a page
#
my $oem = $tb->getOem();
my $isMobile = $client->isMobileWebtop();

$oem->printHtmlHeader( title=> "Authentication", isLogin=> 1);
$oem->printBodyHeader( noLink => 1, isMobile=> $isMobile  );

print "<center>\n";

$isMobile or print "<br/><br/><br/>\n";

print "
	<form autocomplete='off' method='POST' name='loginForm'>
		<input type='hidden' name='loginQueryString' value=\"" . HTML::Entities::encode($loginQueryString) . "\" />";

if (@outstanding)
{
	my ($id, $desc, $user, $pass, $canSave, $optional, $is_phone_factor) = @{ shift @outstanding };
	$user = "" unless defined $user;
	$desc = HTML::Entities::encode($desc);

	if (defined($err))
	{
		$err = "<b>$err</b>";
	}
	else
	{
		$err = "";
	}

	my $box = $tb->createBox( type=> "webTheme", title=> "Additional Authentication", width=> "300" );

	print "<input type='hidden' name='method' value='try'>\n";
	print "<input type='hidden' name='auxAuthId' value='$id'>\n";
	$isMobile or print $box->getHeaderHtml();
	
	print "
		<center>
		<br/><B>" . ($optional ? 'Optionally' : 'Please') . " enter the credentials for $desc.</B><br/><br/>
		<table border='0' class='SIGNONTABLE' cellspacing='0' cellpadding='4' style='margin-bottom: 10px;'>
		<tr>
			<td class='USERNAMETD' align='right'>
				<b>
					Username:
				</b>
			</td>
			<td class='USERNAMEINPUTTD' align='left'>
				<input type='text' size=\"" . ($isMobile ? "10" : "20") . "\" name='un' value=\"" . ($is_phone_factor ? '' : HTML::Entities::encode($user)) . "\" id='un' " . ($is_phone_factor ? 'DISABLED' : '') . "/>
			</td>
		</tr>
		<tr>
			<td class='PASSWORDTD' align='right'>
				<b>
					<div id='passwordLabel' class='PASSWORDTD'>
						Password:
					</div>
				</b>
			</td>
			<td class='PASSWORDINPUTTD' align='left'>
				<input type='password' size=\"" . ($isMobile ? "10" : "20") . "\" name='pw' id='pw' " . ($is_phone_factor ? 'DISABLED' : '') . "/>&nbsp;
			</td>			
		</tr>
		<tr>
			<td class='SIGNONBUTTON' align='center' colspan='2'>
				<input type='submit' name='signOnButton' id='signOnButton' value='Authenticate'>
				" . ($optional ? "<input type='submit' name='ignoreButton' id='ignoreButton' value='Skip'/>" : '') . "
				<input type='submit' value='Cancel'>
			</td>
		</tr>
	";

	if ($canSave)
	{
		print "
			<tr>
				<td align='center' colspan='2'>
					<input type='checkbox' name='save' /> <b>Save</b>
				</td>
			</tr>
		";
	}

	print "
		</table>
		$err	
		<br/><br/><br/>
		</center>
	";

	$isMobile or print $box->getFooterHtml();
}
elsif (@expired)
{
	my ($id, $desc, $user) = @{ shift @expired };
	$user = "" unless defined $user;
	$desc = HTML::Entities::encode($desc);

	if (defined($err))
	{
		$err = "<b>$err</b>";
	}
	else
	{
		$err = "";
	}

	my $box = $tb->createBox( type=> "webTheme", title=> "Password Expired", width=> "300" );

	print "<input type='hidden' name='method' value='change'>\n";
	print "<input type='hidden' name='auxAuthId' value='$id'>\n";
	$isMobile or print $box->getHeaderHtml();
	
	print "
		<center>
		<br/><B>Please choose a new password for $desc</B>.<br/><br/>
		<table border='0' class='SIGNONTABLE' cellspacing='0' cellpadding='4' style='margin-bottom: 10px;'>
		<tr>
			<td class='PASSWORDTD' align='right'>
				<b>
					<div id='passwordLabel' class='PASSWORDTD'>
						Old Password:
					</div>
				</b>
			</td>
			<td class='PASSWORDINPUTTD' align='left'>
				<input type='password' size=\"" . ($isMobile ? "10" : "20") . "\" name='opw' id='pw'/>&nbsp;
			</td>			
		</tr>
		<tr>
			<td class='PASSWORDTD' align='right'>
				<b>
					<div id='passwordLabel' class='PASSWORDTD'>
						New Password:
					</div>
				</b>
			</td>
			<td class='PASSWORDINPUTTD' align='left'>
				<input type='password' size=\"" . ($isMobile ? "10" : "20") . "\" name='pw1' />&nbsp;
			</td>			
		</tr>
		<tr>
			<td class='PASSWORDTD' align='right'>
				<b>
					<div id='passwordLabel' class='PASSWORDTD'>
						Repeat New Password:
					</div>
				</b>
			</td>
			<td class='PASSWORDINPUTTD' align='left'>
				<input type='password' size=\"" . ($isMobile ? "10" : "20") . "\" name='pw2' />&nbsp;
			</td>			
		</tr>
		<tr>
			<td class='SIGNONBUTTON' align='center' colspan='2'>
				<input type='submit' name='signOnButton' id='signOnButton' value='Change Password'>
			</td>
		</tr>
		</table>
		$err	
		<br/><br/><br/>
		</center>
	";

	$isMobile or print $box->getFooterHtml();
}

print "
	</center>
	</form>

	<script type='text/javascript'>
		document.getElementById('pw').focus();
	</script>
";

$oem->printBodyFooter();
$oem->printHtmlFooter();

