mirror of
https://github.com/slawkens/myaac.git
synced 2025-04-26 17:29:21 +02:00
Update login.php for latest TFS 1.x and otservbr
Works in both. Thanks for Znote for rfc6238 lib.
This commit is contained in:
parent
ac271839a6
commit
240be18367
277
login.php
277
login.php
@ -1,45 +1,47 @@
|
|||||||
<?php
|
<?php
|
||||||
require_once('common.php');
|
require_once 'common.php';
|
||||||
require_once('config.php');
|
require_once 'config.php';
|
||||||
require_once('config.local.php');
|
require_once 'config.local.php';
|
||||||
require_once(SYSTEM . 'functions.php');
|
require_once SYSTEM . 'functions.php';
|
||||||
require_once(SYSTEM . 'init.php');
|
require_once SYSTEM . 'init.php';
|
||||||
require_once(SYSTEM . 'status.php');
|
require_once SYSTEM . 'status.php';
|
||||||
|
|
||||||
# error function
|
# error function
|
||||||
function sendError($msg){
|
function sendError($message, $code = 3){
|
||||||
$ret = [];
|
$ret = [];
|
||||||
$ret["errorCode"] = 3;
|
$ret['errorCode'] = $code;
|
||||||
$ret["errorMessage"] = $msg;
|
$ret['errorMessage'] = $message;
|
||||||
die(json_encode($ret));
|
die(json_encode($ret));
|
||||||
}
|
}
|
||||||
|
|
||||||
# event schedule function
|
# event schedule function
|
||||||
function parseEvent($table1, $date, $table2)
|
function parseEvent($table1, $date, $table2)
|
||||||
{
|
{
|
||||||
if ($table1) {
|
if ($table1) {
|
||||||
if ($date) {
|
if ($date) {
|
||||||
if ($table2) {
|
if ($table2) {
|
||||||
$date = $table1->getAttribute('startdate');
|
$date = $table1->getAttribute('startdate');
|
||||||
return date_create("{$date}")->format('U');
|
return date_create("{$date}")->format('U');
|
||||||
|
} else {
|
||||||
|
$date = $table1->getAttribute('enddate');
|
||||||
|
return date_create("{$date}")->format('U');
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
$date = $table1->getAttribute('enddate');
|
foreach($table1 as $attr) {
|
||||||
return date_create("{$date}")->format('U');
|
if ($attr) {
|
||||||
}
|
return $attr->getAttribute($table2);
|
||||||
} else {
|
}
|
||||||
foreach($table1 as $attr) {
|
|
||||||
if ($attr) {
|
|
||||||
return $attr->getAttribute($table2);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
return 'error';
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$request = file_get_contents('php://input');
|
$request = json_decode(file_get_contents('php://input'));
|
||||||
$result = json_decode($request);
|
$action = $request->type ?? '';
|
||||||
$action = isset($result->type) ? $result->type : '';
|
|
||||||
|
/** @var OTS_Base_DB $db */
|
||||||
|
/** @var array $config */
|
||||||
|
|
||||||
switch ($action) {
|
switch ($action) {
|
||||||
case 'cacheinfo':
|
case 'cacheinfo':
|
||||||
@ -51,35 +53,32 @@ switch ($action) {
|
|||||||
'gamingyoutubestreams' => 0,
|
'gamingyoutubestreams' => 0,
|
||||||
'gamingyoutubeviewer' => 0
|
'gamingyoutubeviewer' => 0
|
||||||
]));
|
]));
|
||||||
break;
|
|
||||||
|
|
||||||
case 'eventschedule':
|
|
||||||
$eventlist = [];
|
|
||||||
$file_path = config('server_path') . 'data/XML/events.xml';
|
|
||||||
if (!file_exists($file_path)) {
|
|
||||||
die(json_encode([]));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
$xml = new DOMDocument;
|
|
||||||
$xml->load($file_path);
|
|
||||||
$tmplist = [];
|
|
||||||
$tableevent = $xml->getElementsByTagName('event');
|
|
||||||
|
|
||||||
foreach ($tableevent as $event) {
|
case 'eventschedule':
|
||||||
if ($event) { $tmplist = [
|
$eventlist = [];
|
||||||
'colorlight' => parseEvent($event->getElementsByTagName('colors'), false, 'colorlight'),
|
$file_path = config('server_path') . 'data/XML/events.xml';
|
||||||
'colordark' => parseEvent($event->getElementsByTagName('colors'), false, 'colordark'),
|
if (!file_exists($file_path)) {
|
||||||
'description' => parseEvent($event->getElementsByTagName('description'), false, 'description'),
|
die(json_encode([]));
|
||||||
'displaypriority' => intval(parseEvent($event->getElementsByTagName('details'), false, 'displaypriority')),
|
}
|
||||||
'enddate' => intval(parseEvent($event, true, false)),
|
$xml = new DOMDocument;
|
||||||
'isseasonal' => getBoolean(intval(parseEvent($event->getElementsByTagName('details'), false, 'isseasonal'))),
|
$xml->load($file_path);
|
||||||
'name' => $event->getAttribute('name'),
|
$tmplist = [];
|
||||||
'startdate' => intval(parseEvent($event, true, true)),
|
$tableevent = $xml->getElementsByTagName('event');
|
||||||
'specialevent' => intval(parseEvent($event->getElementsByTagName('details'), false, 'specialevent'))
|
|
||||||
];
|
foreach ($tableevent as $event) {
|
||||||
$eventlist[] = $tmplist; } }
|
if ($event) { $tmplist = [
|
||||||
die(json_encode(['eventlist' => $eventlist, 'lastupdatetimestamp' => time()]));
|
'colorlight' => parseEvent($event->getElementsByTagName('colors'), false, 'colorlight'),
|
||||||
break;
|
'colordark' => parseEvent($event->getElementsByTagName('colors'), false, 'colordark'),
|
||||||
|
'description' => parseEvent($event->getElementsByTagName('description'), false, 'description'),
|
||||||
|
'displaypriority' => intval(parseEvent($event->getElementsByTagName('details'), false, 'displaypriority')),
|
||||||
|
'enddate' => intval(parseEvent($event, true, false)),
|
||||||
|
'isseasonal' => getBoolean(intval(parseEvent($event->getElementsByTagName('details'), false, 'isseasonal'))),
|
||||||
|
'name' => $event->getAttribute('name'),
|
||||||
|
'startdate' => intval(parseEvent($event, true, true)),
|
||||||
|
'specialevent' => intval(parseEvent($event->getElementsByTagName('details'), false, 'specialevent'))
|
||||||
|
];
|
||||||
|
$eventlist[] = $tmplist; } }
|
||||||
|
die(json_encode(['eventlist' => $eventlist, 'lastupdatetimestamp' => time()]));
|
||||||
|
|
||||||
case 'boostedcreature':
|
case 'boostedcreature':
|
||||||
$boostDB = $db->query("select * from " . $db->tableName('boosted_creature'))->fetchAll();
|
$boostDB = $db->query("select * from " . $db->tableName('boosted_creature'))->fetchAll();
|
||||||
@ -92,9 +91,9 @@ switch ($action) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'login':
|
case 'login':
|
||||||
|
|
||||||
$port = $config['lua']['gameProtocolPort'];
|
$port = $config['lua']['gameProtocolPort'];
|
||||||
|
|
||||||
// default world info
|
// default world info
|
||||||
$world = [
|
$world = [
|
||||||
'id' => 0,
|
'id' => 0,
|
||||||
@ -115,75 +114,136 @@ switch ($action) {
|
|||||||
];
|
];
|
||||||
|
|
||||||
$characters = [];
|
$characters = [];
|
||||||
$account = null;
|
|
||||||
|
|
||||||
// common columns
|
|
||||||
$columns = 'name, level, sex, vocation, looktype, lookhead, lookbody, looklegs, lookfeet, lookaddons, lastlogin, isreward, istutorial';
|
|
||||||
|
|
||||||
$account = new OTS_Account();
|
$account = new OTS_Account();
|
||||||
$account->findByEmail($result->email);
|
|
||||||
|
$inputEmail = $request->email ?? false;
|
||||||
|
$inputAccountName = $request->accountname ?? false;
|
||||||
|
$inputToken = $request->token ?? false;
|
||||||
|
|
||||||
|
if ($inputEmail != false) { // login by email
|
||||||
|
$account->findByEmail($request->email);
|
||||||
|
}
|
||||||
|
else if($inputAccountName != false) { // login by account name
|
||||||
|
$account->find($inputAccountName);
|
||||||
|
}
|
||||||
|
|
||||||
$config_salt_enabled = fieldExist('salt', 'accounts');
|
$config_salt_enabled = fieldExist('salt', 'accounts');
|
||||||
$current_password = encrypt(($config_salt_enabled ? $account->getCustomField('salt') : '') . $result->password);
|
$current_password = encrypt(($config_salt_enabled ? $account->getCustomField('salt') : '') . $request->password);
|
||||||
|
|
||||||
if (!$account->isLoaded() || $account->getPassword() != $current_password) {
|
if (!$account->isLoaded() || $account->getPassword() != $current_password) {
|
||||||
sendError('Email or password is not correct.');
|
sendError(($inputEmail != false ? 'Email' : 'Account name') . ' or password is not correct.');
|
||||||
|
}
|
||||||
|
|
||||||
|
//log_append('test.log', var_export($account->getCustomField('secret'), true));
|
||||||
|
$accountHasSecret = false;
|
||||||
|
if (fieldExist('secret', 'accounts')) {
|
||||||
|
$accountSecret = $account->getCustomField('secret');
|
||||||
|
if ($accountSecret != null && $accountSecret != '') {
|
||||||
|
$accountHasSecret = true;
|
||||||
|
if ($inputToken === false) {
|
||||||
|
sendError('Submit a valid two-factor authentication token.', 6);
|
||||||
|
} else {
|
||||||
|
require_once LIBS . 'rfc6238.php';
|
||||||
|
if (TokenAuth6238::verify($accountSecret, $inputToken) !== true) {
|
||||||
|
sendError('Two-factor authentication failed, token is wrong.', 6);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// common columns
|
||||||
|
$columns = 'id, name, level, sex, vocation, looktype, lookhead, lookbody, looklegs, lookfeet, lookaddons';
|
||||||
|
|
||||||
|
if (fieldExist('isreward', 'accounts')) {
|
||||||
|
$columns .= ', isreward';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fieldExist('istutorial', 'accounts')) {
|
||||||
|
$columns .= ', istutorial';
|
||||||
}
|
}
|
||||||
|
|
||||||
$players = $db->query("select {$columns} from players where account_id = " . $account->getId() . " AND deletion = 0");
|
$players = $db->query("select {$columns} from players where account_id = " . $account->getId() . " AND deletion = 0");
|
||||||
if($players && $players->rowCount() > 0) {
|
if($players && $players->rowCount() > 0) {
|
||||||
$players = $players->fetchAll();
|
$players = $players->fetchAll();
|
||||||
|
|
||||||
|
$highestLevelId = 0;
|
||||||
|
$highestLevel = 0;
|
||||||
foreach ($players as $player) {
|
foreach ($players as $player) {
|
||||||
$characters[] = create_char($player);
|
if ($player['level'] >= $highestLevel) {
|
||||||
|
$highestLevel = $player['level'];
|
||||||
|
$highestLevelId = $player['id'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($players as $player) {
|
||||||
|
$characters[] = create_char($player, $highestLevelId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$save = false;
|
if (fieldExist('premdays', 'accounts') && fieldExist('lastday', 'accounts')) {
|
||||||
$timeNow = time();
|
$save = false;
|
||||||
$query = $db->query("select `premdays`, `lastday` from `accounts` where `id` = " . $account->getId());
|
$timeNow = time();
|
||||||
if($query->rowCount() > 0) {
|
$query = $db->query("select `premdays`, `lastday` from `accounts` where `id` = " . $account->getId());
|
||||||
|
if ($query->rowCount() > 0) {
|
||||||
$query = $query->fetch();
|
$query = $query->fetch();
|
||||||
$premDays = (int)$query['premdays'];
|
$premDays = (int)$query['premdays'];
|
||||||
$lastDay = (int)$query['lastday'];
|
$lastDay = (int)$query['lastday'];
|
||||||
$lastLogin = $lastDay;
|
$lastLogin = $lastDay;
|
||||||
}
|
|
||||||
else {
|
|
||||||
sendError("Error while fetching your account data. Please contact admin.");
|
|
||||||
}
|
|
||||||
if($premDays != 0 && $premDays != PHP_INT_MAX ) {
|
|
||||||
if($lastDay == 0) {
|
|
||||||
$lastDay = $timeNow;
|
|
||||||
$save = true;
|
|
||||||
} else {
|
} else {
|
||||||
$days = (int)(($timeNow - $lastDay) / 86400);
|
sendError("Error while fetching your account data. Please contact admin.");
|
||||||
if($days > 0) {
|
}
|
||||||
if($days >= $premDays) {
|
if ($premDays != 0 && $premDays != PHP_INT_MAX) {
|
||||||
$premDays = 0;
|
if ($lastDay == 0) {
|
||||||
$lastDay = 0;
|
$lastDay = $timeNow;
|
||||||
} else {
|
$save = true;
|
||||||
$premDays -= $days;
|
} else {
|
||||||
$reminder = (int)(($timeNow - $lastDay) % 86400);
|
$days = (int)(($timeNow - $lastDay) / 86400);
|
||||||
$lastDay = $timeNow - $reminder;
|
if ($days > 0) {
|
||||||
}
|
if ($days >= $premDays) {
|
||||||
|
$premDays = 0;
|
||||||
$save = true;
|
$lastDay = 0;
|
||||||
}
|
} else {
|
||||||
|
$premDays -= $days;
|
||||||
|
$reminder = ($timeNow - $lastDay) % 86400;
|
||||||
|
$lastDay = $timeNow - $reminder;
|
||||||
|
}
|
||||||
|
|
||||||
|
$save = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if ($lastDay != 0) {
|
||||||
|
$lastDay = 0;
|
||||||
|
$save = true;
|
||||||
|
}
|
||||||
|
if ($save) {
|
||||||
|
$db->query("update `accounts` set `premdays` = " . $premDays . ", `lastday` = " . $lastDay . " where `id` = " . $account->getId());
|
||||||
}
|
}
|
||||||
} else if ($lastDay != 0) {
|
|
||||||
$lastDay = 0;
|
|
||||||
$save = true;
|
|
||||||
}
|
}
|
||||||
if($save) {
|
|
||||||
$db->query("update `accounts` set `premdays` = " . $premDays . ", `lastday` = " . $lastDay . " where `id` = " . $account->getId());
|
|
||||||
}
|
|
||||||
$premiumAccount = $premDays > 0;
|
|
||||||
$timePremium = time() + ($premDays * 86400);
|
|
||||||
|
|
||||||
$worlds = [$world];
|
$worlds = [$world];
|
||||||
$playdata = compact('worlds', 'characters');
|
$playdata = compact('worlds', 'characters');
|
||||||
|
|
||||||
|
$sessionKey = ($inputEmail !== false) ? $inputEmail : $inputAccountName; // email or account name
|
||||||
|
$sessionKey .= "\n" . $request->password; // password
|
||||||
|
if (!fieldExist('istutorial', 'players')) {
|
||||||
|
$sessionKey .= "\n";
|
||||||
|
}
|
||||||
|
$sessionKey .= ($accountHasSecret && strlen($accountSecret) > 5) ? $inputToken : '';
|
||||||
|
|
||||||
|
// this is workaround to distinguish between TFS 1.x and otservbr
|
||||||
|
// TFS 1.x requires the number in session key
|
||||||
|
// otservbr requires just login and password
|
||||||
|
// so we check for istutorial field which is present in otservbr, and not in TFS
|
||||||
|
if (!fieldExist('istutorial', 'players')) {
|
||||||
|
$sessionKey .= "\n".floor(time() / 30);
|
||||||
|
}
|
||||||
|
|
||||||
|
log_append('slaw.log', $sessionKey);
|
||||||
|
|
||||||
$session = [
|
$session = [
|
||||||
'sessionkey' => "$result->email\n$result->password",
|
'sessionkey' => $sessionKey,
|
||||||
'lastlogintime' => (!$account) ? 0 : $account->getLastLogin(),
|
'lastlogintime' => 0,
|
||||||
'ispremium' => ($config['lua']['freePremium']) ? true : $account->isPremium(),
|
'ispremium' => $config['lua']['freePremium'] || $account->isPremium(),
|
||||||
'premiumuntil' => ($account->getPremDays()) > 0 ? (time() + ($account->getPremDays() * 86400)) : 0,
|
'premiumuntil' => ($account->getPremDays()) > 0 ? (time() + ($account->getPremDays() * 86400)) : 0,
|
||||||
'status' => 'active', // active, frozen or suspended
|
'status' => 'active', // active, frozen or suspended
|
||||||
'returnernotification' => false,
|
'returnernotification' => false,
|
||||||
@ -195,20 +255,19 @@ switch ($action) {
|
|||||||
'emailcoderequest' => false
|
'emailcoderequest' => false
|
||||||
];
|
];
|
||||||
die(json_encode(compact('session', 'playdata')));
|
die(json_encode(compact('session', 'playdata')));
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
sendError("Unrecognized event {$action}.");
|
sendError("Unrecognized event {$action}.");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
function create_char($player) {
|
function create_char($player, $highestLevelId) {
|
||||||
global $config;
|
global $config;
|
||||||
return [
|
return [
|
||||||
'worldid' => 0,
|
'worldid' => 0,
|
||||||
'name' => $player['name'],
|
'name' => $player['name'],
|
||||||
'ismale' => intval($player['sex']) === 1,
|
'ismale' => intval($player['sex']) === 1,
|
||||||
'tutorial' => (bool)$player['istutorial'],
|
'tutorial' => isset($player['istutorial']) && $player['istutorial'],
|
||||||
'level' => intval($player['level']),
|
'level' => intval($player['level']),
|
||||||
'vocation' => $config['vocations'][$player['vocation']],
|
'vocation' => $config['vocations'][$player['vocation']],
|
||||||
'outfitid' => intval($player['looktype']),
|
'outfitid' => intval($player['looktype']),
|
||||||
@ -217,10 +276,10 @@ function create_char($player) {
|
|||||||
'legscolor' => intval($player['looklegs']),
|
'legscolor' => intval($player['looklegs']),
|
||||||
'detailcolor' => intval($player['lookfeet']),
|
'detailcolor' => intval($player['lookfeet']),
|
||||||
'addonsflags' => intval($player['lookaddons']),
|
'addonsflags' => intval($player['lookaddons']),
|
||||||
'ishidden' => 0,
|
'ishidden' => isset($player['deletion']) && (int)$player['deletion'] === 1,
|
||||||
'istournamentparticipant' => false,
|
'istournamentparticipant' => false,
|
||||||
'ismaincharacter' => true,
|
'ismaincharacter' => $highestLevelId == $player['id'],
|
||||||
'dailyrewardstate' => intval($player['isreward']),
|
'dailyrewardstate' => isset($player['isreward']) ? intval($player['isreward']) : 0,
|
||||||
'remainingdailytournamentplaytime' => 0
|
'remainingdailytournamentplaytime' => 0
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
285
system/libs/rfc6238.php
Normal file
285
system/libs/rfc6238.php
Normal file
@ -0,0 +1,285 @@
|
|||||||
|
<?php
|
||||||
|
/** https://github.com/Voronenko/PHPOTP/blob/08cda9cb9c30b7242cf0b3a9100a6244a2874927/code/base32static.php
|
||||||
|
* Encode in Base32 based on RFC 4648.
|
||||||
|
* Requires 20% more space than base64
|
||||||
|
* Great for case-insensitive filesystems like Windows and URL's (except for = char which can be excluded using the pad option for urls)
|
||||||
|
*
|
||||||
|
* @package default
|
||||||
|
* @author Bryan Ruiz
|
||||||
|
**/
|
||||||
|
class Base32Static {
|
||||||
|
|
||||||
|
private static $map = array(
|
||||||
|
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', // 7
|
||||||
|
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', // 15
|
||||||
|
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', // 23
|
||||||
|
'Y', 'Z', '2', '3', '4', '5', '6', '7', // 31
|
||||||
|
'=' // padding character
|
||||||
|
);
|
||||||
|
|
||||||
|
private static $flippedMap = array(
|
||||||
|
'A'=>'0', 'B'=>'1', 'C'=>'2', 'D'=>'3', 'E'=>'4', 'F'=>'5', 'G'=>'6', 'H'=>'7',
|
||||||
|
'I'=>'8', 'J'=>'9', 'K'=>'10', 'L'=>'11', 'M'=>'12', 'N'=>'13', 'O'=>'14', 'P'=>'15',
|
||||||
|
'Q'=>'16', 'R'=>'17', 'S'=>'18', 'T'=>'19', 'U'=>'20', 'V'=>'21', 'W'=>'22', 'X'=>'23',
|
||||||
|
'Y'=>'24', 'Z'=>'25', '2'=>'26', '3'=>'27', '4'=>'28', '5'=>'29', '6'=>'30', '7'=>'31'
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use padding false when encoding for urls
|
||||||
|
*
|
||||||
|
* @return base32 encoded string
|
||||||
|
* @author Bryan Ruiz
|
||||||
|
**/
|
||||||
|
public static function encode($input, $padding = true) {
|
||||||
|
if(empty($input)) return "";
|
||||||
|
|
||||||
|
$input = str_split($input);
|
||||||
|
$binaryString = "";
|
||||||
|
|
||||||
|
for($i = 0; $i < count($input); $i++) {
|
||||||
|
$binaryString .= str_pad(base_convert(ord($input[$i]), 10, 2), 8, '0', STR_PAD_LEFT);
|
||||||
|
}
|
||||||
|
|
||||||
|
$fiveBitBinaryArray = str_split($binaryString, 5);
|
||||||
|
$base32 = "";
|
||||||
|
$i=0;
|
||||||
|
|
||||||
|
while($i < count($fiveBitBinaryArray)) {
|
||||||
|
$base32 .= self::$map[base_convert(str_pad($fiveBitBinaryArray[$i], 5,'0'), 2, 10)];
|
||||||
|
$i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if($padding && ($x = strlen($binaryString) % 40) != 0) {
|
||||||
|
if($x == 8) $base32 .= str_repeat(self::$map[32], 6);
|
||||||
|
else if($x == 16) $base32 .= str_repeat(self::$map[32], 4);
|
||||||
|
else if($x == 24) $base32 .= str_repeat(self::$map[32], 3);
|
||||||
|
else if($x == 32) $base32 .= self::$map[32];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $base32;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function decode($input) {
|
||||||
|
if(empty($input)) return;
|
||||||
|
|
||||||
|
$paddingCharCount = substr_count($input, self::$map[32]);
|
||||||
|
$allowedValues = array(6,4,3,1,0);
|
||||||
|
|
||||||
|
if(!in_array($paddingCharCount, $allowedValues)) return false;
|
||||||
|
|
||||||
|
for($i=0; $i<4; $i++){
|
||||||
|
if($paddingCharCount == $allowedValues[$i] &&
|
||||||
|
substr($input, -($allowedValues[$i])) != str_repeat(self::$map[32], $allowedValues[$i])) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$input = str_replace('=','', $input);
|
||||||
|
$input = str_split($input);
|
||||||
|
$binaryString = "";
|
||||||
|
|
||||||
|
for($i=0; $i < count($input); $i = $i+8) {
|
||||||
|
$x = "";
|
||||||
|
|
||||||
|
if(!in_array($input[$i], self::$map)) return false;
|
||||||
|
|
||||||
|
for($j=0; $j < 8; $j++) {
|
||||||
|
$x .= str_pad(base_convert(@self::$flippedMap[@$input[$i + $j]], 10, 2), 5, '0', STR_PAD_LEFT);
|
||||||
|
}
|
||||||
|
|
||||||
|
$eightBits = str_split($x, 8);
|
||||||
|
|
||||||
|
for($z = 0; $z < count($eightBits); $z++) {
|
||||||
|
$binaryString .= ( ($y = chr(base_convert($eightBits[$z], 2, 10))) || ord($y) == 48 ) ? $y:"";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $binaryString;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// http://www.faqs.org/rfcs/rfc6238.html
|
||||||
|
// https://github.com/Voronenko/PHPOTP/blob/08cda9cb9c30b7242cf0b3a9100a6244a2874927/code/rfc6238.php
|
||||||
|
// Local changes: http -> https, consistent indentation, 200x200 -> 300x300 QR image size, PHP end tag
|
||||||
|
class TokenAuth6238 {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* verify
|
||||||
|
*
|
||||||
|
* @param string $secretkey Secret clue (base 32).
|
||||||
|
* @return bool True if success, false if failure
|
||||||
|
*/
|
||||||
|
public static function verify($secretkey, $code, $rangein30s = 3) {
|
||||||
|
$key = base32static::decode($secretkey);
|
||||||
|
$unixtimestamp = time()/30;
|
||||||
|
|
||||||
|
for($i=-($rangein30s); $i<=$rangein30s; $i++) {
|
||||||
|
$checktime = (int)($unixtimestamp+$i);
|
||||||
|
$thiskey = self::oath_hotp($key, $checktime);
|
||||||
|
|
||||||
|
if ((int)$code == self::oath_truncate($thiskey,6)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static function getTokenCode($secretkey,$rangein30s = 3) {
|
||||||
|
$result = "";
|
||||||
|
$key = base32static::decode($secretkey);
|
||||||
|
$unixtimestamp = time()/30;
|
||||||
|
|
||||||
|
for($i=-($rangein30s); $i<=$rangein30s; $i++) {
|
||||||
|
$checktime = (int)($unixtimestamp+$i);
|
||||||
|
$thiskey = self::oath_hotp($key, $checktime);
|
||||||
|
$result = $result." # ".self::oath_truncate($thiskey,6);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getTokenCodeDebug($secretkey,$rangein30s = 3) {
|
||||||
|
$result = "";
|
||||||
|
print "<br/>SecretKey: $secretkey <br/>";
|
||||||
|
|
||||||
|
$key = base32static::decode($secretkey);
|
||||||
|
print "Key(base 32 decode): $key <br/>";
|
||||||
|
|
||||||
|
$unixtimestamp = time()/30;
|
||||||
|
print "UnixTimeStamp (time()/30): $unixtimestamp <br/>";
|
||||||
|
|
||||||
|
for($i=-($rangein30s); $i<=$rangein30s; $i++) {
|
||||||
|
$checktime = (int)($unixtimestamp+$i);
|
||||||
|
print "Calculating oath_hotp from (int)(unixtimestamp +- 30sec offset): $checktime basing on secret key<br/>";
|
||||||
|
|
||||||
|
$thiskey = self::oath_hotp($key, $checktime, true);
|
||||||
|
print "======================================================<br/>";
|
||||||
|
print "CheckTime: $checktime oath_hotp:".$thiskey."<br/>";
|
||||||
|
|
||||||
|
$result = $result." # ".self::oath_truncate($thiskey,6,true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getBarCodeUrl($username, $domain, $secretkey, $issuer) {
|
||||||
|
$url = "https://chart.apis.google.com/chart";
|
||||||
|
$url = $url."?chs=300x300&chld=M|0&cht=qr&chl=otpauth://totp/";
|
||||||
|
$url = $url.$username . "@" . $domain . "%3Fsecret%3D" . $secretkey . '%26issuer%3D' . rawurlencode($issuer);
|
||||||
|
return $url;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function generateRandomClue($length = 16) {
|
||||||
|
$b32 = "234567QWERTYUIOPASDFGHJKLZXCVBNM";
|
||||||
|
$s = "";
|
||||||
|
|
||||||
|
for ($i = 0; $i < $length; $i++)
|
||||||
|
$s .= $b32[rand(0,31)];
|
||||||
|
|
||||||
|
return $s;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function hotp_tobytestream($key) {
|
||||||
|
$result = array();
|
||||||
|
$last = strlen($key);
|
||||||
|
for ($i = 0; $i < $last; $i = $i + 2) {
|
||||||
|
$x = $key[$i] + $key[$i + 1];
|
||||||
|
$x = strtoupper($x);
|
||||||
|
$x = hexdec($x);
|
||||||
|
$result = $result.chr($x);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function oath_hotp ($key, $counter, $debug=false) {
|
||||||
|
$result = "";
|
||||||
|
$orgcounter = $counter;
|
||||||
|
$cur_counter = array(0,0,0,0,0,0,0,0);
|
||||||
|
|
||||||
|
if ($debug) {
|
||||||
|
print "Packing counter $counter (".dechex($counter).")into binary string - pay attention to hex representation of key and binary representation<br/>";
|
||||||
|
}
|
||||||
|
|
||||||
|
for($i=7;$i>=0;$i--) { // C for unsigned char, * for repeating to the end of the input data
|
||||||
|
$cur_counter[$i] = pack ('C*', $counter);
|
||||||
|
|
||||||
|
if ($debug) {
|
||||||
|
print $cur_counter[$i]."(".dechex(ord($cur_counter[$i])).")"." from $counter <br/>";
|
||||||
|
}
|
||||||
|
|
||||||
|
$counter = $counter >> 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($debug) {
|
||||||
|
foreach ($cur_counter as $char) {
|
||||||
|
print ord($char) . " ";
|
||||||
|
}
|
||||||
|
|
||||||
|
print "<br/>";
|
||||||
|
}
|
||||||
|
|
||||||
|
$binary = implode($cur_counter);
|
||||||
|
|
||||||
|
// Pad to 8 characters
|
||||||
|
str_pad($binary, 8, chr(0), STR_PAD_LEFT);
|
||||||
|
|
||||||
|
if ($debug) {
|
||||||
|
print "Prior to HMAC calculation pad with zero on the left until 8 characters.<br/>";
|
||||||
|
print "Calculate sha1 HMAC(Hash-based Message Authentication Code http://en.wikipedia.org/wiki/HMAC).<br/>";
|
||||||
|
print "hash_hmac ('sha1', $binary, $key)<br/>";
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = hash_hmac ('sha1', $binary, $key);
|
||||||
|
|
||||||
|
if ($debug) {
|
||||||
|
print "Result: $result <br/>";
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function oath_truncate($hash, $length = 6, $debug=false) {
|
||||||
|
$result="";
|
||||||
|
|
||||||
|
// Convert to dec
|
||||||
|
if($debug) {
|
||||||
|
print "converting hex hash into characters<br/>";
|
||||||
|
}
|
||||||
|
|
||||||
|
$hashcharacters = str_split($hash,2);
|
||||||
|
|
||||||
|
if($debug) {
|
||||||
|
print_r($hashcharacters);
|
||||||
|
print "<br/>and convert to decimals:<br/>";
|
||||||
|
}
|
||||||
|
|
||||||
|
for ($j=0; $j<count($hashcharacters); $j++) {
|
||||||
|
$hmac_result[]=hexdec($hashcharacters[$j]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if($debug) {
|
||||||
|
print_r($hmac_result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// http://php.net/manual/ru/function.hash-hmac.php
|
||||||
|
// adopted from brent at thebrent dot net 21-May-2009 08:17 comment
|
||||||
|
|
||||||
|
$offset = $hmac_result[19] & 0xf;
|
||||||
|
|
||||||
|
if($debug) {
|
||||||
|
print "Calculating offset as 19th element of hmac:".$hmac_result[19]."<br/>";
|
||||||
|
print "offset:".$offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = (
|
||||||
|
(($hmac_result[$offset+0] & 0x7f) << 24 ) |
|
||||||
|
(($hmac_result[$offset+1] & 0xff) << 16 ) |
|
||||||
|
(($hmac_result[$offset+2] & 0xff) << 8 ) |
|
||||||
|
($hmac_result[$offset+3] & 0xff)
|
||||||
|
) % pow(10,$length);
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
Loading…
x
Reference in New Issue
Block a user