Reorganized even more code
This commit is contained in:
parent
5fe5673fb7
commit
8366d2c3b6
@ -19,7 +19,7 @@ class API
|
|||||||
public function __construct($params = [])
|
public function __construct($params = [])
|
||||||
{
|
{
|
||||||
set_error_handler(['\danog\MadelineProto\Exception', 'ExceptionErrorHandler']);
|
set_error_handler(['\danog\MadelineProto\Exception', 'ExceptionErrorHandler']);
|
||||||
$this->session = new Session($params);
|
$this->session = new MTProto($params);
|
||||||
$future_salts = $this->get_future_salts(3);
|
$future_salts = $this->get_future_salts(3);
|
||||||
$future_salts = $this->get_future_salts(3);
|
$future_salts = $this->get_future_salts(3);
|
||||||
}
|
}
|
||||||
|
@ -13,8 +13,102 @@ If not, see <http://www.gnu.org/licenses/>.
|
|||||||
namespace danog\MadelineProto;
|
namespace danog\MadelineProto;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages encryption and message frames.
|
* Manages all of the mtproto stuff.
|
||||||
*/
|
*/
|
||||||
class MTProto extends Tools
|
class MTProto extends MTProtoTools
|
||||||
{
|
{
|
||||||
|
public $settings = [];
|
||||||
|
|
||||||
|
public function __construct($settings = [])
|
||||||
|
{
|
||||||
|
// Set default settings
|
||||||
|
$default_settings = [
|
||||||
|
'authorization' => [
|
||||||
|
'auth_key' => null,
|
||||||
|
'temp_auth_key' => null,
|
||||||
|
'default_temp_auth_key_expires_in' => 86400,
|
||||||
|
'session_id' => \phpseclib\Crypt\Random::string(8),
|
||||||
|
'rsa_key' => '-----BEGIN RSA PUBLIC KEY-----
|
||||||
|
MIIBCgKCAQEAwVACPi9w23mF3tBkdZz+zwrzKOaaQdr01vAbU4E1pvkfj4sqDsm6
|
||||||
|
lyDONS789sVoD/xCS9Y0hkkC3gtL1tSfTlgCMOOul9lcixlEKzwKENj1Yz/s7daS
|
||||||
|
an9tqw3bfUV/nqgbhGX81v/+7RFAEd+RwFnK7a+XYl9sluzHRyVVaTTveB2GazTw
|
||||||
|
Efzk2DWgkBluml8OREmvfraX3bkHZJTKX4EQSjBbbdJ2ZXIsRrYOXfaA+xayEGB+
|
||||||
|
8hdlLmAjbCVfaigxX0CDqWeR1yFL9kwd9P0NsZRPsmoqVwMbMu7mStFai6aIhc3n
|
||||||
|
Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB
|
||||||
|
-----END RSA PUBLIC KEY-----',
|
||||||
|
'message_ids_limit' => 5,
|
||||||
|
],
|
||||||
|
'connection' => [
|
||||||
|
'ip_address' => '149.154.167.50',
|
||||||
|
'port' => '443',
|
||||||
|
'protocol' => 'tcp_full',
|
||||||
|
],
|
||||||
|
'app_info' => [
|
||||||
|
'api_id' => 25628,
|
||||||
|
'api_hash' => '1fe17cda7d355166cdaa71f04122873c',
|
||||||
|
],
|
||||||
|
'tl_schema' => [
|
||||||
|
'layer' => 55,
|
||||||
|
'src' => [
|
||||||
|
__DIR__.'/TL_mtproto_v1.json',
|
||||||
|
__DIR__.'/TL_telegram_v55.json',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'logging' => [
|
||||||
|
'logging' => 1,
|
||||||
|
'logging_param' => '/tmp/MadelineProto.log',
|
||||||
|
'logging' => 3,
|
||||||
|
],
|
||||||
|
'max_tries' => [
|
||||||
|
'query' => 5,
|
||||||
|
'authorization' => 5,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
foreach ($default_settings as $key => $param) {
|
||||||
|
if (!isset($settings[$key])) {
|
||||||
|
$settings[$key] = $param;
|
||||||
|
}
|
||||||
|
foreach ($param as $subkey => $subparam) {
|
||||||
|
if (!isset($settings[$key][$subkey])) {
|
||||||
|
$settings[$key][$subkey] = $subparam;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->settings = $settings;
|
||||||
|
|
||||||
|
// Connect to servers
|
||||||
|
$this->sock = new Connection($this->settings['connection']['ip_address'], $this->settings['connection']['port'], $this->settings['connection']['protocol']);
|
||||||
|
|
||||||
|
// Load rsa key
|
||||||
|
$this->key = new RSA($settings['authorization']['rsa_key']);
|
||||||
|
// Istantiate struct class
|
||||||
|
$this->struct = new \danog\PHP\StructTools();
|
||||||
|
// Istantiate prime class
|
||||||
|
$this->PrimeModule = new PrimeModule();
|
||||||
|
// Istantiate TL class
|
||||||
|
$this->tl = new TL\TL($this->settings['tl_schema']['src']);
|
||||||
|
// Istantiate logging class
|
||||||
|
$this->log = new Logging($this->settings['logging']['logging'], $this->settings['logging']['logging_param']);
|
||||||
|
|
||||||
|
$this->seq_no = 0;
|
||||||
|
$this->timedelta = 0; // time delta
|
||||||
|
$this->incoming_message_ids = [];
|
||||||
|
$this->outgoing_message_ids = [];
|
||||||
|
$this->ack_incoming_message_ids = [];
|
||||||
|
$this->ack_outgoing_message_ids = [];
|
||||||
|
$this->future_salts = [];
|
||||||
|
|
||||||
|
if ($this->settings['authorization']['temp_auth_key'] == null || $this->settings['authorization']['auth_key'] == null) {
|
||||||
|
if ($this->settings['authorization']['auth_key'] == null) {
|
||||||
|
$this->settings['authorization']['auth_key'] = $this->create_auth_key(-1);
|
||||||
|
}
|
||||||
|
$this->settings['authorization']['temp_auth_key'] = $this->create_auth_key($this->settings['authorization']['default_temp_auth_key_expires_in']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __destruct()
|
||||||
|
{
|
||||||
|
unset($this->sock);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
20
src/danog/MadelineProto/MTProtoTools.php
Normal file
20
src/danog/MadelineProto/MTProtoTools.php
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
/*
|
||||||
|
Copyright 2016 Daniil Gentili
|
||||||
|
(https://daniil.it)
|
||||||
|
This file is part of MadelineProto.
|
||||||
|
MadelineProto is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||||
|
MadelineProto is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
|
See the GNU Affero General Public License for more details.
|
||||||
|
You should have received a copy of the GNU General Public License along with the MadelineProto.
|
||||||
|
If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace danog\MadelineProto;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages encryption and message frames.
|
||||||
|
*/
|
||||||
|
class MTProtoTools extends \danog\MadelineProto\MTProtoTools\SeqNoHandler
|
||||||
|
{
|
||||||
|
}
|
48
src/danog/MadelineProto/MTProtoTools/AckHandler.php
Normal file
48
src/danog/MadelineProto/MTProtoTools/AckHandler.php
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
<?php
|
||||||
|
/*
|
||||||
|
Copyright 2016 Daniil Gentili
|
||||||
|
(https://daniil.it)
|
||||||
|
This file is part of MadelineProto.
|
||||||
|
MadelineProto is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||||
|
MadelineProto is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
|
See the GNU Affero General Public License for more details.
|
||||||
|
You should have received a copy of the GNU General Public License along with the MadelineProto.
|
||||||
|
If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace danog\MadelineProto\MTProtoTools;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages acknowledgement of messages.
|
||||||
|
*/
|
||||||
|
class AckHandler extends \danog\MadelineProto\Tools
|
||||||
|
{
|
||||||
|
public function ack_outgoing_message_id($message_id)
|
||||||
|
{
|
||||||
|
// The server acknowledges that it received my message
|
||||||
|
if (!in_array($message_id, $this->outgoing_message_ids)) {
|
||||||
|
throw new Exception("Couldn't find message id ".$message_id.' in the array of outgoing message ids. Maybe try to increase its size?');
|
||||||
|
}
|
||||||
|
$this->ack_outgoing_message_ids[] = $message_id;
|
||||||
|
if (count($this->ack_outgoing_message_ids) > $this->settings['authorization']['message_ids_limit']) {
|
||||||
|
array_shift($this->ack_outgoing_message_ids);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function ack_incoming_message_id($message_id)
|
||||||
|
{
|
||||||
|
if ($this->settings['authorization']['temp_auth_key']['id'] === null || $this->settings['authorization']['temp_auth_key']['id'] == \danog\MadelineProto\Tools::string2bin('\x00\x00\x00\x00\x00\x00\x00\x00')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// I let the server know that I received its message
|
||||||
|
if (!in_array($message_id, $this->incoming_message_ids)) {
|
||||||
|
throw new Exception("Couldn't find message id ".$message_id.' in the array of incoming message ids. Maybe try to increase its size?');
|
||||||
|
}
|
||||||
|
$this->object_call('msgs_ack', ['msg_ids' => [$message_id]]);
|
||||||
|
$this->ack_incoming_message_ids[] = $message_id;
|
||||||
|
if (count($this->ack_incoming_message_ids) > $this->settings['authorization']['message_ids_limit']) {
|
||||||
|
array_shift($this->ack_incoming_message_ids);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
244
src/danog/MadelineProto/MTProtoTools/AuthKeyHandler.php
Normal file
244
src/danog/MadelineProto/MTProtoTools/AuthKeyHandler.php
Normal file
@ -0,0 +1,244 @@
|
|||||||
|
<?php
|
||||||
|
/*
|
||||||
|
Copyright 2016 Daniil Gentili
|
||||||
|
(https://daniil.it)
|
||||||
|
This file is part of MadelineProto.
|
||||||
|
MadelineProto is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||||
|
MadelineProto is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
|
See the GNU Affero General Public License for more details.
|
||||||
|
You should have received a copy of the GNU General Public License along with the MadelineProto.
|
||||||
|
If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace danog\MadelineProto\MTProtoTools;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages the creation of the authorization key.
|
||||||
|
*/
|
||||||
|
class AuthKeyHandler extends AckHandler
|
||||||
|
{
|
||||||
|
public function create_auth_key($expires_in = -1)
|
||||||
|
{
|
||||||
|
foreach (\danog\MadelineProto\Tools::range(0, $this->settings['max_tries']['authorization']) as $retry_id_total) {
|
||||||
|
// Make pq request
|
||||||
|
$nonce = \phpseclib\Crypt\Random::string(16);
|
||||||
|
$this->log->log('Handshake: Requesting pq');
|
||||||
|
$ResPQ = $this->method_call('req_pq', ['nonce' => $nonce]);
|
||||||
|
$server_nonce = $ResPQ['server_nonce'];
|
||||||
|
if ($ResPQ['nonce'] !== $nonce) {
|
||||||
|
throw new Exception('Handshake: wrong nonce');
|
||||||
|
}
|
||||||
|
foreach ($ResPQ['server_public_key_fingerprints'] as $curfp) {
|
||||||
|
$curfp_biginteger = new \phpseclib\Math\BigInteger($curfp);
|
||||||
|
if ($this->key->fp->equals($curfp_biginteger)) {
|
||||||
|
$public_key_fingerprint = $curfp;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!isset($public_key_fingerprint)) {
|
||||||
|
throw new Exception("Handshake: couldn't find our key in the server_public_key_fingerprints vector.");
|
||||||
|
}
|
||||||
|
$pq_bytes = $ResPQ['pq'];
|
||||||
|
// Compute p and q
|
||||||
|
$pq = new \phpseclib\Math\BigInteger($pq_bytes, 256);
|
||||||
|
list($p, $q) = $this->PrimeModule->primefactors($pq);
|
||||||
|
$p = new \phpseclib\Math\BigInteger($p);
|
||||||
|
$q = new \phpseclib\Math\BigInteger($q);
|
||||||
|
if ($p->compare($q) > 0) {
|
||||||
|
list($p, $q) = [$q, $p];
|
||||||
|
}
|
||||||
|
if (!($pq->equals($p->multiply($q)) && $p->compare($q) < 0)) {
|
||||||
|
throw new Exception("Handshake: couldn't compute p and q.");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$this->log->log(sprintf('Factorization %s = %s * %s', $pq, $p, $q));
|
||||||
|
|
||||||
|
// Serialize object for req_DH_params
|
||||||
|
$p_bytes = $this->struct->pack('>I', (string) $p);
|
||||||
|
$q_bytes = $this->struct->pack('>I', (string) $q);
|
||||||
|
|
||||||
|
$new_nonce = \phpseclib\Crypt\Random::string(32);
|
||||||
|
if ($expires_in < 0) {
|
||||||
|
$data = $this->tl->serialize_obj('p_q_inner_data', ['pq' => $pq_bytes, 'p' => $p_bytes, 'q' => $q_bytes, 'nonce' => $nonce, 'server_nonce' => $server_nonce, 'new_nonce' => $new_nonce]);
|
||||||
|
} else {
|
||||||
|
$data = $this->tl->serialize_obj('p_q_inner_data_temp', ['pq' => $pq_bytes, 'p' => $p_bytes, 'q' => $q_bytes, 'nonce' => $nonce, 'server_nonce' => $server_nonce, 'new_nonce' => $new_nonce, 'expires_in' => $expires_in]);
|
||||||
|
}
|
||||||
|
$sha_digest = sha1($data, true);
|
||||||
|
|
||||||
|
// Encrypt serialized object
|
||||||
|
$random_bytes = \phpseclib\Crypt\Random::string(255 - strlen($data) - strlen($sha_digest));
|
||||||
|
$to_encrypt = $sha_digest.$data.$random_bytes;
|
||||||
|
$encrypted_data = $this->key->encrypt($to_encrypt);
|
||||||
|
|
||||||
|
// req_DH_params
|
||||||
|
$this->log->log('Starting Diffie Hellman key exchange');
|
||||||
|
$server_dh_params = $this->method_call('req_DH_params',
|
||||||
|
[
|
||||||
|
'nonce' => $nonce,
|
||||||
|
'server_nonce' => $server_nonce,
|
||||||
|
'p' => $p_bytes,
|
||||||
|
'q' => $q_bytes,
|
||||||
|
'public_key_fingerprint' => $public_key_fingerprint,
|
||||||
|
'encrypted_data' => $encrypted_data,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
// Check nonce and server_nonce
|
||||||
|
if ($nonce != $server_dh_params['nonce']) {
|
||||||
|
throw new Exception('Handshake: wrong nonce.');
|
||||||
|
}
|
||||||
|
if ($server_nonce != $server_dh_params['server_nonce']) {
|
||||||
|
throw new Exception('Handshake: wrong server nonce.');
|
||||||
|
}
|
||||||
|
if (isset($server_dh_params['new_nonce_hash']) && substr(sha1($new_nonce), -32) != $server_dh_params['new_nonce_hash']) {
|
||||||
|
throw new Exception('Handshake: wrong new nonce hash.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get key and iv and decrypt answer
|
||||||
|
$encrypted_answer = $server_dh_params['encrypted_answer'];
|
||||||
|
$tmp_aes_key = sha1($new_nonce.$server_nonce, true).substr(sha1($server_nonce.$new_nonce, true), 0, 12);
|
||||||
|
$tmp_aes_iv = substr(sha1($server_nonce.$new_nonce, true), 12, 8).sha1($new_nonce.$new_nonce, true).substr($new_nonce, 0, 4);
|
||||||
|
$answer_with_hash = \danog\MadelineProto\Crypt::ige_decrypt($encrypted_answer, $tmp_aes_key, $tmp_aes_iv);
|
||||||
|
|
||||||
|
// Separate answer and hash
|
||||||
|
$answer_hash = substr($answer_with_hash, 0, 20);
|
||||||
|
$answer = substr($answer_with_hash, 20);
|
||||||
|
|
||||||
|
// Deserialize
|
||||||
|
$server_DH_inner_data = $this->tl->deserialize(\danog\MadelineProto\Tools::fopen_and_write('php://memory', 'rw+b', $answer));
|
||||||
|
|
||||||
|
// Do some checks
|
||||||
|
$server_DH_inner_data_length = $this->tl->get_length(\danog\MadelineProto\Tools::fopen_and_write('php://memory', 'rw+b', $answer));
|
||||||
|
if (sha1(substr($answer, 0, $server_DH_inner_data_length), true) != $answer_hash) {
|
||||||
|
throw new Exception('Handshake: answer_hash mismatch.');
|
||||||
|
}
|
||||||
|
if ($nonce != $server_DH_inner_data['nonce']) {
|
||||||
|
throw new Exception('Handshake: wrong nonce');
|
||||||
|
}
|
||||||
|
if ($server_nonce != $server_DH_inner_data['server_nonce']) {
|
||||||
|
throw new Exception('Handshake: wrong server nonce');
|
||||||
|
}
|
||||||
|
$g = new \phpseclib\Math\BigInteger($server_DH_inner_data['g']);
|
||||||
|
$g_a = new \phpseclib\Math\BigInteger($server_DH_inner_data['g_a'], 256);
|
||||||
|
$dh_prime = new \phpseclib\Math\BigInteger($server_DH_inner_data['dh_prime'], 256);
|
||||||
|
|
||||||
|
// Time delta
|
||||||
|
$server_time = $server_DH_inner_data['server_time'];
|
||||||
|
$this->timedelta = ($server_time - time());
|
||||||
|
$this->log->log(sprintf('Server-client time delta = %.1f s', $this->timedelta));
|
||||||
|
|
||||||
|
|
||||||
|
// Define some needed numbers for BigInteger
|
||||||
|
$one = new \phpseclib\Math\BigInteger(1);
|
||||||
|
$two = new \phpseclib\Math\BigInteger(2);
|
||||||
|
$twoe2047 = new \phpseclib\Math\BigInteger('16158503035655503650357438344334975980222051334857742016065172713762327569433945446598600705761456731844358980460949009747059779575245460547544076193224141560315438683650498045875098875194826053398028819192033784138396109321309878080919047169238085235290822926018152521443787945770532904303776199561965192760957166694834171210342487393282284747428088017663161029038902829665513096354230157075129296432088558362971801859230928678799175576150822952201848806616643615613562842355410104862578550863465661734839271290328348967522998634176499319107762583194718667771801067716614802322659239302476074096777926805529798115328');
|
||||||
|
$twoe2048 = new \phpseclib\Math\BigInteger('32317006071311007300714876688669951960444102669715484032130345427524655138867890893197201411522913463688717960921898019494119559150490921095088152386448283120630877367300996091750197750389652106796057638384067568276792218642619756161838094338476170470581645852036305042887575891541065808607552399123930385521914333389668342420684974786564569494856176035326322058077805659331026192708460314150258592864177116725943603718461857357598351152301645904403697613233287231227125684710820209725157101726931323469678542580656697935045997268352998638215525166389437335543602135433229604645318478604952148193555853611059596230656');
|
||||||
|
|
||||||
|
// Check validity of dh_prime
|
||||||
|
if (!$dh_prime->isPrime()) {
|
||||||
|
throw new Exception("Handshake: dh_prime isn't a safe 2048-bit prime (dh_prime isn't a prime).");
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
// Almost always fails
|
||||||
|
if (!$dh_prime->subtract($one)->divide($two)[0]->isPrime()) {
|
||||||
|
throw new Exception("Handshake: dh_prime isn't a safe 2048-bit prime ((dh_prime - 1) / 2 isn't a prime).");
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
// 2^2047 < dh_prime < 2^2048
|
||||||
|
if ($dh_prime->compare($twoe2047) <= 0 // 2^2047 < dh_prime or dh_prime > 2^2047 or ! dh_prime <= 2^2047
|
||||||
|
|| $dh_prime->compare($twoe2048) >= 0 // dh_prime < 2^2048 or ! dh_prime >= 2^2048
|
||||||
|
) {
|
||||||
|
throw new Exception("Handshake: g isn't a safe 2048-bit prime (2^2047 < dh_prime < 2^2048 is false).");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check validity of g
|
||||||
|
// 1 < g < dh_prime - 1
|
||||||
|
if ($g->compare($one) <= 0 // 1 < g or g > 1 or ! g <= 1
|
||||||
|
|| $g->compare($dh_prime->subtract($one)) >= 0 // g < dh_prime - 1 or ! g >= dh_prime - 1
|
||||||
|
) {
|
||||||
|
throw new Exception('Handshake: g is invalid (1 < g < dh_prime - 1 is false).');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check validity of g_a
|
||||||
|
// 1 < g_a < dh_prime - 1
|
||||||
|
if ($g_a->compare($one) <= 0 // 1 < g_a or g_a > 1 or ! g_a <= 1
|
||||||
|
|| $g_a->compare($dh_prime->subtract($one)) >= 0 // g_a < dh_prime - 1 or ! g_a >= dh_prime - 1
|
||||||
|
) {
|
||||||
|
throw new Exception('Handshake: g_a is invalid (1 < g_a < dh_prime - 1 is false).');
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (\danog\MadelineProto\Tools::range(0, $this->settings['max_tries']['authorization']) as $retry_id) {
|
||||||
|
$b = new \phpseclib\Math\BigInteger(\phpseclib\Crypt\Random::string(256), 256);
|
||||||
|
$g_b = $g->powMod($b, $dh_prime);
|
||||||
|
|
||||||
|
// Check validity of g_b
|
||||||
|
// 1 < g_b < dh_prime - 1
|
||||||
|
if ($g_b->compare($one) <= 0 // 1 < g_b or g_b > 1 or ! g_b <= 1
|
||||||
|
|| $g_b->compare($dh_prime->subtract($one)) >= 0 // g_b < dh_prime - 1 or ! g_b >= dh_prime - 1
|
||||||
|
) {
|
||||||
|
throw new Exception('Handshake: g_b is invalid (1 < g_b < dh_prime - 1 is false).');
|
||||||
|
}
|
||||||
|
|
||||||
|
$g_b_str = $g_b->toBytes();
|
||||||
|
|
||||||
|
// serialize client_DH_inner_data
|
||||||
|
$data = $this->tl->serialize_obj('client_DH_inner_data', ['nonce' => $nonce, 'server_nonce' => $server_nonce, 'retry_id' => $retry_id, 'g_b' => $g_b_str]);
|
||||||
|
$data_with_sha = sha1($data, true).$data;
|
||||||
|
$data_with_sha_padded = $data_with_sha.\phpseclib\Crypt\Random::string(\danog\MadelineProto\Tools::posmod(-strlen($data_with_sha), 16));
|
||||||
|
|
||||||
|
// encrypt client_DH_inner_data
|
||||||
|
$encrypted_data = \danog\MadelineProto\Crypt::ige_encrypt($data_with_sha_padded, $tmp_aes_key, $tmp_aes_iv);
|
||||||
|
|
||||||
|
// Send set_client_DH_params query
|
||||||
|
$Set_client_DH_params_answer = $this->method_call('set_client_DH_params', ['nonce' => $nonce, 'server_nonce' => $server_nonce, 'encrypted_data' => $encrypted_data]);
|
||||||
|
|
||||||
|
// Generate auth_key
|
||||||
|
$auth_key = $g_a->powMod($b, $dh_prime);
|
||||||
|
$auth_key_str = $auth_key->toBytes();
|
||||||
|
$auth_key_sha = sha1($auth_key_str, true);
|
||||||
|
$auth_key_aux_hash = substr($auth_key_sha, 0, 8);
|
||||||
|
$new_nonce_hash1 = substr(sha1($new_nonce.chr(1).$auth_key_aux_hash, true), -16);
|
||||||
|
$new_nonce_hash2 = substr(sha1($new_nonce.chr(2).$auth_key_aux_hash, true), -16);
|
||||||
|
$new_nonce_hash3 = substr(sha1($new_nonce.chr(3).$auth_key_aux_hash, true), -16);
|
||||||
|
|
||||||
|
if ($Set_client_DH_params_answer['nonce'] != $nonce) {
|
||||||
|
throw new Exception('Handshake: wrong nonce.');
|
||||||
|
}
|
||||||
|
if ($Set_client_DH_params_answer['server_nonce'] != $server_nonce) {
|
||||||
|
throw new Exception('Handshake: wrong server nonce');
|
||||||
|
}
|
||||||
|
if ($Set_client_DH_params_answer['_'] == 'dh_gen_ok') {
|
||||||
|
if ($Set_client_DH_params_answer['new_nonce_hash1'] != $new_nonce_hash1) {
|
||||||
|
throw new Exception('Handshake: wrong new_nonce_hash1');
|
||||||
|
}
|
||||||
|
$this->log->log('Diffie Hellman key exchange processed successfully');
|
||||||
|
|
||||||
|
$res_authorization = ['server_salt' => substr($new_nonce, 0, 8 - 0) ^ substr($server_nonce, 0, 8 - 0)];
|
||||||
|
$res_authorization['auth_key'] = $auth_key_str;
|
||||||
|
$res_authorization['id'] = substr($auth_key_sha, -8);
|
||||||
|
if ($expires_in < 0) {
|
||||||
|
$res_authorization['expires_in'] = $expires_in;
|
||||||
|
}
|
||||||
|
$this->log->log('Auth key generated');
|
||||||
|
$this->timedelta = 0;
|
||||||
|
|
||||||
|
return $res_authorization;
|
||||||
|
} elseif ($Set_client_DH_params_answer['_'] == 'dh_gen_retry') {
|
||||||
|
if ($Set_client_DH_params_answer['new_nonce_hash2'] != $new_nonce_hash2) {
|
||||||
|
throw new Exception('Handshake: wrong new_nonce_hash_2');
|
||||||
|
}
|
||||||
|
$this->log->log('Retrying Auth');
|
||||||
|
} elseif ($Set_client_DH_params_answer['_'] == 'dh_gen_fail') {
|
||||||
|
if ($Set_client_DH_params_answer['new_nonce_hash3'] != $new_nonce_hash3) {
|
||||||
|
throw new Exception('Handshake: wrong new_nonce_hash_3');
|
||||||
|
}
|
||||||
|
$this->log->log('Auth Failed');
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
throw new Exception('Response Error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Exception('Auth Failed');
|
||||||
|
}
|
||||||
|
}
|
68
src/danog/MadelineProto/MTProtoTools/CallHandler.php
Normal file
68
src/danog/MadelineProto/MTProtoTools/CallHandler.php
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
<?php
|
||||||
|
/*
|
||||||
|
Copyright 2016 Daniil Gentili
|
||||||
|
(https://daniil.it)
|
||||||
|
This file is part of MadelineProto.
|
||||||
|
MadelineProto is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||||
|
MadelineProto is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
|
See the GNU Affero General Public License for more details.
|
||||||
|
You should have received a copy of the GNU General Public License along with the MadelineProto.
|
||||||
|
If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace danog\MadelineProto\MTProtoTools;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages method and object calls.
|
||||||
|
*/
|
||||||
|
class CallHandler extends AuthKeyHandler
|
||||||
|
{
|
||||||
|
public function method_call($method, $args)
|
||||||
|
{
|
||||||
|
$opts = $this->tl->get_opts($method);
|
||||||
|
foreach (range(1, $this->settings['max_tries']['query']) as $i) {
|
||||||
|
try {
|
||||||
|
$this->send_message($this->tl->serialize_method($method, $args), $this->tl->content_related($method));
|
||||||
|
if ($opts['requires_answer'] || true) {
|
||||||
|
$server_answer = $this->recv_message();
|
||||||
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$this->log->log('An error occurred while calling method '.$method.': '.$e->getMessage().' in '.$e->getFile().':'.$e->getLine().'. Recreating connection and retrying to call method...');
|
||||||
|
unset($this->sock);
|
||||||
|
$this->sock = new Connection($this->settings['connection']['ip_address'], $this->settings['connection']['port'], $this->settings['connection']['protocol']);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($server_answer == null) {
|
||||||
|
throw new Exception('An error occurred while calling method '.$method.'.');
|
||||||
|
}
|
||||||
|
$deserialized = $this->tl->deserialize(\danog\MadelineProto\Tools::fopen_and_write('php://memory', 'rw+b', $server_answer));
|
||||||
|
|
||||||
|
return $this->handle_response($deserialized, $method, $args);
|
||||||
|
}
|
||||||
|
throw new Exception('An error occurred while calling method '.$method.'.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function object_call($object, $kwargs)
|
||||||
|
{
|
||||||
|
foreach (range(1, $this->settings['max_tries']['query']) as $i) {
|
||||||
|
try {
|
||||||
|
$this->send_message($this->tl->serialize_obj($object, $kwargs), $this->tl->content_related($object));
|
||||||
|
// $server_answer = $this->recv_message();
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$this->log->log('An error occurred while calling object '.$object.': '.$e->getMessage().' in '.$e->getFile().':'.$e->getLine().'. Recreating connection and retrying to call object...');
|
||||||
|
unset($this->sock);
|
||||||
|
$this->sock = new Connection($this->settings['connection']['ip_address'], $this->settings['connection']['port'], $this->settings['connection']['protocol']);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
// if ($server_answer == null) {
|
||||||
|
// throw new Exception('An error occurred while calling object '.$object.'.');
|
||||||
|
// }
|
||||||
|
// $deserialized = $this->tl->deserialize(\danog\MadelineProto\Tools::fopen_and_write('php://memory', 'rw+b', $server_answer));
|
||||||
|
// return $deserialized;
|
||||||
|
}
|
||||||
|
throw new Exception('An error occurred while calling object '.$object.'.');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
29
src/danog/MadelineProto/MTProtoTools/Crypt.php
Normal file
29
src/danog/MadelineProto/MTProtoTools/Crypt.php
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
/*
|
||||||
|
Copyright 2016 Daniil Gentili
|
||||||
|
(https://daniil.it)
|
||||||
|
This file is part of MadelineProto.
|
||||||
|
MadelineProto is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||||
|
MadelineProto is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
|
See the GNU Affero General Public License for more details.
|
||||||
|
You should have received a copy of the GNU General Public License along with the MadelineProto.
|
||||||
|
If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace danog\MadelineProto\MTProtoTools;
|
||||||
|
|
||||||
|
class Crypt extends CallHandler
|
||||||
|
{
|
||||||
|
public function aes_calculate($msg_key, $direction = 'to server')
|
||||||
|
{
|
||||||
|
$x = ($direction == 'to server') ? 0 : 8;
|
||||||
|
$sha1_a = sha1($msg_key.substr($this->settings['authorization']['temp_auth_key']['auth_key'], $x, ($x + 32) - $x), true);
|
||||||
|
$sha1_b = sha1(substr($this->settings['authorization']['temp_auth_key']['auth_key'], ($x + 32), ($x + 48) - ($x + 32)).$msg_key.substr($this->settings['authorization']['temp_auth_key']['auth_key'], (48 + $x), (64 + $x) - (48 + $x)), true);
|
||||||
|
$sha1_c = sha1(substr($this->settings['authorization']['temp_auth_key']['auth_key'], ($x + 64), ($x + 96) - ($x + 64)).$msg_key, true);
|
||||||
|
$sha1_d = sha1($msg_key.substr($this->settings['authorization']['temp_auth_key']['auth_key'], ($x + 96), ($x + 128) - ($x + 96)), true);
|
||||||
|
$aes_key = substr($sha1_a, 0, 8 - 0).substr($sha1_b, 8, 20 - 8).substr($sha1_c, 4, 16 - 4);
|
||||||
|
$aes_iv = substr($sha1_a, 8, 20 - 8).substr($sha1_b, 0, 8 - 0).substr($sha1_c, 16, 20 - 16).substr($sha1_d, 0, 8 - 0);
|
||||||
|
|
||||||
|
return [$aes_key, $aes_iv];
|
||||||
|
}
|
||||||
|
}
|
112
src/danog/MadelineProto/MTProtoTools/MessageHandler.php
Normal file
112
src/danog/MadelineProto/MTProtoTools/MessageHandler.php
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
<?php
|
||||||
|
/*
|
||||||
|
Copyright 2016 Daniil Gentili
|
||||||
|
(https://daniil.it)
|
||||||
|
This file is part of MadelineProto.
|
||||||
|
MadelineProto is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||||
|
MadelineProto is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
|
See the GNU Affero General Public License for more details.
|
||||||
|
You should have received a copy of the GNU General Public License along with the MadelineProto.
|
||||||
|
If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace danog\MadelineProto\MTProtoTools;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages packing and unpacking of messages
|
||||||
|
*/
|
||||||
|
class MessageHandler extends Crypt
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forming the message frame and sending message to server
|
||||||
|
* :param message: byte string to send.
|
||||||
|
*/
|
||||||
|
public function send_message($message_data, $content_related)
|
||||||
|
{
|
||||||
|
$int_message_id = (int) ((time() + $this->timedelta) * pow(2, 30)) * 4;
|
||||||
|
$message_id = $this->struct->pack('<Q', $int_message_id);
|
||||||
|
$this->check_message_id($int_message_id, true);
|
||||||
|
if (($this->settings['authorization']['temp_auth_key']['auth_key'] == null) || ($this->settings['authorization']['temp_auth_key']['server_salt'] == null)) {
|
||||||
|
$message = \danog\MadelineProto\Tools::string2bin('\x00\x00\x00\x00\x00\x00\x00\x00').$message_id.$this->struct->pack('<I', strlen($message_data)).$message_data;
|
||||||
|
$this->last_sent = ['message_id' => $int_message_id];
|
||||||
|
} else {
|
||||||
|
$seq_no = $this->generate_seq_no($content_related);
|
||||||
|
$encrypted_data = $this->settings['authorization']['temp_auth_key']['server_salt'].$this->settings['authorization']['session_id'].$message_id.$this->struct->pack('<II', $seq_no, strlen($message_data)).$message_data;
|
||||||
|
$message_key = substr(sha1($encrypted_data, true), -16);
|
||||||
|
$padding = \phpseclib\Crypt\Random::string(\danog\MadelineProto\Tools::posmod(-strlen($encrypted_data), 16));
|
||||||
|
list($aes_key, $aes_iv) = $this->aes_calculate($message_key);
|
||||||
|
$message = $this->settings['authorization']['temp_auth_key']['id'].$message_key.\danog\MadelineProto\Crypt::ige_encrypt($encrypted_data.$padding, $aes_key, $aes_iv);
|
||||||
|
$this->last_sent = ['message_id' => $int_message_id, 'seq_no' => $seq_no];
|
||||||
|
}
|
||||||
|
$this->sock->send_message($message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reading socket and receiving message from server. Check the CRC32.
|
||||||
|
*/
|
||||||
|
public function recv_message()
|
||||||
|
{
|
||||||
|
$payload = $this->sock->read_message();
|
||||||
|
if (fstat($payload)['size'] == 4) {
|
||||||
|
throw new Exception('Server response error: '.abs($this->struct->unpack('<i', fread($payload, 4))[0]));
|
||||||
|
}
|
||||||
|
$auth_key_id = fread($payload, 8);
|
||||||
|
if ($auth_key_id == \danog\MadelineProto\Tools::string2bin('\x00\x00\x00\x00\x00\x00\x00\x00')) {
|
||||||
|
list($message_id, $message_length) = $this->struct->unpack('<QI', fread($payload, 12));
|
||||||
|
$this->check_message_id($message_id, false);
|
||||||
|
$message_data = fread($payload, $message_length);
|
||||||
|
$this->last_received = ['message_id' => $message_id];
|
||||||
|
} elseif ($auth_key_id == $this->settings['authorization']['temp_auth_key']['id']) {
|
||||||
|
$message_key = fread($payload, 16);
|
||||||
|
$encrypted_data = stream_get_contents($payload);
|
||||||
|
list($aes_key, $aes_iv) = $this->aes_calculate($message_key, 'from server');
|
||||||
|
$decrypted_data = \danog\MadelineProto\Crypt::ige_decrypt($encrypted_data, $aes_key, $aes_iv);
|
||||||
|
|
||||||
|
$server_salt = substr($decrypted_data, 0, 8);
|
||||||
|
if ($server_salt != $this->settings['authorization']['temp_auth_key']['server_salt']) {
|
||||||
|
throw new Exception('Server salt mismatch.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$session_id = substr($decrypted_data, 8, 8);
|
||||||
|
if ($session_id != $this->settings['authorization']['session_id']) {
|
||||||
|
throw new Exception('Session id mismatch.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$message_id = $this->struct->unpack('<Q', substr($decrypted_data, 16, 8))[0];
|
||||||
|
$this->check_message_id($message_id, false);
|
||||||
|
|
||||||
|
$seq_no = $this->struct->unpack('<I', substr($decrypted_data, 24, 4)) [0];
|
||||||
|
// Dunno how to handle any incorrect sequence numbers
|
||||||
|
|
||||||
|
$message_data_length = $this->struct->unpack('<I', substr($decrypted_data, 28, 4)) [0];
|
||||||
|
|
||||||
|
if ($message_data_length > strlen($decrypted_data)) {
|
||||||
|
throw new Exception('message_data_length is too big');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((strlen($decrypted_data) - 32) - $message_data_length > 15) {
|
||||||
|
throw new Exception('difference between message_data_length and the length of the remaining decrypted buffer is too big');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($message_data_length < 0) {
|
||||||
|
throw new Exception('message_data_length not positive');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($message_data_length % 4 != 0) {
|
||||||
|
throw new Exception('message_data_length not divisible by 4');
|
||||||
|
}
|
||||||
|
|
||||||
|
$message_data = substr($decrypted_data, 32, $message_data_length);
|
||||||
|
if ($message_key != substr(sha1(substr($decrypted_data, 0, 32 + $message_data_length), true), -16)) {
|
||||||
|
throw new Exception('msg_key mismatch');
|
||||||
|
}
|
||||||
|
$this->last_received = ['message_id' => $message_id, 'seq_no' => $seq_no];
|
||||||
|
} else {
|
||||||
|
throw new Exception('Got unknown auth_key id');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $message_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
53
src/danog/MadelineProto/MTProtoTools/MsgIdHandler.php
Normal file
53
src/danog/MadelineProto/MTProtoTools/MsgIdHandler.php
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
<?php
|
||||||
|
/*
|
||||||
|
Copyright 2016 Daniil Gentili
|
||||||
|
(https://daniil.it)
|
||||||
|
This file is part of MadelineProto.
|
||||||
|
MadelineProto is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||||
|
MadelineProto is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
|
See the GNU Affero General Public License for more details.
|
||||||
|
You should have received a copy of the GNU General Public License along with the MadelineProto.
|
||||||
|
If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace danog\MadelineProto\MTProtoTools;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages message ids.
|
||||||
|
*/
|
||||||
|
class MsgIdHandler extends MessageHandler
|
||||||
|
{
|
||||||
|
|
||||||
|
public function check_message_id($new_message_id, $outgoing)
|
||||||
|
{
|
||||||
|
if (((int) ((time() + $this->timedelta - 300) * pow(2, 30)) * 4) > $new_message_id) {
|
||||||
|
throw new Exception('Given message id ('.$new_message_id.') is too old.');
|
||||||
|
}
|
||||||
|
if (((int) ((time() + $this->timedelta + 30) * pow(2, 30)) * 4) < $new_message_id) {
|
||||||
|
throw new Exception('Given message id ('.$new_message_id.') is too new.');
|
||||||
|
}
|
||||||
|
if ($outgoing) {
|
||||||
|
if ($new_message_id % 4 != 0) {
|
||||||
|
throw new Exception('Given message id ('.$new_message_id.') is not divisible by 4.');
|
||||||
|
}
|
||||||
|
$this->outgoing_message_ids[] = $new_message_id;
|
||||||
|
if (count($this->outgoing_message_ids) > $this->settings['authorization']['message_ids_limit']) {
|
||||||
|
array_shift($this->outgoing_message_ids);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ($new_message_id % 4 != 1 && $new_message_id % 4 != 3) {
|
||||||
|
throw new Exception('message id mod 4 != 1 or 3');
|
||||||
|
}
|
||||||
|
foreach ($this->incoming_message_ids as $message_id) {
|
||||||
|
if ($new_message_id <= $message_id) {
|
||||||
|
throw new Exception('Given message id ('.$new_message_id.') is lower than or equal than the current limit ('.$message_id.').');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->incoming_message_ids[] = $new_message_id;
|
||||||
|
if (count($this->incoming_message_ids) > $this->settings['authorization']['message_ids_limit']) {
|
||||||
|
array_shift($this->incoming_message_ids);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
107
src/danog/MadelineProto/MTProtoTools/ResponseHandler.php
Normal file
107
src/danog/MadelineProto/MTProtoTools/ResponseHandler.php
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
<?php
|
||||||
|
/*
|
||||||
|
Copyright 2016 Daniil Gentili
|
||||||
|
(https://daniil.it)
|
||||||
|
This file is part of MadelineProto.
|
||||||
|
MadelineProto is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||||
|
MadelineProto is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
|
See the GNU Affero General Public License for more details.
|
||||||
|
You should have received a copy of the GNU General Public License along with the MadelineProto.
|
||||||
|
If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace danog\MadelineProto\MTProtoTools;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages responses.
|
||||||
|
*/
|
||||||
|
class ResponseHandler extends MsgIdHandler
|
||||||
|
{
|
||||||
|
public function handle_response($response, $name, $args)
|
||||||
|
{
|
||||||
|
switch ($response['_']) {
|
||||||
|
case 'rpc_result':
|
||||||
|
$this->ack_incoming_message_id($this->last_received['message_id']); // Acknowledge that I received the server's response
|
||||||
|
$this->ack_outgoing_message_id($response['req_msg_id']); // Acknowledge that the server received my
|
||||||
|
if ($response['req_msg_id'] != $this->last_sent['message_id']) {
|
||||||
|
throw new Exception('Message id mismatch; req_msg_id ('.$response['req_msg_id'].') != last sent msg id ('.$this->last_sent['message_id'].').');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->handle_response($response['result'], $name, $args);
|
||||||
|
break;
|
||||||
|
case 'rpc_error':
|
||||||
|
throw new Exception('Got rpc error '.$response['error_code'].': '.$response['error_message']);
|
||||||
|
break;
|
||||||
|
case 'rpc_answer_unknown':
|
||||||
|
$this->ack_outgoing_message_id($this->last_sent['message_id']); // Acknowledge that the server received my message
|
||||||
|
return $response; // I'm not handling this error
|
||||||
|
break;
|
||||||
|
case 'rpc_answer_dropped_running':
|
||||||
|
$this->ack_incoming_message_id($this->last_received['message_id']); // Acknowledge that I received the server's response
|
||||||
|
$this->ack_outgoing_message_id($this->last_sent['message_id']); // Acknowledge that the server received my message
|
||||||
|
|
||||||
|
$this->ack_outgoing_message_id($response['req_msg_id']); // Acknowledge that the server received the original query (the same one, the response to which we wish to forget)
|
||||||
|
return $response; // I'm not handling this
|
||||||
|
break;
|
||||||
|
case 'rpc_answer_dropped':
|
||||||
|
$this->ack_incoming_message_id($this->last_received['message_id']); // Acknowledge that I received the server's response
|
||||||
|
$this->ack_outgoing_message_id($this->last_sent['message_id']); // Acknowledge that the server received my message
|
||||||
|
|
||||||
|
$this->ack_outgoing_message_id($response['req_msg_id']); // Acknowledge that the server received the original query (the same one, the response to which we wish to forget)
|
||||||
|
return $response; // I'm not handling this
|
||||||
|
break;
|
||||||
|
case 'future_salts':
|
||||||
|
$this->ack_outgoing_message_id($this->last_sent['message_id']); // Acknowledge that the server received my message
|
||||||
|
if ($response['req_msg_id'] != $this->last_sent['message_id']) {
|
||||||
|
throw new Exception('Message id mismatch; req_msg_id ('.$response['req_msg_id'].') != last sent msg id ('.$this->last_sent['message_id'].').');
|
||||||
|
}
|
||||||
|
$this->log->log('Received future salts.');
|
||||||
|
$this->future_salts = $response['salts'];
|
||||||
|
break;
|
||||||
|
case 'pong':
|
||||||
|
$this->ack_incoming_message_id($this->last_received['message_id']); // Acknowledge that I received the server's response
|
||||||
|
$this->ack_outgoing_message_id($this->last_sent['message_id']); // Acknowledge that the server received my message
|
||||||
|
$this->log->log('pong');
|
||||||
|
break;
|
||||||
|
case 'msgs_ack':
|
||||||
|
foreach ($response['msg_ids'] as $msg_id) {
|
||||||
|
$this->ack_outgoing_message_id($msg_id); // Acknowledge that the server received my message
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'new_session_created':
|
||||||
|
$this->ack_incoming_message_id($this->last_received['message_id']); // Acknowledge that I received the server's response
|
||||||
|
$this->log->log('new session created');
|
||||||
|
$this->log->log($response);
|
||||||
|
break;
|
||||||
|
case 'msg_container':
|
||||||
|
$responses = [];
|
||||||
|
$this->log->log('Received container.');
|
||||||
|
$this->log->log($response['messages']);
|
||||||
|
foreach ($response['messages'] as $message) {
|
||||||
|
$this->last_recieved = ['message_id' => $message['msg_id'], 'seq_no' => $message['seqno']];
|
||||||
|
$responses[] = $this->handle_response($message['body'], $name, $args);
|
||||||
|
}
|
||||||
|
foreach ($responses as $response) {
|
||||||
|
if ($response != null) {
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'msg_copy':
|
||||||
|
$this->handle_response($response['orig_message'], $name, $args);
|
||||||
|
break;
|
||||||
|
case 'gzip_packed':
|
||||||
|
return $this->handle_response(gzdecode($response));
|
||||||
|
break;
|
||||||
|
case 'http_wait':
|
||||||
|
$this->log->log('Received http wait.');
|
||||||
|
$this->log->log($response);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$this->ack_incoming_message_id($this->last_received['message_id']); // Acknowledge that I received the server's response
|
||||||
|
return $response;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
30
src/danog/MadelineProto/MTProtoTools/SeqNoHandler.php
Normal file
30
src/danog/MadelineProto/MTProtoTools/SeqNoHandler.php
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
/*
|
||||||
|
Copyright 2016 Daniil Gentili
|
||||||
|
(https://daniil.it)
|
||||||
|
This file is part of MadelineProto.
|
||||||
|
MadelineProto is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||||
|
MadelineProto is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
|
See the GNU Affero General Public License for more details.
|
||||||
|
You should have received a copy of the GNU General Public License along with the MadelineProto.
|
||||||
|
If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace danog\MadelineProto\MTProtoTools;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages sequence number.
|
||||||
|
*/
|
||||||
|
class SeqNoHandler extends ResponseHandler
|
||||||
|
{
|
||||||
|
|
||||||
|
public function generate_seq_no($content_related = true)
|
||||||
|
{
|
||||||
|
$in = $content_related ? 1 : 0;
|
||||||
|
$value = $this->seq_no;
|
||||||
|
$this->seq_no += $in;
|
||||||
|
|
||||||
|
return ($value * 2) + $in;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,646 +0,0 @@
|
|||||||
<?php
|
|
||||||
/*
|
|
||||||
Copyright 2016 Daniil Gentili
|
|
||||||
(https://daniil.it)
|
|
||||||
This file is part of MadelineProto.
|
|
||||||
MadelineProto is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
|
||||||
MadelineProto is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
||||||
See the GNU Affero General Public License for more details.
|
|
||||||
You should have received a copy of the GNU General Public License along with the MadelineProto.
|
|
||||||
If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace danog\MadelineProto;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Manages session and the creation of authorization keys.
|
|
||||||
*/
|
|
||||||
class Session extends Tools
|
|
||||||
{
|
|
||||||
public $settings = [];
|
|
||||||
|
|
||||||
public function __construct($settings = [])
|
|
||||||
{
|
|
||||||
// Set default settings
|
|
||||||
$default_settings = [
|
|
||||||
'authorization' => [
|
|
||||||
'auth_key' => null,
|
|
||||||
'temp_auth_key' => null,
|
|
||||||
'default_temp_auth_key_expires_in' => 86400,
|
|
||||||
'session_id' => \phpseclib\Crypt\Random::string(8),
|
|
||||||
'rsa_key' => '-----BEGIN RSA PUBLIC KEY-----
|
|
||||||
MIIBCgKCAQEAwVACPi9w23mF3tBkdZz+zwrzKOaaQdr01vAbU4E1pvkfj4sqDsm6
|
|
||||||
lyDONS789sVoD/xCS9Y0hkkC3gtL1tSfTlgCMOOul9lcixlEKzwKENj1Yz/s7daS
|
|
||||||
an9tqw3bfUV/nqgbhGX81v/+7RFAEd+RwFnK7a+XYl9sluzHRyVVaTTveB2GazTw
|
|
||||||
Efzk2DWgkBluml8OREmvfraX3bkHZJTKX4EQSjBbbdJ2ZXIsRrYOXfaA+xayEGB+
|
|
||||||
8hdlLmAjbCVfaigxX0CDqWeR1yFL9kwd9P0NsZRPsmoqVwMbMu7mStFai6aIhc3n
|
|
||||||
Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB
|
|
||||||
-----END RSA PUBLIC KEY-----',
|
|
||||||
'message_ids_limit' => 5,
|
|
||||||
],
|
|
||||||
'connection' => [
|
|
||||||
'ip_address' => '149.154.167.50',
|
|
||||||
'port' => '443',
|
|
||||||
'protocol' => 'tcp_full',
|
|
||||||
],
|
|
||||||
'app_info' => [
|
|
||||||
'api_id' => 25628,
|
|
||||||
'api_hash' => '1fe17cda7d355166cdaa71f04122873c',
|
|
||||||
],
|
|
||||||
'tl_schema' => [
|
|
||||||
'layer' => 55,
|
|
||||||
'src' => [
|
|
||||||
__DIR__.'/TL_mtproto_v1.json',
|
|
||||||
__DIR__.'/TL_telegram_v55.json',
|
|
||||||
],
|
|
||||||
],
|
|
||||||
'logging' => [
|
|
||||||
'logging' => 1,
|
|
||||||
'logging_param' => '/tmp/MadelineProto.log',
|
|
||||||
'logging' => 3,
|
|
||||||
],
|
|
||||||
'max_tries' => [
|
|
||||||
'query' => 5,
|
|
||||||
'authorization' => 5,
|
|
||||||
],
|
|
||||||
];
|
|
||||||
foreach ($default_settings as $key => $param) {
|
|
||||||
if (!isset($settings[$key])) {
|
|
||||||
$settings[$key] = $param;
|
|
||||||
}
|
|
||||||
foreach ($param as $subkey => $subparam) {
|
|
||||||
if (!isset($settings[$key][$subkey])) {
|
|
||||||
$settings[$key][$subkey] = $subparam;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$this->settings = $settings;
|
|
||||||
|
|
||||||
// Connect to servers
|
|
||||||
$this->sock = new Connection($this->settings['connection']['ip_address'], $this->settings['connection']['port'], $this->settings['connection']['protocol']);
|
|
||||||
|
|
||||||
// Load rsa key
|
|
||||||
$this->key = new RSA($settings['authorization']['rsa_key']);
|
|
||||||
// Istantiate struct class
|
|
||||||
$this->struct = new \danog\PHP\StructTools();
|
|
||||||
// Istantiate prime class
|
|
||||||
$this->PrimeModule = new PrimeModule();
|
|
||||||
// Istantiate TL class
|
|
||||||
$this->tl = new TL\TL($this->settings['tl_schema']['src']);
|
|
||||||
// Istantiate logging class
|
|
||||||
$this->log = new Logging($this->settings['logging']['logging'], $this->settings['logging']['logging_param']);
|
|
||||||
|
|
||||||
$this->seq_no = 0;
|
|
||||||
$this->timedelta = 0; // time delta
|
|
||||||
$this->incoming_message_ids = [];
|
|
||||||
$this->outgoing_message_ids = [];
|
|
||||||
$this->ack_incoming_message_ids = [];
|
|
||||||
$this->ack_outgoing_message_ids = [];
|
|
||||||
$this->future_salts = [];
|
|
||||||
|
|
||||||
if ($this->settings['authorization']['temp_auth_key'] == null || $this->settings['authorization']['auth_key'] == null) {
|
|
||||||
if ($this->settings['authorization']['auth_key'] == null) {
|
|
||||||
$this->settings['authorization']['auth_key'] = $this->create_auth_key(-1);
|
|
||||||
}
|
|
||||||
$this->settings['authorization']['temp_auth_key'] = $this->create_auth_key($this->settings['authorization']['default_temp_auth_key_expires_in']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function __destruct()
|
|
||||||
{
|
|
||||||
unset($this->sock);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function check_message_id($new_message_id, $outgoing)
|
|
||||||
{
|
|
||||||
if (((int) ((time() + $this->timedelta - 300) * pow(2, 30)) * 4) > $new_message_id) {
|
|
||||||
throw new Exception('Given message id ('.$new_message_id.') is too old.');
|
|
||||||
}
|
|
||||||
if (((int) ((time() + $this->timedelta + 30) * pow(2, 30)) * 4) < $new_message_id) {
|
|
||||||
throw new Exception('Given message id ('.$new_message_id.') is too new.');
|
|
||||||
}
|
|
||||||
if ($outgoing) {
|
|
||||||
if ($new_message_id % 4 != 0) {
|
|
||||||
throw new Exception('Given message id ('.$new_message_id.') is not divisible by 4.');
|
|
||||||
}
|
|
||||||
$this->outgoing_message_ids[] = $new_message_id;
|
|
||||||
if (count($this->outgoing_message_ids) > $this->settings['authorization']['message_ids_limit']) {
|
|
||||||
array_shift($this->outgoing_message_ids);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if ($new_message_id % 4 != 1 && $new_message_id % 4 != 3) {
|
|
||||||
throw new Exception('message id mod 4 != 1 or 3');
|
|
||||||
}
|
|
||||||
foreach ($this->incoming_message_ids as $message_id) {
|
|
||||||
if ($new_message_id <= $message_id) {
|
|
||||||
throw new Exception('Given message id ('.$new_message_id.') is lower than or equal than the current limit ('.$message_id.').');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$this->incoming_message_ids[] = $new_message_id;
|
|
||||||
if (count($this->incoming_message_ids) > $this->settings['authorization']['message_ids_limit']) {
|
|
||||||
array_shift($this->incoming_message_ids);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function ack_outgoing_message_id($message_id)
|
|
||||||
{
|
|
||||||
// The server acknowledges that it received my message
|
|
||||||
if (!in_array($message_id, $this->outgoing_message_ids)) {
|
|
||||||
throw new Exception("Couldn't find message id ".$message_id.' in the array of outgoing message ids. Maybe try to increase its size?');
|
|
||||||
}
|
|
||||||
$this->ack_outgoing_message_ids[] = $message_id;
|
|
||||||
if (count($this->ack_outgoing_message_ids) > $this->settings['authorization']['message_ids_limit']) {
|
|
||||||
array_shift($this->ack_outgoing_message_ids);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function ack_incoming_message_id($message_id)
|
|
||||||
{
|
|
||||||
if ($this->settings['authorization']['temp_auth_key']['id'] === null || $this->settings['authorization']['temp_auth_key']['id'] == Tools::string2bin('\x00\x00\x00\x00\x00\x00\x00\x00')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// I let the server know that I received its message
|
|
||||||
if (!in_array($message_id, $this->incoming_message_ids)) {
|
|
||||||
throw new Exception("Couldn't find message id ".$message_id.' in the array of incoming message ids. Maybe try to increase its size?');
|
|
||||||
}
|
|
||||||
$this->object_call('msgs_ack', ['msg_ids' => [$message_id]]);
|
|
||||||
$this->ack_incoming_message_ids[] = $message_id;
|
|
||||||
if (count($this->ack_incoming_message_ids) > $this->settings['authorization']['message_ids_limit']) {
|
|
||||||
array_shift($this->ack_incoming_message_ids);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function generate_seq_no($content_related = true)
|
|
||||||
{
|
|
||||||
$in = $content_related ? 1 : 0;
|
|
||||||
$value = $this->seq_no;
|
|
||||||
$this->seq_no += $in;
|
|
||||||
|
|
||||||
return ($value * 2) + $in;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Forming the message frame and sending message to server
|
|
||||||
* :param message: byte string to send.
|
|
||||||
*/
|
|
||||||
public function send_message($message_data, $content_related)
|
|
||||||
{
|
|
||||||
$int_message_id = (int) ((time() + $this->timedelta) * pow(2, 30)) * 4;
|
|
||||||
$message_id = $this->struct->pack('<Q', $int_message_id);
|
|
||||||
$this->check_message_id($int_message_id, true);
|
|
||||||
if (($this->settings['authorization']['temp_auth_key']['auth_key'] == null) || ($this->settings['authorization']['temp_auth_key']['server_salt'] == null)) {
|
|
||||||
$message = Tools::string2bin('\x00\x00\x00\x00\x00\x00\x00\x00').$message_id.$this->struct->pack('<I', strlen($message_data)).$message_data;
|
|
||||||
$this->last_sent = ['message_id' => $int_message_id];
|
|
||||||
} else {
|
|
||||||
$seq_no = $this->generate_seq_no($content_related);
|
|
||||||
$encrypted_data = $this->settings['authorization']['temp_auth_key']['server_salt'].$this->settings['authorization']['session_id'].$message_id.$this->struct->pack('<II', $seq_no, strlen($message_data)).$message_data;
|
|
||||||
$message_key = substr(sha1($encrypted_data, true), -16);
|
|
||||||
$padding = \phpseclib\Crypt\Random::string(Tools::posmod(-strlen($encrypted_data), 16));
|
|
||||||
list($aes_key, $aes_iv) = $this->aes_calculate($message_key);
|
|
||||||
$message = $this->settings['authorization']['temp_auth_key']['id'].$message_key.Crypt::ige_encrypt($encrypted_data.$padding, $aes_key, $aes_iv);
|
|
||||||
$this->last_sent = ['message_id' => $int_message_id, 'seq_no' => $seq_no];
|
|
||||||
}
|
|
||||||
$this->sock->send_message($message);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reading socket and receiving message from server. Check the CRC32.
|
|
||||||
*/
|
|
||||||
public function recv_message()
|
|
||||||
{
|
|
||||||
$payload = $this->sock->read_message();
|
|
||||||
if (fstat($payload)['size'] == 4) {
|
|
||||||
throw new Exception('Server response error: '.abs($this->struct->unpack('<i', fread($payload, 4))[0]));
|
|
||||||
}
|
|
||||||
$auth_key_id = fread($payload, 8);
|
|
||||||
if ($auth_key_id == Tools::string2bin('\x00\x00\x00\x00\x00\x00\x00\x00')) {
|
|
||||||
list($message_id, $message_length) = $this->struct->unpack('<QI', fread($payload, 12));
|
|
||||||
$this->check_message_id($message_id, false);
|
|
||||||
$message_data = fread($payload, $message_length);
|
|
||||||
$this->last_received = ['message_id' => $message_id];
|
|
||||||
} elseif ($auth_key_id == $this->settings['authorization']['temp_auth_key']['id']) {
|
|
||||||
$message_key = fread($payload, 16);
|
|
||||||
$encrypted_data = stream_get_contents($payload);
|
|
||||||
list($aes_key, $aes_iv) = $this->aes_calculate($message_key, 'from server');
|
|
||||||
$decrypted_data = Crypt::ige_decrypt($encrypted_data, $aes_key, $aes_iv);
|
|
||||||
|
|
||||||
$server_salt = substr($decrypted_data, 0, 8);
|
|
||||||
if ($server_salt != $this->settings['authorization']['temp_auth_key']['server_salt']) {
|
|
||||||
throw new Exception('Server salt mismatch.');
|
|
||||||
}
|
|
||||||
|
|
||||||
$session_id = substr($decrypted_data, 8, 8);
|
|
||||||
if ($session_id != $this->settings['authorization']['session_id']) {
|
|
||||||
throw new Exception('Session id mismatch.');
|
|
||||||
}
|
|
||||||
|
|
||||||
$message_id = $this->struct->unpack('<Q', substr($decrypted_data, 16, 8))[0];
|
|
||||||
$this->check_message_id($message_id, false);
|
|
||||||
|
|
||||||
$seq_no = $this->struct->unpack('<I', substr($decrypted_data, 24, 4)) [0];
|
|
||||||
// Dunno how to handle any incorrect sequence numbers
|
|
||||||
|
|
||||||
$message_data_length = $this->struct->unpack('<I', substr($decrypted_data, 28, 4)) [0];
|
|
||||||
|
|
||||||
if ($message_data_length > strlen($decrypted_data)) {
|
|
||||||
throw new Exception('message_data_length is too big');
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((strlen($decrypted_data) - 32) - $message_data_length > 15) {
|
|
||||||
throw new Exception('difference between message_data_length and the length of the remaining decrypted buffer is too big');
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($message_data_length < 0) {
|
|
||||||
throw new Exception('message_data_length not positive');
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($message_data_length % 4 != 0) {
|
|
||||||
throw new Exception('message_data_length not divisible by 4');
|
|
||||||
}
|
|
||||||
|
|
||||||
$message_data = substr($decrypted_data, 32, $message_data_length);
|
|
||||||
if ($message_key != substr(sha1(substr($decrypted_data, 0, 32 + $message_data_length), true), -16)) {
|
|
||||||
throw new Exception('msg_key mismatch');
|
|
||||||
}
|
|
||||||
$this->last_received = ['message_id' => $message_id, 'seq_no' => $seq_no];
|
|
||||||
} else {
|
|
||||||
throw new Exception('Got unknown auth_key id');
|
|
||||||
}
|
|
||||||
|
|
||||||
return $message_data;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function method_call($method, $args)
|
|
||||||
{
|
|
||||||
$opts = $this->tl->get_opts($method);
|
|
||||||
foreach (range(1, $this->settings['max_tries']['query']) as $i) {
|
|
||||||
try {
|
|
||||||
$this->send_message($this->tl->serialize_method($method, $args), $this->tl->content_related($method));
|
|
||||||
if ($opts['requires_answer']) {
|
|
||||||
$server_answer = $this->recv_message();
|
|
||||||
}
|
|
||||||
} catch (Exception $e) {
|
|
||||||
$this->log->log('An error occurred while calling method '.$method.': '.$e->getMessage().' in '.$e->getFile().':'.$e->getLine().'. Recreating connection and retrying to call method...');
|
|
||||||
unset($this->sock);
|
|
||||||
$this->sock = new Connection($this->settings['connection']['ip_address'], $this->settings['connection']['port'], $this->settings['connection']['protocol']);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if ($server_answer == null) {
|
|
||||||
throw new Exception('An error occurred while calling method '.$method.'.');
|
|
||||||
}
|
|
||||||
$deserialized = $this->tl->deserialize(Tools::fopen_and_write('php://memory', 'rw+b', $server_answer));
|
|
||||||
|
|
||||||
return $this->handle_response($deserialized, $method, $args);
|
|
||||||
}
|
|
||||||
throw new Exception('An error occurred while calling method '.$method.'.');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function object_call($object, $kwargs)
|
|
||||||
{
|
|
||||||
foreach (range(1, $this->settings['max_tries']['query']) as $i) {
|
|
||||||
try {
|
|
||||||
$this->send_message($this->tl->serialize_obj($object, $kwargs), $this->tl->content_related($object));
|
|
||||||
// $server_answer = $this->recv_message();
|
|
||||||
} catch (Exception $e) {
|
|
||||||
$this->log->log('An error occurred while calling object '.$object.': '.$e->getMessage().' in '.$e->getFile().':'.$e->getLine().'. Recreating connection and retrying to call object...');
|
|
||||||
unset($this->sock);
|
|
||||||
$this->sock = new Connection($this->settings['connection']['ip_address'], $this->settings['connection']['port'], $this->settings['connection']['protocol']);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
// if ($server_answer == null) {
|
|
||||||
// throw new Exception('An error occurred while calling object '.$object.'.');
|
|
||||||
// }
|
|
||||||
// $deserialized = $this->tl->deserialize(Tools::fopen_and_write('php://memory', 'rw+b', $server_answer));
|
|
||||||
// return $deserialized;
|
|
||||||
}
|
|
||||||
throw new Exception('An error occurred while calling object '.$object.'.');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function handle_response($response, $name, $args)
|
|
||||||
{
|
|
||||||
switch ($response['_']) {
|
|
||||||
case 'rpc_result':
|
|
||||||
$this->ack_incoming_message_id($this->last_received['message_id']); // Acknowledge that I received the server's response
|
|
||||||
$this->ack_outgoing_message_id($response['req_msg_id']); // Acknowledge that the server received my
|
|
||||||
if ($response['req_msg_id'] != $this->last_sent['message_id']) {
|
|
||||||
throw new Exception('Message id mismatch; req_msg_id ('.$response['req_msg_id'].') != last sent msg id ('.$this->last_sent['message_id'].').');
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->handle_response($response['result'], $name, $args);
|
|
||||||
break;
|
|
||||||
case 'rpc_error':
|
|
||||||
throw new Exception('Got rpc error '.$response['error_code'].': '.$response['error_message']);
|
|
||||||
break;
|
|
||||||
case 'rpc_answer_unknown':
|
|
||||||
$this->ack_outgoing_message_id($this->last_sent['message_id']); // Acknowledge that the server received my message
|
|
||||||
return $response; // I'm not handling this error
|
|
||||||
break;
|
|
||||||
case 'rpc_answer_dropped_running':
|
|
||||||
$this->ack_incoming_message_id($this->last_received['message_id']); // Acknowledge that I received the server's response
|
|
||||||
$this->ack_outgoing_message_id($this->last_sent['message_id']); // Acknowledge that the server received my message
|
|
||||||
|
|
||||||
$this->ack_outgoing_message_id($response['req_msg_id']); // Acknowledge that the server received the original query (the same one, the response to which we wish to forget)
|
|
||||||
return $response; // I'm not handling this
|
|
||||||
break;
|
|
||||||
case 'rpc_answer_dropped':
|
|
||||||
$this->ack_incoming_message_id($this->last_received['message_id']); // Acknowledge that I received the server's response
|
|
||||||
$this->ack_outgoing_message_id($this->last_sent['message_id']); // Acknowledge that the server received my message
|
|
||||||
|
|
||||||
$this->ack_outgoing_message_id($response['req_msg_id']); // Acknowledge that the server received the original query (the same one, the response to which we wish to forget)
|
|
||||||
return $response; // I'm not handling this
|
|
||||||
break;
|
|
||||||
case 'future_salts':
|
|
||||||
$this->ack_outgoing_message_id($this->last_sent['message_id']); // Acknowledge that the server received my message
|
|
||||||
if ($response['req_msg_id'] != $this->last_sent['message_id']) {
|
|
||||||
throw new Exception('Message id mismatch; req_msg_id ('.$response['req_msg_id'].') != last sent msg id ('.$this->last_sent['message_id'].').');
|
|
||||||
}
|
|
||||||
$this->log->log('Received future salts.');
|
|
||||||
$this->future_salts = $response['salts'];
|
|
||||||
break;
|
|
||||||
case 'pong':
|
|
||||||
$this->ack_incoming_message_id($this->last_received['message_id']); // Acknowledge that I received the server's response
|
|
||||||
$this->ack_outgoing_message_id($this->last_sent['message_id']); // Acknowledge that the server received my message
|
|
||||||
$this->log->log('pong');
|
|
||||||
break;
|
|
||||||
case 'msgs_ack':
|
|
||||||
foreach ($response['msg_ids'] as $msg_id) {
|
|
||||||
$this->ack_outgoing_message_id($msg_id); // Acknowledge that the server received my message
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'new_session_created':
|
|
||||||
$this->ack_incoming_message_id($this->last_received['message_id']); // Acknowledge that I received the server's response
|
|
||||||
$this->log->log('new session created');
|
|
||||||
$this->log->log($response);
|
|
||||||
break;
|
|
||||||
case 'msg_container':
|
|
||||||
$responses = [];
|
|
||||||
$this->log->log('Received container.');
|
|
||||||
$this->log->log($response['messages']);
|
|
||||||
foreach ($response['messages'] as $message) {
|
|
||||||
$this->last_recieved = ['message_id' => $message['msg_id'], 'seq_no' => $message['seqno']];
|
|
||||||
$responses[] = $this->handle_response($message['body'], $name, $args);
|
|
||||||
}
|
|
||||||
foreach ($responses as $response) {
|
|
||||||
if ($response != null) {
|
|
||||||
return $response;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'msg_copy':
|
|
||||||
$this->handle_response($response['orig_message'], $name, $args);
|
|
||||||
break;
|
|
||||||
case 'gzip_packed':
|
|
||||||
return $this->handle_response(gzdecode($response));
|
|
||||||
break;
|
|
||||||
case 'http_wait':
|
|
||||||
$this->log->log('Received http wait.');
|
|
||||||
$this->log->log($response);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
$this->ack_incoming_message_id($this->last_received['message_id']); // Acknowledge that I received the server's response
|
|
||||||
return $response;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function create_auth_key($expires_in = -1)
|
|
||||||
{
|
|
||||||
foreach (Tools::range(0, $this->settings['max_tries']['authorization']) as $retry_id_total) {
|
|
||||||
// Make pq request
|
|
||||||
$nonce = \phpseclib\Crypt\Random::string(16);
|
|
||||||
$this->log->log('Handshake: Requesting pq');
|
|
||||||
$ResPQ = $this->method_call('req_pq', ['nonce' => $nonce]);
|
|
||||||
$server_nonce = $ResPQ['server_nonce'];
|
|
||||||
if ($ResPQ['nonce'] !== $nonce) {
|
|
||||||
throw new Exception('Handshake: wrong nonce');
|
|
||||||
}
|
|
||||||
foreach ($ResPQ['server_public_key_fingerprints'] as $curfp) {
|
|
||||||
$curfp_biginteger = new \phpseclib\Math\BigInteger($curfp);
|
|
||||||
if ($this->key->fp->equals($curfp_biginteger)) {
|
|
||||||
$public_key_fingerprint = $curfp;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!isset($public_key_fingerprint)) {
|
|
||||||
throw new Exception("Handshake: couldn't find our key in the server_public_key_fingerprints vector.");
|
|
||||||
}
|
|
||||||
$pq_bytes = $ResPQ['pq'];
|
|
||||||
// Compute p and q
|
|
||||||
$pq = new \phpseclib\Math\BigInteger($pq_bytes, 256);
|
|
||||||
list($p, $q) = $this->PrimeModule->primefactors($pq);
|
|
||||||
$p = new \phpseclib\Math\BigInteger($p);
|
|
||||||
$q = new \phpseclib\Math\BigInteger($q);
|
|
||||||
if ($p->compare($q) > 0) {
|
|
||||||
list($p, $q) = [$q, $p];
|
|
||||||
}
|
|
||||||
if (!($pq->equals($p->multiply($q)) && $p->compare($q) < 0)) {
|
|
||||||
throw new Exception("Handshake: couldn't compute p and q.");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
$this->log->log(sprintf('Factorization %s = %s * %s', $pq, $p, $q));
|
|
||||||
|
|
||||||
// Serialize object for req_DH_params
|
|
||||||
$p_bytes = $this->struct->pack('>I', (string) $p);
|
|
||||||
$q_bytes = $this->struct->pack('>I', (string) $q);
|
|
||||||
|
|
||||||
$new_nonce = \phpseclib\Crypt\Random::string(32);
|
|
||||||
if ($expires_in < 0) {
|
|
||||||
$data = $this->tl->serialize_obj('p_q_inner_data', ['pq' => $pq_bytes, 'p' => $p_bytes, 'q' => $q_bytes, 'nonce' => $nonce, 'server_nonce' => $server_nonce, 'new_nonce' => $new_nonce]);
|
|
||||||
} else {
|
|
||||||
$data = $this->tl->serialize_obj('p_q_inner_data_temp', ['pq' => $pq_bytes, 'p' => $p_bytes, 'q' => $q_bytes, 'nonce' => $nonce, 'server_nonce' => $server_nonce, 'new_nonce' => $new_nonce, 'expires_in' => $expires_in]);
|
|
||||||
}
|
|
||||||
$sha_digest = sha1($data, true);
|
|
||||||
|
|
||||||
// Encrypt serialized object
|
|
||||||
$random_bytes = \phpseclib\Crypt\Random::string(255 - strlen($data) - strlen($sha_digest));
|
|
||||||
$to_encrypt = $sha_digest.$data.$random_bytes;
|
|
||||||
$encrypted_data = $this->key->encrypt($to_encrypt);
|
|
||||||
|
|
||||||
// req_DH_params
|
|
||||||
$this->log->log('Starting Diffie Hellman key exchange');
|
|
||||||
$server_dh_params = $this->method_call('req_DH_params',
|
|
||||||
[
|
|
||||||
'nonce' => $nonce,
|
|
||||||
'server_nonce' => $server_nonce,
|
|
||||||
'p' => $p_bytes,
|
|
||||||
'q' => $q_bytes,
|
|
||||||
'public_key_fingerprint' => $public_key_fingerprint,
|
|
||||||
'encrypted_data' => $encrypted_data,
|
|
||||||
]
|
|
||||||
);
|
|
||||||
// Check nonce and server_nonce
|
|
||||||
if ($nonce != $server_dh_params['nonce']) {
|
|
||||||
throw new Exception('Handshake: wrong nonce.');
|
|
||||||
}
|
|
||||||
if ($server_nonce != $server_dh_params['server_nonce']) {
|
|
||||||
throw new Exception('Handshake: wrong server nonce.');
|
|
||||||
}
|
|
||||||
if (isset($server_dh_params['new_nonce_hash']) && substr(sha1($new_nonce), -32) != $server_dh_params['new_nonce_hash']) {
|
|
||||||
throw new Exception('Handshake: wrong new nonce hash.');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get key and iv and decrypt answer
|
|
||||||
$encrypted_answer = $server_dh_params['encrypted_answer'];
|
|
||||||
$tmp_aes_key = sha1($new_nonce.$server_nonce, true).substr(sha1($server_nonce.$new_nonce, true), 0, 12);
|
|
||||||
$tmp_aes_iv = substr(sha1($server_nonce.$new_nonce, true), 12, 8).sha1($new_nonce.$new_nonce, true).substr($new_nonce, 0, 4);
|
|
||||||
$answer_with_hash = Crypt::ige_decrypt($encrypted_answer, $tmp_aes_key, $tmp_aes_iv);
|
|
||||||
|
|
||||||
// Separate answer and hash
|
|
||||||
$answer_hash = substr($answer_with_hash, 0, 20);
|
|
||||||
$answer = substr($answer_with_hash, 20);
|
|
||||||
|
|
||||||
// Deserialize
|
|
||||||
$server_DH_inner_data = $this->tl->deserialize(Tools::fopen_and_write('php://memory', 'rw+b', $answer));
|
|
||||||
|
|
||||||
// Do some checks
|
|
||||||
$server_DH_inner_data_length = $this->tl->get_length(Tools::fopen_and_write('php://memory', 'rw+b', $answer));
|
|
||||||
if (sha1(substr($answer, 0, $server_DH_inner_data_length), true) != $answer_hash) {
|
|
||||||
throw new Exception('Handshake: answer_hash mismatch.');
|
|
||||||
}
|
|
||||||
if ($nonce != $server_DH_inner_data['nonce']) {
|
|
||||||
throw new Exception('Handshake: wrong nonce');
|
|
||||||
}
|
|
||||||
if ($server_nonce != $server_DH_inner_data['server_nonce']) {
|
|
||||||
throw new Exception('Handshake: wrong server nonce');
|
|
||||||
}
|
|
||||||
$g = new \phpseclib\Math\BigInteger($server_DH_inner_data['g']);
|
|
||||||
$g_a = new \phpseclib\Math\BigInteger($server_DH_inner_data['g_a'], 256);
|
|
||||||
$dh_prime = new \phpseclib\Math\BigInteger($server_DH_inner_data['dh_prime'], 256);
|
|
||||||
|
|
||||||
// Time delta
|
|
||||||
$server_time = $server_DH_inner_data['server_time'];
|
|
||||||
$this->timedelta = ($server_time - time());
|
|
||||||
$this->log->log(sprintf('Server-client time delta = %.1f s', $this->timedelta));
|
|
||||||
|
|
||||||
|
|
||||||
// Define some needed numbers for BigInteger
|
|
||||||
$one = new \phpseclib\Math\BigInteger(1);
|
|
||||||
$two = new \phpseclib\Math\BigInteger(2);
|
|
||||||
$twoe2047 = new \phpseclib\Math\BigInteger('16158503035655503650357438344334975980222051334857742016065172713762327569433945446598600705761456731844358980460949009747059779575245460547544076193224141560315438683650498045875098875194826053398028819192033784138396109321309878080919047169238085235290822926018152521443787945770532904303776199561965192760957166694834171210342487393282284747428088017663161029038902829665513096354230157075129296432088558362971801859230928678799175576150822952201848806616643615613562842355410104862578550863465661734839271290328348967522998634176499319107762583194718667771801067716614802322659239302476074096777926805529798115328');
|
|
||||||
$twoe2048 = new \phpseclib\Math\BigInteger('32317006071311007300714876688669951960444102669715484032130345427524655138867890893197201411522913463688717960921898019494119559150490921095088152386448283120630877367300996091750197750389652106796057638384067568276792218642619756161838094338476170470581645852036305042887575891541065808607552399123930385521914333389668342420684974786564569494856176035326322058077805659331026192708460314150258592864177116725943603718461857357598351152301645904403697613233287231227125684710820209725157101726931323469678542580656697935045997268352998638215525166389437335543602135433229604645318478604952148193555853611059596230656');
|
|
||||||
|
|
||||||
// Check validity of dh_prime
|
|
||||||
if (!$dh_prime->isPrime()) {
|
|
||||||
throw new Exception("Handshake: dh_prime isn't a safe 2048-bit prime (dh_prime isn't a prime).");
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
// Almost always fails
|
|
||||||
if (!$dh_prime->subtract($one)->divide($two)[0]->isPrime()) {
|
|
||||||
throw new Exception("Handshake: dh_prime isn't a safe 2048-bit prime ((dh_prime - 1) / 2 isn't a prime).");
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
// 2^2047 < dh_prime < 2^2048
|
|
||||||
if ($dh_prime->compare($twoe2047) <= 0 // 2^2047 < dh_prime or dh_prime > 2^2047 or ! dh_prime <= 2^2047
|
|
||||||
|| $dh_prime->compare($twoe2048) >= 0 // dh_prime < 2^2048 or ! dh_prime >= 2^2048
|
|
||||||
) {
|
|
||||||
throw new Exception("Handshake: g isn't a safe 2048-bit prime (2^2047 < dh_prime < 2^2048 is false).");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check validity of g
|
|
||||||
// 1 < g < dh_prime - 1
|
|
||||||
if ($g->compare($one) <= 0 // 1 < g or g > 1 or ! g <= 1
|
|
||||||
|| $g->compare($dh_prime->subtract($one)) >= 0 // g < dh_prime - 1 or ! g >= dh_prime - 1
|
|
||||||
) {
|
|
||||||
throw new Exception('Handshake: g is invalid (1 < g < dh_prime - 1 is false).');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check validity of g_a
|
|
||||||
// 1 < g_a < dh_prime - 1
|
|
||||||
if ($g_a->compare($one) <= 0 // 1 < g_a or g_a > 1 or ! g_a <= 1
|
|
||||||
|| $g_a->compare($dh_prime->subtract($one)) >= 0 // g_a < dh_prime - 1 or ! g_a >= dh_prime - 1
|
|
||||||
) {
|
|
||||||
throw new Exception('Handshake: g_a is invalid (1 < g_a < dh_prime - 1 is false).');
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (Tools::range(0, $this->settings['max_tries']['authorization']) as $retry_id) {
|
|
||||||
$b = new \phpseclib\Math\BigInteger(\phpseclib\Crypt\Random::string(256), 256);
|
|
||||||
$g_b = $g->powMod($b, $dh_prime);
|
|
||||||
|
|
||||||
// Check validity of g_b
|
|
||||||
// 1 < g_b < dh_prime - 1
|
|
||||||
if ($g_b->compare($one) <= 0 // 1 < g_b or g_b > 1 or ! g_b <= 1
|
|
||||||
|| $g_b->compare($dh_prime->subtract($one)) >= 0 // g_b < dh_prime - 1 or ! g_b >= dh_prime - 1
|
|
||||||
) {
|
|
||||||
throw new Exception('Handshake: g_b is invalid (1 < g_b < dh_prime - 1 is false).');
|
|
||||||
}
|
|
||||||
|
|
||||||
$g_b_str = $g_b->toBytes();
|
|
||||||
|
|
||||||
// serialize client_DH_inner_data
|
|
||||||
$data = $this->tl->serialize_obj('client_DH_inner_data', ['nonce' => $nonce, 'server_nonce' => $server_nonce, 'retry_id' => $retry_id, 'g_b' => $g_b_str]);
|
|
||||||
$data_with_sha = sha1($data, true).$data;
|
|
||||||
$data_with_sha_padded = $data_with_sha.\phpseclib\Crypt\Random::string(Tools::posmod(-strlen($data_with_sha), 16));
|
|
||||||
|
|
||||||
// encrypt client_DH_inner_data
|
|
||||||
$encrypted_data = Crypt::ige_encrypt($data_with_sha_padded, $tmp_aes_key, $tmp_aes_iv);
|
|
||||||
|
|
||||||
// Send set_client_DH_params query
|
|
||||||
$Set_client_DH_params_answer = $this->method_call('set_client_DH_params', ['nonce' => $nonce, 'server_nonce' => $server_nonce, 'encrypted_data' => $encrypted_data]);
|
|
||||||
|
|
||||||
// Generate auth_key
|
|
||||||
$auth_key = $g_a->powMod($b, $dh_prime);
|
|
||||||
$auth_key_str = $auth_key->toBytes();
|
|
||||||
$auth_key_sha = sha1($auth_key_str, true);
|
|
||||||
$auth_key_aux_hash = substr($auth_key_sha, 0, 8);
|
|
||||||
$new_nonce_hash1 = substr(sha1($new_nonce.chr(1).$auth_key_aux_hash, true), -16);
|
|
||||||
$new_nonce_hash2 = substr(sha1($new_nonce.chr(2).$auth_key_aux_hash, true), -16);
|
|
||||||
$new_nonce_hash3 = substr(sha1($new_nonce.chr(3).$auth_key_aux_hash, true), -16);
|
|
||||||
|
|
||||||
if ($Set_client_DH_params_answer['nonce'] != $nonce) {
|
|
||||||
throw new Exception('Handshake: wrong nonce.');
|
|
||||||
}
|
|
||||||
if ($Set_client_DH_params_answer['server_nonce'] != $server_nonce) {
|
|
||||||
throw new Exception('Handshake: wrong server nonce');
|
|
||||||
}
|
|
||||||
if ($Set_client_DH_params_answer['_'] == 'dh_gen_ok') {
|
|
||||||
if ($Set_client_DH_params_answer['new_nonce_hash1'] != $new_nonce_hash1) {
|
|
||||||
throw new Exception('Handshake: wrong new_nonce_hash1');
|
|
||||||
}
|
|
||||||
$this->log->log('Diffie Hellman key exchange processed successfully');
|
|
||||||
|
|
||||||
$res_authorization = ['server_salt' => substr($new_nonce, 0, 8 - 0) ^ substr($server_nonce, 0, 8 - 0)];
|
|
||||||
$res_authorization['auth_key'] = $auth_key_str;
|
|
||||||
$res_authorization['id'] = substr($auth_key_sha, -8);
|
|
||||||
if ($expires_in < 0) {
|
|
||||||
$res_authorization['expires_in'] = $expires_in;
|
|
||||||
}
|
|
||||||
$this->log->log('Auth key generated');
|
|
||||||
$this->timedelta = 0;
|
|
||||||
|
|
||||||
return $res_authorization;
|
|
||||||
} elseif ($Set_client_DH_params_answer['_'] == 'dh_gen_retry') {
|
|
||||||
if ($Set_client_DH_params_answer['new_nonce_hash2'] != $new_nonce_hash2) {
|
|
||||||
throw new Exception('Handshake: wrong new_nonce_hash_2');
|
|
||||||
}
|
|
||||||
$this->log->log('Retrying Auth');
|
|
||||||
} elseif ($Set_client_DH_params_answer['_'] == 'dh_gen_fail') {
|
|
||||||
if ($Set_client_DH_params_answer['new_nonce_hash3'] != $new_nonce_hash3) {
|
|
||||||
throw new Exception('Handshake: wrong new_nonce_hash_3');
|
|
||||||
}
|
|
||||||
$this->log->log('Auth Failed');
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
throw new Exception('Response Error');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new Exception('Auth Failed');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function aes_calculate($msg_key, $direction = 'to server')
|
|
||||||
{
|
|
||||||
$x = ($direction == 'to server') ? 0 : 8;
|
|
||||||
$sha1_a = sha1($msg_key.substr($this->settings['authorization']['temp_auth_key']['auth_key'], $x, ($x + 32) - $x), true);
|
|
||||||
$sha1_b = sha1(substr($this->settings['authorization']['temp_auth_key']['auth_key'], ($x + 32), ($x + 48) - ($x + 32)).$msg_key.substr($this->settings['authorization']['temp_auth_key']['auth_key'], (48 + $x), (64 + $x) - (48 + $x)), true);
|
|
||||||
$sha1_c = sha1(substr($this->settings['authorization']['temp_auth_key']['auth_key'], ($x + 64), ($x + 96) - ($x + 64)).$msg_key, true);
|
|
||||||
$sha1_d = sha1($msg_key.substr($this->settings['authorization']['temp_auth_key']['auth_key'], ($x + 96), ($x + 128) - ($x + 96)), true);
|
|
||||||
$aes_key = substr($sha1_a, 0, 8 - 0).substr($sha1_b, 8, 20 - 8).substr($sha1_c, 4, 16 - 4);
|
|
||||||
$aes_iv = substr($sha1_a, 8, 20 - 8).substr($sha1_b, 0, 8 - 0).substr($sha1_c, 16, 20 - 16).substr($sha1_d, 0, 8 - 0);
|
|
||||||
|
|
||||||
return [$aes_key, $aes_iv];
|
|
||||||
}
|
|
||||||
}
|
|
@ -266,6 +266,6 @@ class TL
|
|||||||
public function get_opts($method) {
|
public function get_opts($method) {
|
||||||
$opts = ["requires_answer" => !in_array($method, [
|
$opts = ["requires_answer" => !in_array($method, [
|
||||||
'msgs_ack',
|
'msgs_ack',
|
||||||
]]
|
])];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,4 +7,9 @@ if (!$config) {
|
|||||||
pyjslib_printnl("File 'credentials' seems to not exist.");
|
pyjslib_printnl("File 'credentials' seems to not exist.");
|
||||||
exit(-1);
|
exit(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function base64url_decode($data) {
|
||||||
|
return base64_decode(str_pad(strtr($data, '-_', '+/'), strlen($data) % 4, '=', STR_PAD_RIGHT));
|
||||||
|
}
|
||||||
$MadelineProto = new \danog\MadelineProto\API($config);
|
$MadelineProto = new \danog\MadelineProto\API($config);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user