<?php
###############################################################################
# custom.login.googleauth-sample.php
#
# Custom Login for 2FO using Google Authenticator. Requires Watch My Domains
# SED v3.2.15 or later.
#
# Author: Anil Kumar
# CodePunch Solutions
# https://codepunch.com/
#
# 1. Rename this file to custom.login.php and copy to the root folder.
# 2. Edit this file to specify the $google_2fo_setupkey & 
#    $google_2fo_superadmin_secret 
#
# 3. Provide $google_2fo_setupkey to your users so that they can setup the
#    authenticator app from Google on their phone.
#    https://support.google.com/accounts/answer/1066447?co=GENIE.Platform%3DiOS&hl=en
#
# 4. To reset the authenticator secret for any user, login to Watch My Domains 
#    SED as administrator and deactivate the user. While the user is 
#    deactivated open the /custom.login.php (this file) in another tab of your 
#    browser.  Come back to Watch My Domains SED and reactivate the user.
###############################################################################

# Who should have Google 2FA Enabled?
# Use "NONE", "ALL" or "ADMIN". If you specify "ADMIN" only the admin users
# will have 2FO enabled.
#
$google_2fo_authentication 		= "ALL";  

# This is a one time code that will allow your users to setup the Google 
# Authenticator. Please replace the code below with your own (8 characters 
# or more). You should then provide this code to all your users so that they 
# can use it when prompted for the 2nd password.
#
# They will be shown a bar-code for setting up the Google Authenticator
# when they login for the first time. This code will work only once for each
# user after they login using their regular credentials.
#
$google_2fo_setupkey			= "YOURSECRET";

# Create a special 16 character secret code for super admin. This is very 
# important, so don't use the default here. Use only alphabets.
#
$google_2fo_superadmin_secret 	= "ABCDEFGHIJKLMNOP"; 

# Specify a code here for superadmin to reset the Google Authenticator for 
# self at any time. Leave this blank and use it only if really required. 
# Using this is not very safe because this key, if set, will always work
# (but only after you have logged in as a super admin)
#
$google_2fo_superadmin_setupkey = "";

# Do you want allow users to authenticate their device for a specific number
# of days? This will set a cookie so that they needn't do 2nd authentication
# for a while. 0 means no cookie. You can enter any number between 0 and 45,
# anything else will be ignored.
#
$google_2fa_cookie_set_days		= 30;

###############################################################################

# Please don't edit below this line unless you know what you are doing.

if($google_2fo_superadmin_secret == "ABCDEFGHIJKLMNOP" || $google_2fo_setupkey == "YOURSECRET") 
{
	echo "Please remove default keys like \"YOURSECRET\", \"ABCDEFGHIJKLMNOP\" etc.<br>";
	exit;
}

###############################################################################

define("USE_AUTH_DB","TRUE");

###############################################################################

require_once("lib/php/basic.php");				# Base Functions.
require_once("lib/php/ui/ui.php");				# UI Functions.
require_once("lib/php/pdo/dbinit.php");			# Database
require_once("lib/php/config.check.php");		# Load the configuration.

###############################################################################

$google_2fo_show = 0;
$google_2fo_qrcodeurl = "";

if(strtolower($google_2fo_authentication) == "all" || strtolower($google_2fo_authentication) == "admin") {
	include_once("lib/php/googleauthenticator/src/FixedBitNotation.php");
	include_once("lib/php/googleauthenticator/src/GoogleAuthenticatorInterface.php");
	include_once("lib/php/googleauthenticator/src/GoogleAuthenticator.php");
	include_once("lib/php/googleauthenticator/src/GoogleQrUrl.php");
}

###############################################################################

$auth = validateSession();
if($auth == 0) {
	cleanupGoogle2FO();
	interfaceRedir();
}

###############################################################################

$login_error = "";

###############################################################################

if(isset($_POST['ui-user']) && isset($_POST['ui-password'])) 
{
	$username = strip_tags(trim($_REQUEST['ui-user']));
	$password = strip_tags(trim($_REQUEST['ui-password']));
	$login_error = "";
	$login_fail_count = 0;
	$last_login_fail_at = date("Y-m-d H:i:s");
	$returnurl = false;
	
	$user = getConfigData('admin_user_name', "");
	$pass = getConfigData('admin_password', "");
	
	$max_login_attempts = get_config_data('max_login_attempts');
	$login_lockout_minutes = get_config_data('login_lockout_minutes');
	
	$pdo = init_db();
	if(isUserAuthEnabled() && $login_error == "" && $username != "" && $password != "")
	{
		$failcount = $pdo->getSingleEntry($pdo->auth_log_table, "ip", $_SERVER['REMOTE_ADDR'], "login_fail_count");
		if($failcount !== false);
			$login_fail_count = $failcount;
		$failtime = $pdo->getSingleEntry($pdo->auth_log_table, "ip", $_SERVER['REMOTE_ADDR'], "login_last_failed_at");
		if($failtime !== false)
		{
			if(isADate($failtime))
				$last_login_fail_at = $failtime;
		}
		
		$allow_logins = true;
		$maxlogins = 0;
		$lockout = 5;
		if(isset($max_login_attempts))
			$maxlogins = $max_login_attempts;
		if(isset($login_lockout_minutes))
			$lockout = $login_lockout_minutes;
		if($lockout <= 0)
			$lockout = 30*24*60;
		$unblock_minutes = $lockout;
		if($maxlogins > 0 && $login_fail_count >= $maxlogins)
		{
			$allow_logins = false;
			$timegap = intval((time()-strtotime($last_login_fail_at))/60);
			if($timegap >= $lockout && $lockout > 0)
				$allow_logins = true;
			else
				$unblock_minutes = $lockout-$timegap;
		}
		
		if($allow_logins)
		{
			# if salted and hashed
			if(strlen($pass) > 25)
				$admpassword = generateHash($password, $pass);
			else
				$admpassword = $password;

			if($user == $username && $pass == $admpassword && strlen($pass) >= 6)
			{
				//$pdo->addAuthLog(true);
				$userinfo['uid'] = 0;
				$userinfo['admin'] = true;
				$userinfo['name'] = $username;
				$userinfo['readwrite'] = 1;
				$userinfo['fullname'] = "System Admin";
				$userinfo['displayname'] = "System Admin [c]";
				$userinfo['lastsignin'] = date("Y-m-d H:i:s");
				$returnurl = $pdo->doLogin($userinfo);
				if($returnurl !== false)
					showGoogle2FOAuthentication($pdo, $username, $returnurl);
			}
			else 
			{
				$ip = $_SERVER['REMOTE_ADDR'];
				$failcount = $pdo->getSingleEntry($pdo->auth_log_table, "ip", $ip, "login_fail_count");
				$faildate = $pdo->getSingleEntry($pdo->auth_log_table, "ip", $ip, "login_last_failed_at");
				// checkUserLogin will reset login fail stats, but we don't know if login is OK till 2FA is done.
				$returnurl = checkUserLogin($pdo, $username, $password);
				if($returnurl !== false) {
					// Put back the login error count and date
					$pdo->updateTable($pdo->auth_log_table, "login_last_failed_at=?, login_fail_count=? WHERE ip=?", array($faildate, $failcount, $ip));
					showGoogle2FOAuthentication($pdo, $username, $returnurl);
				}
			}
			
			$failmsg = "";
			if($login_fail_count)
			{
				$failmsg .= " (";
				$failmsg .= $login_fail_count;
				if($maxlogins > 0)
					$failmsg .= "/" . $maxlogins;
				$failmsg .= ")";
			}
				
			if($returnurl === false)
			{
				# Login has failed.
				$login_fail_count++;
				$login_error = "Incorrect Login or Password";
				$login_error .= $failmsg;
			}
		}
		else
			$login_error = "Temporarily blocked for " . $unblock_minutes . " minutes. Too many failed attempts!";
	}
}

###############################################################################

function getUserInfo($pdo, $username)
{
	global $google_2fo_authentication, $google_2fo_superadmin_secret, $google_2fo_setupkey, $google_2fo_superadmin_setupkey;
	$userinfo = $pdo->getTableData($pdo->user_table, "*", "WHERE name=? AND active=1", array($username));
	if($userinfo !== false) {
		if(isset($userinfo[0])) {
			$userinfo[0]['admin'] = false;
			$groupinfo = $pdo->getTableData($pdo->group_access_table, "user_id, group_id", "WHERE user_id = ? AND group_id = 1", array($userinfo[0][id]));
			if($groupinfo !== false && isset($groupinfo[0]))
				$userinfo[0]['admin'] = true;
		}
		else {
			// Super Admin
			$userinfo[0] = array('admin'=>true,'name'=>$username, 'google_2fa'=>$google_2fo_superadmin_secret, 'google_2fa_init'=>$google_2fo_superadmin_setupkey);
		}
		return $userinfo[0];
	}
	return false;
}

///////////////////////////////////////////////////////////////////////////////////////////////
	
function addCustomUserColumn($pdo, $columns)
{
	$tablename = $pdo->user_table;
	$excolumns = $pdo->getAllColumnNames($tablename);
	if($excolumns !== false)
	{
		$count = count($columns)/3;
		$missing = 0;
		$sql = "ALTER TABLE `" . $tablename . "` ADD (";
		for($i = 0; $i < $count; $i++)
		{
			if(!in_array($columns[$i*3], $excolumns))
			{
				$sql .= "`" . $columns[$i*3] . "` ";
				$sql .= $columns[$i*3+1] . " ";
				$sql .= $columns[$i*3+2] . ",";
				$missing++;
			}
		}
		if($missing)
		{
			$sql = rtrim($sql, ", ");
			$sql .= ")";
			try 
			{
				$pdo->db_connect_handle->exec($sql);
				return true;
			} 
			catch (PDOException $e) 
			{
				$pdo->setError($e->getMessage());
				return false;
			}
		}
		else
			$pdo->setError("Column(s) (" . implode(",", $columns) . ") already present in table.");
	}
	return false;
}

###############################################################################

function randomPassword($maxlen) {
    $alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
    $pass = array(); 
    $alphaLength = strlen($alphabet) - 1; //put the length -1 in cache
    for ($i = 0; $i < $maxlen; $i++) {
        $n = rand(0, $alphaLength);
        $pass[] = $alphabet[$n];
    }
    return implode($pass); //turn the array into a string
}

###############################################################################

function showGoogle2FOAuthentication($pdo, $username, $returnurl)
{
	global $google_2fo_authentication, $google_2fo_superadmin_secret, $google_2fo_setupkey;
	global $google_2fa_cookie_set_days;
	
	if($google_2fa_cookie_set_days > 0 && $google_2fa_cookie_set_days <= 45) {
		if(isset($_COOKIE["deval_$username"])) {
			if($_COOKIE["deval_$username"] == "1") {
				$_SESSION['logged'] = true;
				header('Location:' . $returnurl);
				exit;
			}
		}
	}
	
	if(strtolower($google_2fo_authentication) != "all" && strtolower($google_2fo_authentication) != "admin") {
		$_SESSION['logged'] = true;
		header('Location:' . $returnurl);
		exit;
	}
	
	$_SESSION['logged'] = false;
	$columns = $pdo->getAllColumnNames($pdo->user_table);
	if(!in_array("google_2fa", $columns)) {
		// No 2FA Secret Column.
		$newcolumns[] = "google_2fa";
		$newcolumns[] = "varchar(16)";
		$newcolumns[] = "DEFAULT ''";
		addCustomUserColumn($pdo, $newcolumns);
	}
	if(!in_array("google_2fa_init", $columns)) {
		// No 2FA Init Column.
		$newcolumns[] = "google_2fa_init";
		$newcolumns[] = "varchar(16)";
		$newcolumns[] = "DEFAULT ''";
		addCustomUserColumn($pdo, $newcolumns);
	}
	
	$userinfo = getUserInfo($pdo, $username);
	if($userinfo !== false) {
		if(strtolower($google_2fo_authentication) == "admin" && $userinfo['admin'] == false) {
			$_SESSION['logged'] = true;
			header('Location:' . $returnurl);
			exit;
		}

		if(strlen($userinfo['google_2fa']) != 16) {
			$userinfo['google_2fa'] = randomPassword(16);
			$userinfo['google_2fa_init'] = $google_2fo_setupkey;
			$pdo->updateTable($pdo->user_table, "google_2fa=?, google_2fa_init=? WHERE id=?", array($userinfo['google_2fa'],$userinfo['google_2fa_init'],$userinfo['id']));
		}

		$_SESSION['duser'] = $username;
		$_SESSION['url'] = $returnurl;
		showGoogle2FOAuthBox($userinfo, 2);
	}
	else {
		echo "Unable to connect to database";
		exit;
	}
}

###############################################################################

if(isset($_REQUEST['ui-gcode']) && isset($_SESSION['duser'])) {
	$username = $_SESSION['duser'];
	if($username != "") {
		$pdo = init_db();
		$userinfo = getUserInfo($pdo, $username);
		if($userinfo !== false) {
			$usercode = $_REQUEST['ui-gcode'];
			// Special code to setup the authenticator
			if($usercode == $userinfo['google_2fa_init'] && strlen($userinfo['google_2fa_init']) >= 8 && strtoupper($usercode) != "YOURSECRET") {
				$pdo->updateTable($pdo->user_table, "google_2fa_init=? WHERE id=?", array("", $userinfo['id']));
				showGoogle2FOAuthBox($userinfo, 1);
			}
			else {
				$g = new \Sonata\GoogleAuthenticator\GoogleAuthenticator();
				$secret = $userinfo['google_2fa'];
				$currentcode = $g->getCode($secret);
				if ($g->checkCode($secret, $usercode)) {
					$pdo->addAuthLog(true);
					$_SESSION['logged'] = true;
					if($google_2fa_cookie_set_days > 0 && $google_2fa_cookie_set_days <= 45) {
						if(!isset($_COOKIE["deval_$username"]) && isset($_REQUEST['validate_device']))
							setcookie("deval_$username", "1", time() + (86400 * $google_2fa_cookie_set_days), "/");
					}
					header('Location:' . $_SESSION['url']) ;
					exit;
				}
				else {
					$pdo->addAuthLog(false);
					$ip = $_SERVER['REMOTE_ADDR'];
					$failcount = $pdo->getSingleEntry($pdo->auth_log_table, "ip", $ip, "login_fail_count");
					if($failcount) {
						$max_login_attempts = get_config_data('max_login_attempts');
						$login_error = "2FA Failed ($failcount/$max_login_attempts)";
					}
				}
			}
		}
	}
}

###############################################################################

function cleanupGoogle2FO()
{
	$pdo = init_db();
	$columns = $pdo->getAllColumnNames($pdo->user_table);
	if(in_array("google_2fa", $columns)) 
		return $pdo->updateTable($pdo->user_table, "google_2fa=? WHERE active != 1", array(''));
	return false;
}

###############################################################################

function showGoogle2FOAuthBox($userinfo, $gmode)
{
	global $google_2fo_show, $google_2fo_qrcodeurl;
	$google_2fo_show = $gmode;
	$google_2fo_qrcodeurl = \Sonata\GoogleAuthenticator\GoogleQrUrl::generate($userinfo['name'], $userinfo['google_2fa'], 'Watch My Domains SED');
}

###############################################################################

$loginmsg = $login_error;
$extracss = "";
if(isset($_REQUEST['r']))
{
	$loginmsg = filter_var($_REQUEST['r'], FILTER_SANITIZE_STRING);
	if($loginmsg == "timeout")
	{
		$loginmsg = "session timed out";
		doAuditLog(AUDIT_SESSION_TIMEOUT, "Session timedout for " . getUserName());
	}
	else if($loginmsg == "badsession")
		$loginmsg = "invalid session or session timed out";
	$extracss = "";
}
if(isset($_REQUEST['redir']))
{
	$redir = $_REQUEST['redir'];
	if(isValidApp($redir))
		$_SESSION['redir'] = $redir;
}

$phppath = get_php_folder_path();		
require_once($phppath . "version.php");
$layoutpath = get_layout_folder_path();
require_once($layoutpath . "head.inc.php");
echo "<style>.loginbox h3 { margin-top: 0; margin-bottom: 30px; font-size: 20px; }\n";
if($google_2fo_show) {
	echo ".loginbox {padding-top: 20px; background: rgba(250, 250, 250, 0.8) !important;}\n";
}
echo "</style>\n";
echo "<body>\n";

###############################################################################

?>
<div id="header"></div>
<div id="content">
	<div class="container-fluid">
		<div class="row">
			<div class="col-xs-12 main">
				<div id="content">
					<div class="container-fluid">
						<div class="row">
							<div class="col-xs-12">
								<p class="text-center"><a href="menu.php"><img id="loginlogo" src="lib/css/images/logo-small.png" class="img-responsive logo" alt="Watch My Domains SED v3"></a></p>
							</div>
						</div>
						<div class="row lgbox">
							<div class="col-sm-1"></div>
							<div class="col-sm-10 loginbg text-center">
								<div class="loginbox">
									<form action='<?php echo get_root_url(); ?>custom.login.php' method='post'>
									<?php if($google_2fo_show == 1) { 
										echo "<h3>Two Factor Authentication</h3>";
										echo "<p><img src=\"$google_2fo_qrcodeurl\"></p>";
									?>
										<p style="max-width: 320px;" class="text-justify">Please install 
										<a href="https://support.google.com/accounts/answer/1066447?co=GENIE.Platform%3DiOS&hl=en" target="_blank">Google Authenticator</a> 
										in your phone / tablet, open it and then scan the bar code above to add
										this application to it. After you have added the application, enter the code you see in Authenticator
										into the box below and click 'Submit'</p>
										<input type="text" name="ui-gcode" class="form-control" placeholder="Google Authenticator Code" required autofocus>
										<input type='submit' class="btn btn-primary btn-block" value='Submit'>
									<?php 
									} 
									else if($google_2fo_show == 2) { 
										$username = $_SESSION['duser'];
									?>
										<h3>Two Factor Authentication</h3>
										<p><?php echo $username; ?></p>
										<p style="max-width: 320px;" class="text-justify">Please open 
										<a href="https://support.google.com/accounts/answer/1066447?co=GENIE.Platform%3DiOS&hl=en" target="_blank">Google Authenticator</a> 
										in your phone or tablet
										and enter the code you see for this application &amp username 
										into the box below and click 'Submit'.</p>
										<p style="max-width: 320px;" class="text-justify">If you have not setup Google Authenticator yet, contact your
										system administrator for an activation code to enter into the box below.</p>
										<input type="text" name="ui-gcode" class="form-control" placeholder="Google Authenticator Code" required autofocus>
									<?php
										if($google_2fa_cookie_set_days > 0 && $google_2fa_cookie_set_days <= 45) {
											$dvmsg = "Don't ask again on this device for $google_2fa_cookie_set_days days.";
									?>
										<div class="checkbox text-left">
											<label><input type="checkbox" name='validate_device' id="validate_device">
											<?php echo $dvmsg; ?>
											</label>
										</div>
									<?php
										}
									?>
										<input type='submit' class="btn btn-primary btn-block" value='Submit'>
									<?php
									}
									else if($google_2fo_show == 0) { ?>
										<input type="text" name="ui-user" class="form-control" placeholder="Username" required autofocus>
										<input type="password" name="ui-password" class="form-control" placeholder="Password" required>
										<hr>
										<input type='submit' class="btn btn-primary btn-block" value='Submit'>
										<div class="loginmsg"<?php echo $extracss; ?>>
											<br>
											<p class="text-danger bg-danger" id="login-message" style="margin-bottom:0;">
											<?php echo $loginmsg; ?>
											</p>
										</div>
									<?php 
									} ?>
									</form>
								</div>			
							</div>
							<div class="col-sm-1"></div>
						</div>
						<br>
						<div class="row">
							<div class="col-xs-12">
								<p class="text-center">
								Watch My Domains Server Edition
								<?php
									if(function_exists('get_version'))
									{
										echo '&nbsp;' . get_version() . '.&nbsp;(G2FA)';
										echo '<br>Build: ' . get_build_date();
									}
								?>
								</p>
							</div>
						</div>
					</div>
				</div>

			</div>
		</div>
	</div>
</div>
<?php
	require_once($layoutpath . "bodytail.inc.php");
	echo "</body>\n</html>\n";
?>
