Refactor auth key management

This commit is contained in:
Daniil Gentili 2019-09-01 23:39:29 +02:00
parent 3107aee776
commit ea2b28dbb1
17 changed files with 918 additions and 380 deletions

View File

@ -297,8 +297,8 @@ class API extends APIFactory
if ($filename == '') {
throw new \danog\MadelineProto\Exception('Empty filename');
}
if (isset($this->API->setdem) && $this->API->setdem) {
$this->API->setdem = false;
if (isset($this->API->flushSettings) && $this->API->flushSettings) {
$this->API->flushSettings = false;
$this->API->__construct($this->API->settings);
}
if ($this->API === null && !$this->getting_api_id) {

View File

@ -177,8 +177,8 @@ class APIFactory extends AsyncConstruct
Logger::log("Didn't serialize in a while, doing that now...");
$this->serialize($this->session);
}
if ($this->API->setdem) {
$this->API->setdem = false;
if ($this->API->flushSettings) {
$this->API->flushSettings = false;
$this->API->__construct($this->API->settings);
yield $this->API->initAsync();
}
@ -207,7 +207,7 @@ class APIFactory extends AsyncConstruct
$this->wait($this->asyncAPIPromise);
}
if ($name === 'settings') {
$this->API->setdem = true;
$this->API->flushSettings = true;
return $this->API->settings;
}

View File

@ -262,13 +262,9 @@ class Connection extends Session
}
/**
* Connect function.
* Connects to a telegram DC using the specified protocol, proxy and connection parameters.
*
* Connects to a telegram DC using the specified protocol, proxy and connection parameters
*
* @param string $proxy Proxy class name
*
* @internal
* @param ConnectionContext $ctx Connection context
*
* @return \Amp\Promise
*/
@ -348,7 +344,7 @@ class Connection extends Session
*
* @param array $message The message to send
* @param boolean $flush Whether to flush the message right away
*
*
* @return \Generator
*/
public function sendMessage(array $message, bool $flush = true): \Generator
@ -388,7 +384,7 @@ class Connection extends Session
}
/**
* Flush pending packets
* Flush pending packets.
*
* @return void
*/
@ -399,7 +395,9 @@ class Connection extends Session
/**
* Connect main instance.
*
* @param MTProto $extra
* @param DataCenterConnection $extra Extra
* @param callable $readingCallback Read callback
* @param callable $writingCallback Write callback
*
* @return void
*/
@ -423,7 +421,7 @@ class Connection extends Session
}
/**
* Disconnect from DC
* Disconnect from DC.
*
* @return void
*/
@ -445,9 +443,9 @@ class Connection extends Session
}
$this->API->logger->logger("Disconnected from DC {$this->datacenter}");
}
/**
* Reconnect to DC
* Reconnect to DC.
*
* @return \Generator
*/
@ -468,7 +466,7 @@ class Connection extends Session
}
/**
* Get name
* Get name.
*
* @return string
*/

View File

@ -42,6 +42,8 @@ use Amp\Socket\ConnectException;
use Amp\Socket\Socket;
use Amp\TimeoutException;
use danog\MadelineProto\AuthKey\AuthKey;
use danog\MadelineProto\AuthKey\PermAuthKey;
use danog\MadelineProto\AuthKey\TempAuthKey;
use danog\MadelineProto\Stream\Common\BufferedRawStream;
use danog\MadelineProto\Stream\ConnectionContext;
use danog\MadelineProto\Stream\MTProtoTransport\AbridgedStream;
@ -128,16 +130,54 @@ class DataCenter
public function __wakeup()
{
foreach ($this->sockets as &$socket) {
$array = [];
foreach ($this->sockets as $id => $socket) {
if ($socket instanceof Connection) {
$new = new DataCenterConnection;
if ($socket->temp_auth_key) {
$new->setAuthKey(new AuthKey($socket->temp_auth_key), true);
$array[$id]['tempAuthKey'] = $socket->temp_auth_key;
}
if ($socket->auth_key) {
$new->setAuthKey(new AuthKey($socket->auth_key), false);
$array[$id]['authKey'] = $socket->auth_key;
$array[$id]['authKey']['authorized'] = $socket->authorized;
}
}
}
$this->setDataCenterConnections($array);
}
/**
* Set auth key information from saved auth array
*
* @param array $saved Saved auth array
*
* @return void
*/
public function setDataCenterConnections(array $saved)
{
foreach ($saved as $id => $data) {
$connection = $this->sockets[$id] = new DataCenterConnection;
if (isset($data['authKey'])) {
$connection->setPermAuthKey(new PermAuthKey($data['authKey']));
}
if (isset($data['linked'])) {
continue;
}
if (isset($data['tempAuthKey'])) {
$connection->setTempAuthKey(new TempAuthKey($data['tempAuthKey']));
if ($data['tempAuthKey']['bound'] ?? false && $connection->hasPermAuthKey()) {
$connection->bind();
}
}
unset($saved[$id]);
}
foreach ($saved as $id => $data) {
$connection = $this->sockets[$id];
$connection->link($data['linked']);
if (isset($data['tempAuthKey'])) {
$connection->setTempAuthKey(new TempAuthKey($data['tempAuthKey']));
if ($data['tempAuthKey']['bound'] ?? false && $connection->hasPermAuthKey()) {
$connection->bind();
}
$new->authorized($socket->authorized);
}
}
}
@ -786,6 +826,17 @@ class DataCenter
return yield (yield $this->getHTTPClient()->request($url))->getBody();
}
/**
* Get Connection instance for authorization
*
* @param string $dc DC ID
*
* @return Connection
*/
public function getAuthConnection(string $dc): Connection
{
return $this->sockets[$dc]->getAuthConnection();
}
/**
* Get Connection instance.
*

View File

@ -19,6 +19,8 @@
namespace danog\MadelineProto;
use danog\MadelineProto\AuthKey\AuthKey;
use danog\MadelineProto\AuthKey\PermAuthKey;
use danog\MadelineProto\AuthKey\TempAuthKey;
use danog\MadelineProto\Stream\ConnectionContext;
use JsonSerializable;
@ -27,23 +29,16 @@ class DataCenterConnection implements JsonSerializable
/**
* Temporary auth key.
*
* @var AuthKey
* @var TempAuthKey|null
*/
private $tempAuthKey;
/**
* Permanent auth key.
*
* @var AuthKey
* @var PermAuthKey|null
*/
private $authKey;
/**
* Whether this auth key is authorized (as in logged in).
*
* @var boolean
*/
private $authorized = false;
/**
* Connections open to a certain DC.
*
@ -79,11 +74,11 @@ class DataCenterConnection implements JsonSerializable
private $datacenter;
/**
* Index for round robin.
* Linked DC ID.
*
* @var integer
* @var string
*/
private $index = 0;
private $linked;
/**
* Loop to keep weights at sane value.
@ -106,19 +101,19 @@ class DataCenterConnection implements JsonSerializable
/**
* Check if auth key is present.
*
* @param boolean $temp Whether to fetch the temporary auth key
* @param boolean|null $temp Whether to fetch the temporary auth key
*
* @return bool
*/
public function hasAuthKey(bool $temp = true): bool
{
return $this->{$temp ? 'tempAuthKey' : 'authKey'} !== null;
return $this->{$temp ? 'tempAuthKey' : 'authKey'} !== null && $this->{$temp ? 'tempAuthKey' : 'authKey'}->hasAuthKey();
}
/**
* Set auth key.
*
* @param AuthKey|null $key The auth key
* @param boolean $temp Whether to set the temporary auth key
* @param boolean|null $temp Whether to set the temporary auth key
*
* @return void
*/
@ -127,6 +122,79 @@ class DataCenterConnection implements JsonSerializable
$this->{$temp ? 'tempAuthKey' : 'authKey'} = $key;
}
/**
* Get temporary authorization key.
*
* @return AuthKey
*/
public function getTempAuthKey(): TempAuthKey
{
return $this->getAuthKey(true);
}
/**
* Get permanent authorization key.
*
* @return AuthKey
*/
public function getPermAuthKey(): PermAuthKey
{
return $this->getAuthKey(false);
}
/**
* Check if has temporary authorization key.
*
* @return boolean
*/
public function hasTempAuthKey(): bool
{
return $this->hasAuthKey(true);
}
/**
* Check if has permanent authorization key.
*
* @return boolean
*/
public function hasPermAuthKey(): bool
{
return $this->hasAuthKey(false);
}
/**
* Set temporary authorization key.
*
* @param TempAuthKey|null $key Auth key
*
* @return void
*/
public function setTempAuthKey(?TempAuthKey $key)
{
return $this->setAuthKey($key, true);
}
/**
* Set permanent authorization key.
*
* @param PermAuthKey|null $key Auth key
*
* @return void
*/
public function setPermAuthKey(?PermAuthKey $key)
{
return $this->setAuthKey($key, false);
}
/**
* Bind temporary and permanent auth keys.
*
* @param bool $pfs Whether to bind using PFS
*
* @return void
*/
public function bind(bool $pfs = true)
{
$this->tempAuthKey->bind($this->authKey, $pfs);
}
/**
* Check if we are logged in.
*
@ -134,7 +202,7 @@ class DataCenterConnection implements JsonSerializable
*/
public function isAuthorized(): bool
{
return $this->authorized;
return $this->hasTempAuthKey() ? $this->getTempAuthKey()->isAuthorized() : false;
}
/**
@ -146,7 +214,20 @@ class DataCenterConnection implements JsonSerializable
*/
public function authorized(bool $authorized)
{
$this->authorized = $authorized;
$this->getTempAuthKey()->authorized($authorized);
}
/**
* Link permanent authorization info of main DC to media DC.
*
* @param string $dc Main DC ID
*
* @return void
*/
public function link(string $dc)
{
$this->linked = $dc;
$this->authKey = &$this->API->datacenter->getDataCenterConnection($dc)->authKey;
}
/**
@ -171,6 +252,7 @@ class DataCenterConnection implements JsonSerializable
$socket->flush();
}
}
/**
* Get connection context.
*
@ -196,7 +278,6 @@ class DataCenterConnection implements JsonSerializable
$this->datacenter = $ctx->getDc();
$media = $ctx->isMedia();
$count = $media ? $this->API->settings['connection_settings']['media_socket_count'] : 1;
if ($count > 1) {
@ -258,6 +339,16 @@ class DataCenterConnection implements JsonSerializable
yield $this->connect($this->ctx);
}
/**
* Get connection for authorization.
*
* @return Connection
*/
public function getAuthConnection(): Connection
{
return $this->connections[0];
}
/**
* Get best socket in round robin.
*
@ -317,10 +408,14 @@ class DataCenterConnection implements JsonSerializable
*/
public function jsonSerialize(): array
{
return [
return $this->linked ?
[
'linked' => $this->linked,
'tempAuthKey' => $this->tempAuthKey
] :
[
'authKey' => $this->authKey,
'tempAuthKey' => $this->tempAuthKey,
'authorized' => $this->authorized,
'tempAuthKey' => $this->tempAuthKey
];
}
/**
@ -332,6 +427,6 @@ class DataCenterConnection implements JsonSerializable
*/
public function __sleep()
{
return ['authKey', 'tempAuthKey', 'authorized'];
return $this->linked ? ['linked', 'tempAuthKey'] : ['linked', 'authKey', 'tempAuthKey'];
}
}

View File

@ -113,7 +113,7 @@ class ReadLoop extends SignalLoop
$connection->httpReceived();
Loop::defer([$API, 'handle_messages'], $datacenter);
Loop::defer([$connection, 'handle_messages']);
if ($this->API->is_http($datacenter)) {
Loop::defer([$connection->waiter, 'resume']);

View File

@ -139,19 +139,19 @@ class MTProto extends AsyncConstruct implements TLCallback
];
/**
* Localized message info flags
*
* Localized message info flags.
*
* @var array
*/
const MSGS_INFO_FLAGS = [
1 => 'nothing is known about the message (msg_id too low, the other party may have forgotten it)',
2 => 'message not received (msg_id falls within the range of stored identifiers; however, the other party has certainly not received a message like that)',
3 => 'message not received (msg_id too high; however, the other party has certainly not received it yet)',
4 => 'message received (note that this response is also at the same time a receipt acknowledgment)',
4 => 'message received (note that this response is also at the same time a receipt acknowledgment)',
8 => ' and message already acknowledged',
16 => ' and message not requiring acknowledgment',
32 => ' and RPC query contained in message being processed or processing already complete',
64 => ' and content-related response to message already generated',
64 => ' and content-related response to message already generated',
128 => ' and other party knows for a fact that message is already received'
];
const REQUESTED = 0;
@ -281,22 +281,102 @@ class MTProto extends AsyncConstruct implements TLCallback
*/
public $channel_participants = [];
/**
* When we last stored data in remote peer database (now doesn't exist anymore).
*
* @var integer
*/
public $last_stored = 0;
/**
* Temporary array of data to be sent to remote peer database.
*
* @var array
*/
public $qres = [];
/**
* Full chat info database.
*
* @var array
*/
public $full_chats = [];
/**
* Latest chat message ID map for update handling.
*
* @var array
*/
private $msg_ids = [];
/**
* Version integer for upgrades.
*
* @var integer
*/
private $v = 0;
private $dialog_params = ['_' => 'MadelineProto.dialogParams', 'limit' => 0, 'offset_date' => 0, 'offset_id' => 0, 'offset_peer' => ['_' => 'inputPeerEmpty'], 'count' => 0];
public $setdem = false;
/**
* Cached getdialogs params.
*
* @var array
*/
private $dialog_params = ['limit' => 0, 'offset_date' => 0, 'offset_id' => 0, 'offset_peer' => ['_' => 'inputPeerEmpty'], 'count' => 0];
/**
* Whether new settings were set and should be applied.
*
* @var boolean
*/
public $flushSettings = false;
/**
* Storage for arbitrary data.
*
* @var array
*/
public $storage = [];
/**
* Support user ID.
*
* @var integer
*/
private $supportUser = 0;
/**
* File reference database.
*
* @var \danog\MadelineProto\MTProtoTools\ReferenceDatabase
*/
public $referenceDatabase;
public $phoneConfigWatcherId;
/**
* Phone config loop.
*
* @var \danog\MadelineProto\Loop\Update\PeriodicLoop
*/
public $phoneConfigLoop;
/**
* Call checker loop.
*
* @var \danog\MadelineProto\Loop\Update\PeriodicLoop
*/
private $callCheckerLoop;
/**
* Autoserialization loop.
*
* @var \danog\MadelineProto\Loop\Update\PeriodicLoop
*/
private $serializeLoop;
/**
* Feeder loops.
*
* @var array<\danog\MadelineProto\Loop\Update\FeedLoop>
*/
public $feeders = [];
/**
* Updater loops.
*
* @var array<\danog\MadelineProto\Loop\Update\UpdateLoop>
*/
public $updaters = [];
public $destructing = false; // Avoid problems with exceptions thrown by forked strands, see tools
/**
* Boolean to avoid problems with exceptions thrown by forked strands, see tools.
*
* @var boolean
*/
public $destructing = false;
/**
* DataCenter instance.
@ -305,11 +385,25 @@ class MTProto extends AsyncConstruct implements TLCallback
*/
public $datacenter;
/**
* Constructor function.
*
* @param array $settings Settings
*
* @return void
*/
public function __magic_construct($settings = [])
{
$this->setInitPromise($this->__construct_async($settings));
}
/**
* Async constructor function.
*
* @param array $settings Settings
*
* @return void
*/
public function __construct_async($settings = [])
{
\danog\MadelineProto\Magic::class_exists();
@ -349,7 +443,7 @@ class MTProto extends AsyncConstruct implements TLCallback
yield $this->connect_to_all_dcs_async();
$this->startLoops();
$this->datacenter->curdc = 2;
if ((!isset($this->authorization['user']['bot']) || !$this->authorization['user']['bot']) && $this->datacenter->getDataCenterConnection($this->datacenter->curdc)->hasAuthKey()) {
if ((!isset($this->authorization['user']['bot']) || !$this->authorization['user']['bot']) && $this->datacenter->getDataCenterConnection($this->datacenter->curdc)->hasTempAuthKey()) {
try {
$nearest_dc = yield $this->method_call_async_read('help.getNearestDc', [], ['datacenter' => $this->datacenter->curdc]);
$this->logger->logger(\sprintf(\danog\MadelineProto\Lang::$current_lang['nearest_dc'], $nearest_dc['country'], $nearest_dc['nearest_dc']), Logger::NOTICE);
@ -375,8 +469,11 @@ class MTProto extends AsyncConstruct implements TLCallback
return ['supportUser', 'referenceDatabase', 'channel_participants', 'event_handler', 'event_handler_instance', 'loop_callback', 'web_template', 'encrypted_layer', 'settings', 'config', 'authorization', 'authorized', 'rsa_keys', 'dh_config', 'chats', 'last_stored', 'qres', 'got_state', 'channels_state', 'updates', 'updates_key', 'full_chats', 'msg_ids', 'dialog_params', 'datacenter', 'v', 'constructors', 'td_constructors', 'methods', 'td_methods', 'td_descriptions', 'tl_callbacks', 'temp_requested_secret_chats', 'temp_rekeyed_secret_chats', 'secret_chats', 'hook_url', 'storage', 'authorized_dc', 'tos'];
}
/**
* Cleanup memory and session file.
*
* @return void
*/
private function cleanup()
{
@ -434,14 +531,14 @@ class MTProto extends AsyncConstruct implements TLCallback
}
foreach ($this->datacenter->getDataCenterConnections() as $dc) {
if (!$dc->isAuthorized() || $dc->hasAuthKey() === null) {
if (!$dc->isAuthorized() || !$dc->hasTempAuthKey()) {
return false;
}
}
return true;
}
public function trySerialize()
public function serialize()
{
if ($this->wrapper instanceof API && isset($this->wrapper->session) && !\is_null($this->wrapper->session) && !$this->asyncInitPromise) {
$this->logger->logger("Didn't serialize in a while, doing that now...");
@ -453,12 +550,16 @@ class MTProto extends AsyncConstruct implements TLCallback
if (!$this->callCheckerLoop) {
$this->callCheckerLoop = new PeriodicLoop($this, [$this, 'checkCalls'], 'call check', 10);
}
$this->callCheckerLoop->start();
if (!$this->serializeLoop) {
$this->serializeLoop = new PeriodicLoop($this, [$this, 'trySerialize'], 'serialize', $this->settings['serialization']['serialization_interval']);
$this->serializeLoop = new PeriodicLoop($this, [$this, 'serialize'], 'serialize', $this->settings['serialization']['serialization_interval']);
}
if (!$this->phoneConfigLoop) {
$this->phoneConfigLoop = new PeriodicLoop($this, [$this, 'get_phone_config_async'], 'phone config', 24 * 3600 * 1000);
}
$this->callCheckerLoop->start();
$this->serializeLoop->start();
$this->phoneConfigLoop->start();
}
public function stopLoops()
{
@ -470,6 +571,10 @@ class MTProto extends AsyncConstruct implements TLCallback
$this->serializeLoop->signal(true);
$this->serializeLoop = null;
}
if ($this->phoneConfigLoop) {
$this->phoneConfigLoop->signal(true);
$this->phoneConfigLoop = null;
}
}
public function __wakeup()
{
@ -527,7 +632,7 @@ class MTProto extends AsyncConstruct implements TLCallback
$this->setEventHandler($this->event_handler);
}
$force = false;
$this->resetSession();
$this->resetMTProtoSession();
if (isset($backtrace[2]['function'], $backtrace[2]['class'], $backtrace[2]['args']) && $backtrace[2]['class'] === 'danog\\MadelineProto\\API' && $backtrace[2]['function'] === '__construct_async') {
if (\count($backtrace[2]['args']) >= 2) {
$this->parse_settings(\array_replace_recursive($this->settings, $backtrace[2]['args'][1]));
@ -541,7 +646,7 @@ class MTProto extends AsyncConstruct implements TLCallback
if (!isset($this->v) || $this->v !== self::V) {
$this->logger->logger(\danog\MadelineProto\Lang::$current_lang['serialization_ofd'], Logger::WARNING);
foreach ($this->datacenter->getDataCenterConnections() as $dc_id => $socket) {
if ($this->authorized === self::LOGGED_IN && \strpos($dc_id, '_') === false && $socket->hasAuthKey(true) && $socket->hasAuthKey(false)) {
if ($this->authorized === self::LOGGED_IN && \strpos($dc_id, '_') === false && $socket->hasPermAuthKey() && $socket->hasTempAuthKey()) {
$socket->authorized(true);
}
}
@ -601,7 +706,7 @@ class MTProto extends AsyncConstruct implements TLCallback
if ($settings['app_info']['api_id'] === 6) {
unset($settings['app_info']);
}
$this->resetSession(true, true);
$this->resetMTProtoSession(true, true);
$this->config = ['expires' => -1];
$this->dh_config = ['version' => 0];
yield $this->__construct_async($settings);
@ -655,9 +760,6 @@ class MTProto extends AsyncConstruct implements TLCallback
public function __destruct()
{
$this->stopLoops();
if ($this->phoneConfigWatcherId) {
Loop::cancel($this->phoneConfigWatcherId);
}
if (isset($this->seqUpdater)) {
$this->seqUpdater->signal(true);
}
@ -680,12 +782,6 @@ class MTProto extends AsyncConstruct implements TLCallback
$this->logger("Successfully destroyed MadelineProto");
}
public function serialize()
{
if ($this->wrapper instanceof \danog\MadelineProto\API && isset($this->wrapper->session) && !\is_null($this->wrapper->session)) {
$this->wrapper->serialize($this->wrapper->session);
}
}
public static function getSettings($settings, $previousSettings = [])
{
Magic::class_exists();
@ -1036,7 +1132,7 @@ class MTProto extends AsyncConstruct implements TLCallback
*
* @return void
*/
public function resetSession(bool $de = true, bool $auth_key = false)
public function resetMTProtoSession(bool $de = true, bool $auth_key = false)
{
if (!\is_object($this->datacenter)) {
throw new Exception(\danog\MadelineProto\Lang::$current_lang['session_corrupted']);
@ -1086,9 +1182,6 @@ class MTProto extends AsyncConstruct implements TLCallback
}
yield $this->all($dcs);
yield $this->init_authorization_async();
if (!$this->phoneConfigWatcherId) {
$this->phoneConfigWatcherId = Loop::repeat(24 * 3600 * 1000, [$this, 'get_phone_config_async']);
}
yield $this->get_phone_config_async();
}
@ -1200,7 +1293,7 @@ class MTProto extends AsyncConstruct implements TLCallback
public function get_phone_config_async($watcherId = null)
{
if ($this->authorized === self::LOGGED_IN && \class_exists('\\danog\\MadelineProto\\VoIPServerConfigInternal') && !$this->authorization['user']['bot'] && $this->datacenter->getDataCenterConnection($this->settings['connection_settings']['default_dc'])->hasAuthKey()) {
if ($this->authorized === self::LOGGED_IN && \class_exists('\\danog\\MadelineProto\\VoIPServerConfigInternal') && !$this->authorization['user']['bot'] && $this->datacenter->getDataCenterConnection($this->settings['connection_settings']['default_dc'])->hasTempAuthKey()) {
$this->logger->logger('Fetching phone config...');
VoIPServerConfig::updateDefault(yield $this->method_call_async_read('phone.getCallConfig', [], ['datacenter' => $this->settings['connection_settings']['default_dc']]));
} else {

View File

@ -1,6 +1,6 @@
<?php
/**
* MTProto Auth key
* MTProto Auth key.
*
* 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.
@ -21,81 +21,63 @@ namespace danog\MadelineProto\AuthKey;
use JsonSerializable;
/**
* MTProto auth key
* MTProto auth key.
*/
class AuthKey implements JsonSerializable
abstract class AuthKey extends JsonSerializable
{
/**
* Auth key
* Auth key.
*
* @var string
*/
private $authKey;
protected $authKey;
/**
* Auth key ID
* Auth key ID.
*
* @var string
*/
private $id;
protected $id;
/**
* Server salt
* Server salt.
*
* @var string
*/
private $serverSalt;
/**
* Whether the auth key is bound
*
* @var boolean
*/
private $bound = false;
/**
* Whether the connection is inited for this auth key
*
* @var boolean
*/
private $inited = false;
protected $serverSalt;
/**
* Constructor function
* Constructor function.
*
* @param array $old Old auth key array
*/
public function __construct(array $old = [])
{
if (isset($old['auth_key'])) {
if (strlen($old['auth_key']) !== 2048/8 && strpos($old['authkey'], 'pony') === 0) {
$old['auth_key'] = base64_decode(substr($old['auth_key'], 4));
if (\strlen($old['auth_key']) !== 2048/8 && \strpos($old['authkey'], 'pony') === 0) {
$old['auth_key'] = \base64_decode(\substr($old['auth_key'], 4));
}
$this->setAuthKey($old['auth_key']);
}
if (isset($old['server_salt'])) {
$this->setServerSalt($old['server_salt']);
}
if (isset($old['bound'])) {
$this->bind($old['bound']);
}
if (isset($old['connection_inited'])) {
$this->init($old['connection_inited']);
}
}
/**
* Set auth key
* Set auth key.
*
* @param string $authKey Authorization key
*
*
* @return void
*/
public function setAuthKey(string $authKey)
{
$this->authKey = $authKey;
$this->id = substr(sha1($authKey, true), -8);
$this->id = \substr(\sha1($authKey, true), -8);
}
/**
* Check if auth key is present
* Check if auth key is present.
*
* @return boolean
*/
@ -105,7 +87,7 @@ class AuthKey implements JsonSerializable
}
/**
* Get auth key
* Get auth key.
*
* @return string
*/
@ -115,7 +97,7 @@ class AuthKey implements JsonSerializable
}
/**
* Get auth key ID
* Get auth key ID.
*
* @return string
*/
@ -125,10 +107,10 @@ class AuthKey implements JsonSerializable
}
/**
* Set server salt
* Set server salt.
*
* @param string $salt Server salt
*
*
* @return void
*/
public function setServerSalt(string $salt)
@ -137,7 +119,7 @@ class AuthKey implements JsonSerializable
}
/**
* Get server salt
* Get server salt.
*
* @return string
*/
@ -147,7 +129,7 @@ class AuthKey implements JsonSerializable
}
/**
* Check if has server salt
* Check if has server salt.
*
* @return boolean
*/
@ -157,61 +139,18 @@ class AuthKey implements JsonSerializable
}
/**
* Bind auth key
*
* @param boolean $bound Bind or unbind
*
* @return void
*/
public function bind(bool $bound = true)
{
$this->bound = $bound;
}
/**
* Check if auth key is bound
* Check if we are logged in.
*
* @return boolean
*/
public function isBound(): bool
{
return $this->bound;
}
abstract public function isAuthorized(): bool;
/**
* Init or deinit connection for auth key
* Set the authorized boolean.
*
* @param boolean $authorized Whether we are authorized
*
* @param boolean $init Init or deinit
*
* @return void
*/
public function init(bool $init = true)
{
$this->inited = $init;
}
/**
* Check if connection is inited for auth key
*
* @return boolean
*/
public function isInited(): bool
{
return $this->inited;
}
/**
* JSON serialization function
*
* @return array
*/
public function jsonSerialize(): array
{
return [
'auth_key' => 'pony'.base64_encode($this->authKey),
'server_salt' => $this->serverSalt,
'bound' => $this->bound,
'connection_inited' => $this->inited
];
}
}
abstract public function authorized(bool $authorized);
}

View File

@ -0,0 +1,81 @@
<?php
/**
* MTProto permanent auth key.
*
* 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 MadelineProto.
* If not, see <http://www.gnu.org/licenses/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2019 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto\AuthKey;
/**
* MTProto permanent auth key.
*/
class PermAuthKey extends AuthKey
{
/**
* Whether this auth key is authorized (as in associated to an account on Telegram).
*
* @var boolean
*/
private $authorized = false;
/**
* Constructor function.
*
* @param array $old Old auth key array
*/
public function __construct(array $old = [])
{
parent::__construct($old);
if (isset($old['authorized'])) {
$this->authorized($old['authorized']);
}
}
/**
* Check if we are logged in.
*
* @return boolean
*/
public function isAuthorized(): bool
{
return $this->authorized;
}
/**
* Set the authorized boolean.
*
* @param boolean $authorized Whether we are authorized
*
* @return void
*/
public function authorized(bool $authorized)
{
$this->authorized = $authorized;
}
/**
* JSON serialization function.
*
* @return array
*/
public function jsonSerialize(): array
{
return [
'auth_key' => 'pony'.\base64_encode($this->authKey),
'server_salt' => $this->serverSalt,
'authorized' => $this->authorized
];
}
}

View File

@ -0,0 +1,173 @@
<?php
/**
* MTProto temporary auth key.
*
* 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 MadelineProto.
* If not, see <http://www.gnu.org/licenses/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2019 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto\AuthKey;
use JsonSerializable;
/**
* MTProto temporary auth key.
*/
class TempAuthKey extends AuthKey implements JsonSerializable
{
/**
* Bound auth key instance.
*
* @var PermAuthKey|null
*/
private $bound;
/**
* Expiration date.
*
* @var int
*/
private $expires = 0;
/**
* Whether the connection is inited for this auth key.
*
* @var boolean
*/
protected $inited = false;
/**
* Constructor function.
*
* @param array $old Old auth key array
*/
public function __construct(array $old = [])
{
parent::__construct($old);
if (isset($old['expires'])) {
$this->expires($old['expires']);
}
if (isset($old['connection_inited'])) {
$this->init($old['connection_inited']);
}
}
/**
* Init or deinit connection for auth key.
*
* @param boolean $init Init or deinit
*
* @return void
*/
public function init(bool $init = true)
{
$this->inited = $init;
}
/**
* Check if connection is inited for auth key.
*
* @return boolean
*/
public function isInited(): bool
{
return $this->inited;
}
/**
* Bind auth key.
*
* @param PermAuthKey|null $bound Permanent auth key
* @param bool $pfs Whether to bind using PFS
*
* @return void
*/
public function bind(?PermAuthKey $bound, bool $pfs = true)
{
$this->bound = $bound;
if (!$pfs) {
foreach (['authKey', 'id', 'inited', 'serverSalt'] as $key) {
$this->{$key} = &$bound->{$key};
}
}
}
/**
* Check if auth key is bound.
*
* @return boolean
*/
public function isBound(): bool
{
return $this->bound !== null;
}
/**
* Check if we are logged in.
*
* @return boolean
*/
public function isAuthorized(): bool
{
return $this->bound ? $this->bound->isAuthorized() : false;
}
/**
* Set the authorized boolean.
*
* @param boolean $authorized Whether we are authorized
*
* @return void
*/
public function authorized(bool $authorized)
{
$this->bound->authorized($authorized);
}
/**
* Set expiration date of temporary auth key.
*
* @param integer $expires Expiration date
*
* @return void
*/
public function expires(int $expires)
{
$this->expires = $expires;
}
/**
* Check if auth key has expired.
*
* @return boolean
*/
public function expired(): bool
{
return \time() > $this->expires;
}
/**
* JSON serialization function.
*
* @return array
*/
public function jsonSerialize(): array
{
return [
'auth_key' => 'pony'.\base64_encode($this->authKey),
'server_salt' => $this->serverSalt,
'bound' => $this->isBound(),
'expires' => $this->expires,
'connection_inited' => $this->inited
];
}
}

View File

@ -173,7 +173,7 @@ trait CallHandler
return yield all($promises);
}
$args = yield $this->botAPI_to_MTProto_async($args);
$args = yield $this->API->botAPI_to_MTProto_async($args);
if (isset($args['ping_id']) && \is_int($args['ping_id'])) {
$args['ping_id'] = $this->pack_signed_long($args['ping_id']);
}
@ -188,7 +188,7 @@ trait CallHandler
'content_related' => $this->content_related($method),
'promise' => $deferred,
'method' => true,
'unencrypted' => $this->shared->hasAuthKey() && \strpos($method, '.') === false
'unencrypted' => !$this->shared->hasTempAuthKey() && \strpos($method, '.') === false
]
);
@ -220,7 +220,7 @@ trait CallHandler
*/
public function object_call_async(string $object, $args = [], array $aargs = ['msg_id' => null]): Promise
{
$message = ['_' => $object, 'body' => $args, 'content_related' => $this->content_related($object), 'unencrypted' => $this->shared->hasAuthKey(), 'method' => false];
$message = ['_' => $object, 'body' => $args, 'content_related' => $this->content_related($object), 'unencrypted' => !$this->shared->hasTempAuthKey(), 'method' => false];
if (isset($aargs['promise'])) {
$message['promise'] = $aargs['promise'];
}

View File

@ -114,11 +114,11 @@ trait ResponseHandler
$this->check_in_seq_no($current_msg_id);
$only_updates = false;
$this->temp_auth_key['server_salt'] = $this->incoming_messages[$current_msg_id]['content']['server_salt'];
$this->shared->getTempAuthKey()->setServerSalt($this->incoming_messages[$current_msg_id]['content']['server_salt']);
$this->ack_incoming_message_id($current_msg_id);
// Acknowledge that I received the server's response
if ($this->authorized === self::LOGGED_IN && !$this->initing_authorization && $this->API->datacenter->getDataCenterConnection($this->API->datacenter->curdc)->hasAuthKey() && isset($this->updaters[false])) {
if ($this->authorized === self::LOGGED_IN && !$this->initing_authorization && $this->API->datacenter->getDataCenterConnection($this->API->datacenter->curdc)->hasTempAuthKey() && isset($this->updaters[false])) {
$this->updaters[false]->resumeDefer();
}
@ -344,8 +344,8 @@ trait ResponseHandler
if (isset($response['_'])) {
switch ($response['_']) {
case 'rpc_error':
if (isset($request['method']) && $request['method'] && $request['_'] !== 'auth.bindTempAuthKey' && $this->temp_auth_key !== null && (!isset($this->temp_auth_key['connection_inited']) || $this->temp_auth_key['connection_inited'] === false)) {
$this->temp_auth_key['connection_inited'] = true;
if (isset($request['method']) && $request['method'] && $request['_'] !== 'auth.bindTempAuthKey' && $this->shared->hasTempAuthKey() && !$this->shared->getTempAuthKey()->isInited()) {
$this->shared->getTempAuthKey()->init(true);
}
if (\in_array($response['error_message'], ['PERSISTENT_TIMESTAMP_EMPTY', 'PERSISTENT_TIMESTAMP_OUTDATED', 'PERSISTENT_TIMESTAMP_INVALID'])) {
@ -447,9 +447,8 @@ trait ResponseHandler
return;
}
$this->session_id = null;
$this->temp_auth_key = null;
$this->auth_key = null;
$this->authorized = false;
$this->shared->setTempAuthKey(null);
$this->shared->setPermAuthKey(null);
$this->logger->logger('Auth key not registered, resetting temporary and permanent auth keys...', \danog\MadelineProto\Logger::ERROR);
@ -491,7 +490,7 @@ trait ResponseHandler
case 'AUTH_KEY_PERM_EMPTY':
$this->logger->logger('Temporary auth key not bound, resetting temporary auth key...', \danog\MadelineProto\Logger::ERROR);
$this->temp_auth_key = null;
$this->shared->setTempAuthKey(null);
$this->callFork((function () use ($request_id) {
yield $this->API->init_authorization_async();
$this->method_recall('', ['message_id' => $request_id, ]);
@ -535,7 +534,7 @@ trait ResponseHandler
$this->logger->logger('Received bad_msg_notification: '.self::BAD_MSG_ERROR_CODES[$response['error_code']], \danog\MadelineProto\Logger::WARNING);
switch ($response['error_code']) {
case 48:
$this->temp_auth_key['server_salt'] = $response['new_server_salt'];
$this->getTempAuthKey()->setServerSalt($response['new_server_salt']);
$this->method_recall('', ['message_id' => $request_id, 'postpone' => true]);
return;
@ -543,8 +542,8 @@ trait ResponseHandler
case 17:
$this->time_delta = (int) (new \phpseclib\Math\BigInteger(\strrev($response_id), 256))->bitwise_rightShift(32)->subtract(new \phpseclib\Math\BigInteger(\time()))->toString();
$this->logger->logger('Set time delta to '.$this->time_delta, \danog\MadelineProto\Logger::WARNING);
$this->API->resetSession();
$this->temp_auth_key = null;
$this->API->resetMTProtoSession();
$this->shared->setTempAuthKey(null);
$this->callFork((function () use ($request_id) {
yield $this->API->init_authorization_async();
$this->method_recall('', ['message_id' => $request_id, ]);
@ -559,8 +558,8 @@ trait ResponseHandler
}
}
if (isset($request['method']) && $request['method'] && $request['_'] !== 'auth.bindTempAuthKey' && $this->temp_auth_key !== null && (!isset($this->temp_auth_key['connection_inited']) || $this->temp_auth_key['connection_inited'] === false)) {
$this->temp_auth_key['connection_inited'] = true;
if (isset($request['method']) && $request['method'] && $request['_'] !== 'auth.bindTempAuthKey' && $this->shared->hasTempAuthKey() && !$this->shared->getTempAuthKey()->isInited()) {
$this->shared->getTempAuthKey()->init(true);
}
if (!isset($request['promise'])) {

View File

@ -26,7 +26,6 @@ abstract class Session
use AckHandler;
use MsgIdHandler;
use ResponseHandler;
use SaltHandler;
use SeqNoHandler;
use CallHandler;
@ -44,7 +43,7 @@ abstract class Session
public $ack_queue = [];
/**
* Reset MTProto session
* Reset MTProto session.
*
* @return void
*/

View File

@ -19,8 +19,12 @@
namespace danog\MadelineProto\MTProtoTools;
use danog\MadelineProto\Exception;
use Amp\Artax\Request;
use danog\MadelineProto\AuthKey\AuthKey;
use danog\MadelineProto\AuthKey\PermAuthKey;
use danog\MadelineProto\AuthKey\TempAuthKey;
use danog\MadelineProto\DataCenterConnection;
use phpseclib\Math\BigInteger;
/**
* Manages the creation of the authorization key.
@ -30,13 +34,40 @@ use Amp\Artax\Request;
*/
trait AuthKeyHandler
{
/**
* DCs currently initing authorization.
*
* @var array<bool>
*/
private $init_auth_dcs = [];
/**
* Currently initing authorization?
*
* @var boolean
*/
private $pending_auth = false;
public function create_auth_key_async($expires_in, $datacenter): \Generator
/**
* DataCenter instance.
*
* @var \danog\MadelineProto\DataCenter
*/
public $datacenter;
/**
* Create authorization key.
*
* @param int $expires_in Expiry date of auth key, -1 for permanent auth key
* @param string $datacenter DC ID
*
* @return \Generator<AuthKey>
*/
public function create_auth_key_async(int $expires_in, string $datacenter): \Generator
{
$cdn = strpos($datacenter, 'cdn');
$cdn = \strpos($datacenter, 'cdn');
$req_pq = $cdn ? 'req_pq' : 'req_pq_multi';
$connection = $this->datacenter->getAuthConnection($datacenter);
for ($retry_id_total = 1; $retry_id_total <= $this->settings['max_tries']['authorization']; $retry_id_total++) {
try {
$this->logger->logger(\danog\MadelineProto\Lang::$current_lang['req_pq'], \danog\MadelineProto\Logger::VERBOSE);
@ -58,7 +89,7 @@ trait AuthKeyHandler
* ]
*/
$nonce = $this->random(16);
$ResPQ = yield $this->method_call_async_read($req_pq, ['nonce' => $nonce], ['datacenter' => $datacenter]);
$ResPQ = yield $connection->method_call_async_read($req_pq, ['nonce' => $nonce]);
/*
* ***********************************************************************
* Check if the client's nonce and the server's nonce are the same
@ -70,8 +101,8 @@ trait AuthKeyHandler
* ***********************************************************************
* Find our key in the server_public_key_fingerprints vector
*/
foreach ($cdn ? array_merge($this->cdn_rsa_keys, $this->rsa_keys) : $this->rsa_keys as $curkey) {
if (in_array($curkey->fp, $ResPQ['server_public_key_fingerprints'])) {
foreach ($cdn ? \array_merge($this->cdn_rsa_keys, $this->rsa_keys) : $this->rsa_keys as $curkey) {
if (\in_array($curkey->fp, $ResPQ['server_public_key_fingerprints'])) {
$key = $curkey;
}
}
@ -161,14 +192,14 @@ trait AuthKeyHandler
$p_bytes = $p->toBytes();
$q_bytes = $q->toBytes();
$new_nonce = $this->random(32);
$data_unserialized = ['pq' => $pq_bytes, 'p' => $p_bytes, 'q' => $q_bytes, 'nonce' => $nonce, 'server_nonce' => $server_nonce, 'new_nonce' => $new_nonce, 'expires_in' => $expires_in, 'dc' => preg_replace('|_.*|', '', $datacenter)];
$data_unserialized = ['pq' => $pq_bytes, 'p' => $p_bytes, 'q' => $q_bytes, 'nonce' => $nonce, 'server_nonce' => $server_nonce, 'new_nonce' => $new_nonce, 'expires_in' => $expires_in, 'dc' => \preg_replace('|_.*|', '', $datacenter)];
$p_q_inner_data = yield $this->serialize_object_async(['type' => 'p_q_inner_data'.($expires_in < 0 ? '' : '_temp')], $data_unserialized, 'p_q_inner_data');
/*
* ***********************************************************************
* Encrypt serialized object
*/
$sha_digest = sha1($p_q_inner_data, true);
$random_bytes = $this->random(255 - strlen($p_q_inner_data) - strlen($sha_digest));
$sha_digest = \sha1($p_q_inner_data, true);
$random_bytes = $this->random(255 - \strlen($p_q_inner_data) - \strlen($sha_digest));
$to_encrypt = $sha_digest.$p_q_inner_data.$random_bytes;
$encrypted_data = $key->encrypt($to_encrypt);
$this->logger->logger('Starting Diffie Hellman key exchange', \danog\MadelineProto\Logger::VERBOSE);
@ -192,7 +223,7 @@ trait AuthKeyHandler
* ]
*/
//
$server_dh_params = yield $this->method_call_async_read('req_DH_params', ['nonce' => $nonce, 'server_nonce' => $server_nonce, 'p' => $p_bytes, 'q' => $q_bytes, 'public_key_fingerprint' => $key->fp, 'encrypted_data' => $encrypted_data], ['datacenter' => $datacenter]);
$server_dh_params = yield $connection->method_call_async_read('req_DH_params', ['nonce' => $nonce, 'server_nonce' => $server_nonce, 'p' => $p_bytes, 'q' => $q_bytes, 'public_key_fingerprint' => $key->fp, 'encrypted_data' => $encrypted_data]);
/*
* ***********************************************************************
* Check if the client's nonce and the server's nonce are the same
@ -212,7 +243,7 @@ trait AuthKeyHandler
* Check valid new nonce hash if return from server
* new nonce hash return in server_DH_params_fail
*/
if (isset($server_dh_params['new_nonce_hash']) && substr(sha1($new_nonce), -32) != $server_dh_params['new_nonce_hash']) {
if (isset($server_dh_params['new_nonce_hash']) && \substr(\sha1($new_nonce), -32) != $server_dh_params['new_nonce_hash']) {
throw new \danog\MadelineProto\SecurityException('wrong new nonce hash.');
}
/*
@ -220,15 +251,15 @@ trait AuthKeyHandler
* Get key, 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);
$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 = $this->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);
$answer_hash = \substr($answer_with_hash, 0, 20);
$answer = \substr($answer_with_hash, 20);
/*
* ***********************************************************************
* Deserialize answer
@ -247,7 +278,7 @@ trait AuthKeyHandler
* Do some checks
*/
$server_DH_inner_data_length = $this->get_length($answer);
if (sha1(substr($answer, 0, $server_DH_inner_data_length), true) != $answer_hash) {
if (\sha1(\substr($answer, 0, $server_DH_inner_data_length), true) != $answer_hash) {
throw new \danog\MadelineProto\SecurityException('answer_hash mismatch.');
}
if ($nonce != $server_DH_inner_data['nonce']) {
@ -264,8 +295,8 @@ trait AuthKeyHandler
* Time delta
*/
$server_time = $server_DH_inner_data['server_time'];
$this->datacenter->sockets[$datacenter]->time_delta = $server_time - time();
$this->logger->logger(sprintf('Server-client time delta = %.1f s', $this->datacenter->sockets[$datacenter]->time_delta), \danog\MadelineProto\Logger::VERBOSE);
$connection->time_delta = $server_time - \time();
$this->logger->logger(\sprintf('Server-client time delta = %.1f s', $connection->time_delta), \danog\MadelineProto\Logger::VERBOSE);
$this->check_p_g($dh_prime, $g);
$this->check_G($g_a, $dh_prime);
for ($retry_id = 0; $retry_id <= $this->settings['max_tries']['authorization']; $retry_id++) {
@ -301,8 +332,8 @@ trait AuthKeyHandler
* ***********************************************************************
* encrypt client_DH_inner_data
*/
$data_with_sha = sha1($data, true).$data;
$data_with_sha_padded = $data_with_sha.$this->random($this->posmod(-strlen($data_with_sha), 16));
$data_with_sha = \sha1($data, true).$data;
$data_with_sha_padded = $data_with_sha.$this->random($this->posmod(-\strlen($data_with_sha), 16));
$encrypted_data = $this->ige_encrypt($data_with_sha_padded, $tmp_aes_key, $tmp_aes_iv);
$this->logger->logger('Executing set_client_DH_params...', \danog\MadelineProto\Logger::VERBOSE);
/*
@ -322,7 +353,7 @@ trait AuthKeyHandler
* int128 $new_nonce_hash2 : Return this value if server responds with dh_gen_fail
* ]
*/
$Set_client_DH_params_answer = yield $this->method_call_async_read('set_client_DH_params', ['nonce' => $nonce, 'server_nonce' => $server_nonce, 'encrypted_data' => $encrypted_data], ['datacenter' => $datacenter]);
$Set_client_DH_params_answer = yield $connection->method_call_async_read('set_client_DH_params', ['nonce' => $nonce, 'server_nonce' => $server_nonce, 'encrypted_data' => $encrypted_data]);
/*
* ***********************************************************************
* Generate auth_key
@ -330,11 +361,11 @@ trait AuthKeyHandler
$this->logger->logger('Generating authorization key...', \danog\MadelineProto\Logger::VERBOSE);
$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);
$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);
/*
* ***********************************************************************
* Check if the client's nonce and the server's nonce are the same
@ -359,13 +390,17 @@ trait AuthKeyHandler
throw new \danog\MadelineProto\SecurityException('wrong new_nonce_hash1');
}
$this->logger->logger('Diffie Hellman key exchange processed successfully!', \danog\MadelineProto\Logger::VERBOSE);
$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);
$res_authorization['connection_inited'] = false;
$key = $expires_in < 0 ? new PermAuthKey() : new TempAuthKey();
if ($expires_in >= 0) {
$key->expires(\time() + $expires_in);
}
$key->setServerSalt(\substr($new_nonce, 0, 8 - 0) ^ \substr($server_nonce, 0, 8 - 0));
$key->setAuthKey($auth_key_str);
$this->logger->logger('Auth key generated', \danog\MadelineProto\Logger::NOTICE);
return $res_authorization;
return $key;
case 'dh_gen_retry':
if ($Set_client_DH_params_answer['new_nonce_hash2'] != $new_nonce_hash2) {
throw new \danog\MadelineProto\SecurityException('wrong new_nonce_hash_2');
@ -385,9 +420,9 @@ trait AuthKeyHandler
}
}
} catch (\danog\MadelineProto\SecurityException $e) {
$this->logger->logger('An exception occurred while generating the authorization key: '.$e->getMessage().' in '.basename($e->getFile(), '.php').' on line '.$e->getLine().'. Retrying...', \danog\MadelineProto\Logger::WARNING);
$this->logger->logger('An exception occurred while generating the authorization key: '.$e->getMessage().' in '.\basename($e->getFile(), '.php').' on line '.$e->getLine().'. Retrying...', \danog\MadelineProto\Logger::WARNING);
} catch (\danog\MadelineProto\Exception $e) {
$this->logger->logger('An exception occurred while generating the authorization key: '.$e->getMessage().' in '.basename($e->getFile(), '.php').' on line '.$e->getLine().'. Retrying...', \danog\MadelineProto\Logger::WARNING);
$this->logger->logger('An exception occurred while generating the authorization key: '.$e->getMessage().' in '.\basename($e->getFile(), '.php').' on line '.$e->getLine().'. Retrying...', \danog\MadelineProto\Logger::WARNING);
$req_pq = $req_pq === 'req_pq_multi' ? 'req_pq' : 'req_pq_multi';
} catch (\danog\MadelineProto\RPCErrorException $e) {
$this->logger->logger('An RPCErrorException occurred while generating the authorization key: '.$e->getMessage().' Retrying (try number '.$retry_id_total.')...', \danog\MadelineProto\Logger::WARNING);
@ -395,12 +430,20 @@ trait AuthKeyHandler
$this->logger->logger('An exception occurred while generating the authorization key: '.$e.PHP_EOL.' Retrying (try number '.$retry_id_total.')...', \danog\MadelineProto\Logger::WARNING);
}
}
if (strpos($datacenter, 'cdn') === false) {
if (\strpos($datacenter, 'cdn') === false) {
throw new \danog\MadelineProto\SecurityException('Auth Failed');
}
}
public function check_G($g_a, $p)
/**
* Check validity of g_a parameters.
*
* @param BigInteger $g_a
* @param BigInteger $p
*
* @return bool
*/
public function check_G(BigInteger $g_a, BigInteger $p): bool
{
/*
* ***********************************************************************
@ -419,7 +462,15 @@ trait AuthKeyHandler
return true;
}
public function check_p_g($p, $g)
/**
* Check validity of p and g parameters.
*
* @param BigInteger $p
* @param BigInteger $g
*
* @return boolean
*/
public function check_p_g(BigInteger $p, BigInteger $g): bool
{
/*
* ***********************************************************************
@ -465,7 +516,12 @@ trait AuthKeyHandler
return true;
}
public function get_dh_config_async()
/**
* Get diffie-hellman configuration.
*
* @return \Generator<array>
*/
public function get_dh_config_async(): \Generator
{
$dh_config = yield $this->method_call_async_read('messages.getDhConfig', ['version' => $this->dh_config['version'], 'random_length' => 0], ['datacenter' => $this->datacenter->curdc]);
if ($dh_config['_'] === 'messages.dhConfigNotModified') {
@ -480,29 +536,40 @@ trait AuthKeyHandler
return $this->dh_config = $dh_config;
}
public function bind_temp_auth_key_async($expires_in, $datacenter)
/**
* Bind temporary and permanent auth keys.
*
* @param integer $expires_in Date of expiry for binding
* @param string $datacenter DC ID
*
* @return \Generator<bool>
*/
public function bind_temp_auth_key_async(int $expires_in, string $datacenter): \Generator
{
$datacenterConnection = $this->datacenter->getDataCenterConnection($datacenter);
$connection = $datacenterConnection->getAuthConnection();
for ($retry_id_total = 1; $retry_id_total <= $this->settings['max_tries']['authorization']; $retry_id_total++) {
try {
$this->logger->logger('Binding authorization keys...', \danog\MadelineProto\Logger::VERBOSE);
$nonce = $this->random(8);
$expires_at = time() + $expires_in;
$temp_auth_key_id = $this->datacenter->sockets[$datacenter]->temp_auth_key['id'];
$perm_auth_key_id = $this->datacenter->sockets[$datacenter]->auth_key['id'];
$temp_session_id = $this->datacenter->sockets[$datacenter]->session_id;
$expires_at = \time() + $expires_in;
$temp_auth_key_id = $datacenterConnection->getTempAuthKey()->getID();
$perm_auth_key_id = $datacenterConnection->getPermAuthKey()->getID();
$temp_session_id = $connection->session_id;
$message_data = yield $this->serialize_object_async(['type' => 'bind_auth_key_inner'], ['nonce' => $nonce, 'temp_auth_key_id' => $temp_auth_key_id, 'perm_auth_key_id' => $perm_auth_key_id, 'temp_session_id' => $temp_session_id, 'expires_at' => $expires_at], 'bind_temp_auth_key_inner');
$message_id = $this->datacenter->sockets[$datacenter]->generate_message_id();
$message_id = $connection->generate_message_id();
$seq_no = 0;
$encrypted_data = $this->random(16).$message_id.pack('VV', $seq_no, strlen($message_data)).$message_data;
$message_key = substr(sha1($encrypted_data, true), -16);
$padding = $this->random($this->posmod(-strlen($encrypted_data), 16));
list($aes_key, $aes_iv) = $this->old_aes_calculate($message_key, $this->datacenter->sockets[$datacenter]->auth_key['auth_key']);
$encrypted_message = $this->datacenter->sockets[$datacenter]->auth_key['id'].$message_key.$this->ige_encrypt($encrypted_data.$padding, $aes_key, $aes_iv);
$res = yield $this->method_call_async_read('auth.bindTempAuthKey', ['perm_auth_key_id' => $perm_auth_key_id, 'nonce' => $nonce, 'expires_at' => $expires_at, 'encrypted_message' => $encrypted_message], ['msg_id' => $message_id, 'datacenter' => $datacenter]);
$encrypted_data = $this->random(16).$message_id.\pack('VV', $seq_no, \strlen($message_data)).$message_data;
$message_key = \substr(\sha1($encrypted_data, true), -16);
$padding = $this->random($this->posmod(-\strlen($encrypted_data), 16));
list($aes_key, $aes_iv) = $this->old_aes_calculate($message_key, $datacenterConnection->getPermAuthKey()->getAuthKey());
$encrypted_message = $datacenterConnection->getPermAuthKey()->getID().$message_key.$this->ige_encrypt($encrypted_data.$padding, $aes_key, $aes_iv);
$res = yield $connection->method_call_async_read('auth.bindTempAuthKey', ['perm_auth_key_id' => $perm_auth_key_id, 'nonce' => $nonce, 'expires_at' => $expires_at, 'encrypted_message' => $encrypted_message], ['msg_id' => $message_id]);
if ($res === true) {
$this->logger->logger('Successfully binded temporary and permanent authorization keys, DC '.$datacenter, \danog\MadelineProto\Logger::NOTICE);
$this->datacenter->sockets[$datacenter]->temp_auth_key['bound'] = true;
$this->datacenter->sockets[$datacenter]->writer->resume();
$datacenterConnection->bind();
$datacenterConnection->flush();
return true;
}
@ -518,7 +585,14 @@ trait AuthKeyHandler
throw new \danog\MadelineProto\SecurityException('An error occurred while binding temporary and permanent authorization keys.');
}
public function wolfram_single_async($what)
/**
* Factorize number asynchronously using the wolfram API.
*
* @param string|integer $what Number to factorize
*
* @return \Generator<string|bool>
*/
public function wolfram_single_async($what): \Generator
{
$code = yield $this->datacenter->fileGetContents('http://www.wolframalpha.com/api/v1/code');
$query = 'Do prime factorization of '.$what;
@ -530,28 +604,28 @@ trait AuthKeyHandler
'formattimeout' => 8,
'input' => $query,
'output' => 'JSON',
'proxycode' => json_decode($code, true)['code'],
'proxycode' => \json_decode($code, true)['code'],
];
$url = 'https://www.wolframalpha.com/input/json.jsp?'.http_build_query($params);
$url = 'https://www.wolframalpha.com/input/json.jsp?'.\http_build_query($params);
$request = (new Request($url))->withHeader('referer', 'https://www.wolframalpha.com/input/?i='.urlencode($query));
$request = (new Request($url))->withHeader('referer', 'https://www.wolframalpha.com/input/?i='.\urlencode($query));
$res = json_decode(yield (yield $this->datacenter->getHTTPClient()->request($request))->getBody(), true);
$res = \json_decode(yield (yield $this->datacenter->getHTTPClient()->request($request))->getBody(), true);
if (!isset($res['queryresult']['pods'])) {
return false;
}
$fres = false;
foreach ($res['queryresult']['pods'] as $cur) {
if ($cur['id'] === 'Divisors') {
$fres = explode(', ', preg_replace(["/{\d+, /", "/, \d+}$/"], '', $cur['subpods'][0]['moutput']));
$fres = \explode(', ', \preg_replace(["/{\d+, /", "/, \d+}$/"], '', $cur['subpods'][0]['moutput']));
break;
}
}
if (is_array($fres)) {
if (\is_array($fres)) {
$fres = $fres[0];
$newval = intval($fres);
if (is_int($newval)) {
$newval = \intval($fres);
if (\is_int($newval)) {
$fres = $newval;
}
@ -561,7 +635,12 @@ trait AuthKeyHandler
return false;
}
public function init_authorization_async()
/**
* Asynchronously create, bind and check auth keys for all DCs.
*
* @return \Generator
*/
public function init_authorization_async(): \Generator
{
if ($this->pending_auth) {
return;
@ -573,9 +652,9 @@ trait AuthKeyHandler
try {
$dcs = [];
$postpone = [];
foreach ($this->datacenter->sockets as $id => $socket) {
if (strpos($id, 'media') !== false) {
$oid = intval($id);
foreach ($this->datacenter->getDataCenterConnections() as $id => $socket) {
if (\strpos($id, 'media') !== false) {
$oid = \intval($id);
if (isset($dcs[$oid])) {
$postpone[$id] = $socket;
}
@ -590,7 +669,7 @@ trait AuthKeyHandler
};
}
if ($dcs) {
$first = array_shift($dcs)();
$first = \array_shift($dcs)();
yield $first;
}
@ -613,59 +692,65 @@ trait AuthKeyHandler
}
}
public function init_authorization_socket_async($id, $socket)
/**
* Init auth keys for single DC.
*
* @param string $id DC ID
* @param DataCenterConnection $socket DC object
*
* @return \Generator
*/
public function init_authorization_socket_async(string $id, DataCenterConnection $socket): \Generator
{
$this->init_auth_dcs[$id] = true;
$connection = $socket->getAuthConnection();
try {
if ($socket->session_id === null) {
$socket->session_id = $this->random(8);
$socket->session_in_seq_no = 0;
$socket->session_out_seq_no = 0;
if ($connection->session_id === null) {
$connection->session_id = $this->random(8);
$connection->session_in_seq_no = 0;
$connection->session_out_seq_no = 0;
}
$cdn = strpos($id, 'cdn');
$media = strpos($id, 'media');
if ($socket->temp_auth_key === null || $socket->auth_key === null) {
$cdn = \strpos($id, 'cdn');
$media = \strpos($id, 'media');
if (!$socket->hasTempAuthKey() || !$socket->hasPermAuthKey()) {
$dc_config_number = isset($this->settings['connection_settings'][$id]) ? $id : 'all';
if ($socket->auth_key === null && !$cdn && !$media) {
$this->logger->logger(sprintf(\danog\MadelineProto\Lang::$current_lang['gen_perm_auth_key'], $id), \danog\MadelineProto\Logger::NOTICE);
$socket->auth_key = yield $this->create_auth_key_async(-1, $id);
$socket->authorized = false;
} elseif ($socket->auth_key === null && $media) {
$socket->auth_key = $this->datacenter->sockets[intval($id)]->auth_key;
$socket->authorized = &$this->datacenter->sockets[intval($id)]->authorized;
if (!$socket->hasPermAuthKey() && !$cdn && !$media) {
$this->logger->logger(\sprintf(\danog\MadelineProto\Lang::$current_lang['gen_perm_auth_key'], $id), \danog\MadelineProto\Logger::NOTICE);
$socket->setPermAuthKey(yield $this->create_auth_key_async(-1, $id));
$socket->authorized(false);
}
if ($media) {
$socket->authorized = &$this->datacenter->sockets[intval($id)]->authorized;
$socket->link(\intval($id));
}
if ($this->settings['connection_settings'][$dc_config_number]['pfs']) {
if (!$cdn) {
$this->logger->logger(sprintf(\danog\MadelineProto\Lang::$current_lang['gen_temp_auth_key'], $id), \danog\MadelineProto\Logger::NOTICE);
$this->logger->logger(\sprintf(\danog\MadelineProto\Lang::$current_lang['gen_temp_auth_key'], $id), \danog\MadelineProto\Logger::NOTICE);
//$authorized = $socket->authorized;
//$socket->authorized = false;
$socket->temp_auth_key = null;
$socket->temp_auth_key = yield $this->create_auth_key_async($this->settings['authorization']['default_temp_auth_key_expires_in'], $id);
$socket->setTempAuthKey(null);
$socket->setTempAuthKey(yield $this->create_auth_key_async($this->settings['authorization']['default_temp_auth_key_expires_in'], $id));
yield $this->bind_temp_auth_key_async($this->settings['authorization']['default_temp_auth_key_expires_in'], $id);
$config = yield $this->method_call_async_read('help.getConfig', [], ['datacenter' => $id]);
yield $this->sync_authorization_async($id);
yield $this->get_config_async($config);
} elseif ($socket->temp_auth_key === null) {
$this->logger->logger(sprintf(\danog\MadelineProto\Lang::$current_lang['gen_temp_auth_key'], $id), \danog\MadelineProto\Logger::NOTICE);
$socket->temp_auth_key = yield $this->create_auth_key_async($this->settings['authorization']['default_temp_auth_key_expires_in'], $id);
} elseif (!$socket->hasTempAuthKey()) {
$this->logger->logger(\sprintf(\danog\MadelineProto\Lang::$current_lang['gen_temp_auth_key'], $id), \danog\MadelineProto\Logger::NOTICE);
$socket->setTempAuthKey(yield $this->create_auth_key_async($this->settings['authorization']['default_temp_auth_key_expires_in'], $id));
}
} else {
if (!$cdn) {
$socket->temp_auth_key = $socket->auth_key;
$socket->bind(false);
$config = yield $this->method_call_async_read('help.getConfig', [], ['datacenter' => $id]);
yield $this->sync_authorization_async($id);
yield $this->get_config_async($config);
} elseif ($socket->temp_auth_key === null) {
$this->logger->logger(sprintf(\danog\MadelineProto\Lang::$current_lang['gen_temp_auth_key'], $id), \danog\MadelineProto\Logger::NOTICE);
$socket->temp_auth_key = yield $this->create_auth_key_async($this->settings['authorization']['default_temp_auth_key_expires_in'], $id);
} elseif (!$socket->hasTempAuthKey()) {
$this->logger->logger(\sprintf(\danog\MadelineProto\Lang::$current_lang['gen_temp_auth_key'], $id), \danog\MadelineProto\Logger::NOTICE);
$socket->setTempAuthKey(yield $this->create_auth_key_async($this->settings['authorization']['default_temp_auth_key_expires_in'], $id));
}
}
} elseif (!$cdn) {
@ -676,23 +761,30 @@ trait AuthKeyHandler
}
}
public function sync_authorization_async($id)
/**
* Sync authorization data between DCs.
*
* @param string $id DC ID
*
* @return \Generator
*/
public function sync_authorization_async(string $id): \Generator
{
if (!isset($this->datacenter->sockets[$id])) {
if (!$this->datacenter->has($id)) {
return false;
}
$socket = $this->datacenter->sockets[$id];
if ($this->authorized === self::LOGGED_IN && $socket->authorized === false) {
foreach ($this->datacenter->sockets as $authorized_dc_id => $authorized_socket) {
$socket = $this->datacenter->getDataCenterConnection($id);
if ($this->authorized === self::LOGGED_IN && !$socket->isAuthorized()) {
foreach ($this->datacenter->getDataCenterConnections() as $authorized_dc_id => $authorized_socket) {
if ($this->authorized_dc !== -1 && $authorized_dc_id !== $this->authorized_dc) {
continue;
}
if ($authorized_socket->temp_auth_key !== null && $authorized_socket->auth_key !== null && $authorized_socket->authorized === true && $this->authorized === self::LOGGED_IN && $socket->authorized === false && strpos($authorized_dc_id, 'cdn') === false) {
if ($authorized_socket->hasTempAuthKey() && $authorized_socket->hasPermAuthKey() && $authorized_socket->isAuthorized() && $this->authorized === self::LOGGED_IN && !$socket->isAuthorized() && \strpos($authorized_dc_id, 'cdn') === false) {
try {
$this->logger->logger('Trying to copy authorization from dc '.$authorized_dc_id.' to dc '.$id);
$exported_authorization = yield $this->method_call_async_read('auth.exportAuthorization', ['dc_id' => preg_replace('|_.*|', '', $id)], ['datacenter' => $authorized_dc_id]);
$exported_authorization = yield $this->method_call_async_read('auth.exportAuthorization', ['dc_id' => \preg_replace('|_.*|', '', $id)], ['datacenter' => $authorized_dc_id]);
$authorization = yield $this->method_call_async_read('auth.importAuthorization', $exported_authorization, ['datacenter' => $id]);
$socket->authorized = true;
$socket->authorized(true);
break;
} catch (\danog\MadelineProto\Exception $e) {
$this->logger->logger('Failure while syncing authorization from DC '.$authorized_dc_id.' to DC '.$id.': '.$e->getMessage(), \danog\MadelineProto\Logger::ERROR);

View File

@ -19,15 +19,15 @@
namespace danog\MadelineProto\MTProtoTools;
use Amp\Artax\Client;
use Amp\ByteStream\InputStream;
use Amp\ByteStream\OutputStream;
use Amp\ByteStream\ResourceInputStream;
use Amp\ByteStream\ResourceOutputStream;
use Amp\ByteStream\StreamException;
use Amp\Deferred;
use Amp\File\BlockingHandle;
use Amp\File\Handle;
use Amp\File\StatCache;
use Amp\Promise;
use Amp\Success;
use danog\MadelineProto\Async\AsyncParameters;
use danog\MadelineProto\Exception;
@ -37,14 +37,10 @@ use danog\MadelineProto\RPCErrorException;
use danog\MadelineProto\Stream\Common\SimpleBufferedRawStream;
use danog\MadelineProto\Stream\ConnectionContext;
use danog\MadelineProto\Stream\Transport\PremadeStream;
use danog\MadelineProto\Tools;
use function Amp\File\exists;
use function Amp\File\open;
use function Amp\File\stat;
use function Amp\File\touch;
use danog\MadelineProto\Tools;
use function Amp\Promise\all;
use Amp\File\BlockingHandle;
use Amp\Artax\Client;
/**
* Manages upload and download of files.
@ -53,15 +49,15 @@ trait Files
{
public function upload_async($file, $file_name = '', $cb = null, $encrypted = false)
{
if (is_object($file) && $file instanceof FileCallbackInterface) {
if (\is_object($file) && $file instanceof FileCallbackInterface) {
$cb = $file;
$file = $file->getFile();
}
if (is_string($file) || (is_object($file) && method_exists($file, '__toString'))) {
if (filter_var($file, FILTER_VALIDATE_URL)) {
if (\is_string($file) || (\is_object($file) && \method_exists($file, '__toString'))) {
if (\filter_var($file, FILTER_VALIDATE_URL)) {
return yield $this->upload_from_url_async($file);
}
} else if (is_array($file)) {
} elseif (\is_array($file)) {
return yield $this->upload_from_tgfile_async($file, $cb, $encrypted);
}
@ -70,12 +66,12 @@ trait Files
throw new \danog\MadelineProto\Exception(\danog\MadelineProto\Lang::$current_lang['file_not_exist']);
}
if (empty($file_name)) {
$file_name = basename($file);
$file_name = \basename($file);
}
StatCache::clear($file);
$size = (yield stat($file))['size'];
$size = (yield \stat($file))['size'];
if ($size > 512 * 1024 * 3000) {
throw new \danog\MadelineProto\Exception('Given file is too big!');
}
@ -91,7 +87,7 @@ trait Files
}
public function upload_from_url_async($url, int $size = 0, string $file_name = '', $cb = null, bool $encrypted = false)
{
if (is_object($url) && $url instanceof FileCallbackInterface) {
if (\is_object($url) && $url instanceof FileCallbackInterface) {
$cb = $url;
$url = $url->getFile();
}
@ -100,7 +96,7 @@ trait Files
if (200 !== $status = $response->getStatus()) {
throw new Exception("Wrong status code: $status ".$response->getReason());
}
$mime = trim(explode(';', $response->getHeader('content-type') ?? 'application/octet-stream')[0]);
$mime = \trim(\explode(';', $response->getHeader('content-type') ?? 'application/octet-stream')[0]);
$size = $response->getHeader('content-length') ?? $size;
$stream = $response->getBody();
@ -108,7 +104,7 @@ trait Files
$this->logger->logger("No content length for $url, caching first");
$body = $stream;
$stream = new BlockingHandle(fopen('php://temp', 'r+b'), 'php://temp', 'r+b');
$stream = new BlockingHandle(\fopen('php://temp', 'r+b'), 'php://temp', 'r+b');
while (null !== $chunk = yield $body->read()) {
yield $stream->write($chunk);
@ -124,20 +120,20 @@ trait Files
}
public function upload_from_stream_async($stream, int $size, string $mime, string $file_name = '', $cb = null, bool $encrypted = false)
{
if (is_object($stream) && $stream instanceof FileCallbackInterface) {
if (\is_object($stream) && $stream instanceof FileCallbackInterface) {
$cb = $stream;
$stream = $stream->getFile();
}
/** @var $stream \Amp\ByteStream\OutputStream */
if (!is_object($stream)) {
if (!\is_object($stream)) {
$stream = new ResourceOutputStream($stream);
}
if (!$stream instanceof InputStream) {
throw new Exception("Invalid stream provided");
}
$seekable = false;
if (method_exists($stream, 'seek')) {
if (\method_exists($stream, 'seek')) {
try {
yield $stream->seek(0);
$seekable = true;
@ -184,11 +180,11 @@ trait Files
}
public function upload_from_callable_async($callable, int $size, string $mime, string $file_name = '', $cb = null, bool $refetchable = true, bool $encrypted = false)
{
if (is_object($callable) && $callable instanceof FileCallbackInterface) {
if (\is_object($callable) && $callable instanceof FileCallbackInterface) {
$cb = $callable;
$callable = $callable->getFile();
}
if (!is_callable($callable)) {
if (!\is_callable($callable)) {
throw new Exception('Invalid callable provided');
}
if ($cb === null) {
@ -198,14 +194,14 @@ trait Files
}
$datacenter = $this->settings['connection_settings']['default_dc'];
if (isset($this->datacenter->sockets[$datacenter.'_media'])) {
if ($this->datacenter->has($datacenter.'_media')) {
$datacenter .= '_media';
}
$part_size = $this->settings['upload']['part_size'];
$parallel_chunks = $this->settings['upload']['parallel_chunks'] ? $this->settings['upload']['parallel_chunks'] : 3000;
$part_total_num = (int) ceil($size / $part_size);
$part_total_num = (int) \ceil($size / $part_size);
$part_num = 0;
$method = $size > 10 * 1024 * 1024 ? 'upload.saveBigFilePart' : 'upload.saveFilePart';
$constructor = 'input'.($encrypted === true ? 'Encrypted' : '').($size > 10 * 1024 * 1024 ? 'FileBig' : 'File').($encrypted === true ? 'Uploaded' : '');
@ -215,15 +211,15 @@ trait Files
if ($encrypted === true) {
$key = $this->random(32);
$iv = $this->random(32);
$digest = hash('md5', $key.$iv, true);
$fingerprint = $this->unpack_signed_int(substr($digest, 0, 4) ^ substr($digest, 4, 4));
$digest = \hash('md5', $key.$iv, true);
$fingerprint = $this->unpack_signed_int(\substr($digest, 0, 4) ^ \substr($digest, 4, 4));
$ige = new \phpseclib\Crypt\AES('ige');
$ige->setIV($iv);
$ige->setKey($key);
$ige->enableContinuousBuffer();
$refetchable = false;
}
$ctx = hash_init('md5');
$ctx = \hash_init('md5');
$promises = [];
$cb = function () use ($cb, $part_total_num) {
@ -232,7 +228,7 @@ trait Files
$this->callFork($cb($cur * 100 / $part_total_num));
};
$start = microtime(true);
$start = \microtime(true);
while ($part_num < $part_total_num) {
$read_deferred = yield $this->method_call_async_write(
$method,
@ -245,10 +241,10 @@ trait Files
$bytes = yield $callable($part_num * $part_size, $part_size);
if (!$already_fetched) {
hash_update($ctx, $bytes);
\hash_update($ctx, $bytes);
}
if ($ige) {
$bytes = $ige->encrypt(str_pad($bytes, $part_size, chr(0)));
$bytes = $ige->encrypt(\str_pad($bytes, $part_size, \chr(0)));
}
return ['file_id' => $file_id, 'file_part' => $part_num, 'file_total_parts' => $part_total_num, 'bytes' => $bytes];
@ -275,7 +271,7 @@ trait Files
}
$promises = [];
$time = microtime(true) - $start;
$time = \microtime(true) - $start;
$speed = (int) (($size * 8) / $time) / 1000000;
$this->logger->logger("Partial upload time: $time");
$this->logger->logger("Partial upload speed: $speed mbps");
@ -288,7 +284,7 @@ trait Files
throw new \danog\MadelineProto\Exception('Upload of part '.$kkey.' failed');
}
}
$time = microtime(true) - $start;
$time = \microtime(true) - $start;
$speed = (int) (($size * 8) / $time) / 1000000;
$this->logger->logger("Total upload time: $time");
$this->logger->logger("Total upload speed: $speed mbps");
@ -299,7 +295,7 @@ trait Files
$constructor['key'] = $key;
$constructor['iv'] = $iv;
}
$constructor['md5_checksum'] = hash_final($ctx);
$constructor['md5_checksum'] = \hash_final($ctx);
return $constructor;
}
@ -311,7 +307,7 @@ trait Files
public function upload_from_tgfile_async($media, $cb = null, $encrypted = false)
{
if (is_object($media) && $media instanceof FileCallbackInterface) {
if (\is_object($media) && $media instanceof FileCallbackInterface) {
$cb = $media;
$media = $media->getFile();
}
@ -324,9 +320,7 @@ trait Files
$chunk_size = $this->settings['upload']['part_size'];
$bridge = new class
{
$bridge = new class {
private $done = [];
private $pending = [];
public $nextRead;
@ -343,7 +337,7 @@ trait Files
}
if (isset($this->done[$offset])) {
if (strlen($this->done[$offset]) > $size) {
if (\strlen($this->done[$offset]) > $size) {
throw new Exception('Wrong size!');
}
$result = $this->done[$offset];
@ -362,7 +356,7 @@ trait Files
} else {
$this->done[$offset] = $data;
}
$length = strlen($data);
$length = \strlen($data);
if ($offset + $length === $this->size || $length < $this->part_size) {
return;
}
@ -477,7 +471,7 @@ trait Files
public function get_file_info_async($constructor)
{
if (is_string($constructor)) {
if (\is_string($constructor)) {
$constructor = $this->unpack_file_id($constructor)['MessageMedia'];
}
switch ($constructor['_']) {
@ -487,6 +481,7 @@ trait Files
case 'updateEditChannelMessage':
$constructor = $constructor['message'];
// no break
case 'message':
$constructor = $constructor['media'];
}
@ -499,7 +494,7 @@ trait Files
}
public function get_download_info_async($message_media)
{
if (is_string($message_media)) {
if (\is_string($message_media)) {
$message_media = $this->unpack_file_id($message_media)['MessageMedia'];
}
if (!isset($message_media['_'])) {
@ -511,12 +506,14 @@ trait Files
case 'updateNewMessage':
case 'updateNewChannelMessage':
$message_media = $message_media['message'];
// no break
case 'message':
return yield $this->get_download_info_async($message_media['media']);
case 'updateNewEncryptedMessage':
$message_media = $message_media['message'];
// Secret media
// no break
case 'encryptedMessage':
if ($message_media['decrypted_message']['media']['_'] === 'decryptedMessageMediaExternalDocument') {
return yield $this->get_download_info_async($message_media['decrypted_message']['media']);
@ -527,7 +524,7 @@ trait Files
$res['key'] = $message_media['decrypted_message']['media']['key'];
$res['iv'] = $message_media['decrypted_message']['media']['iv'];
if (isset($message_media['decrypted_message']['media']['file_name'])) {
$pathinfo = pathinfo($message_media['decrypted_message']['media']['file_name']);
$pathinfo = \pathinfo($message_media['decrypted_message']['media']['file_name']);
if (isset($pathinfo['extension'])) {
$res['ext'] = '.'.$pathinfo['extension'];
}
@ -542,7 +539,7 @@ trait Files
foreach ($message_media['decrypted_message']['media']['attributes'] as $attribute) {
switch ($attribute['_']) {
case 'documentAttributeFilename':
$pathinfo = pathinfo($attribute['file_name']);
$pathinfo = \pathinfo($attribute['file_name']);
if (isset($pathinfo['extension'])) {
$res['ext'] = '.'.$pathinfo['extension'];
}
@ -582,9 +579,9 @@ trait Files
}
$res['MessageMedia'] = $message_media;
$message_media = $message_media['photo'];
$size = end($message_media['sizes']);
$size = \end($message_media['sizes']);
$res = array_merge($res, yield $this->get_download_info_async($size));
$res = \array_merge($res, yield $this->get_download_info_async($size));
$res['InputFileLocation'] = [
'_' => 'inputPhotoFileLocation',
@ -625,13 +622,13 @@ trait Files
$res['InputFileLocation']['dc_id'] = $message_media['dc_id'];
return $res;
case 'photoStrippedSize':
$res['size'] = strlen($message_media['bytes']);
$res['size'] = \strlen($message_media['bytes']);
$res['data'] = $message_media['bytes'];
$res['thumb_size'] = 'JPG';
return $res;
case 'photoCachedSize':
$res['size'] = strlen($message_media['bytes']);
$res['size'] = \strlen($message_media['bytes']);
$res['data'] = $message_media['bytes'];
//$res['thumb_size'] = $res['data'];
$res['thumb_size'] = $message_media['type'];
@ -641,7 +638,7 @@ trait Files
$res['mime'] = $this->get_mime_from_buffer($res['data']);
$res['ext'] = $this->get_extension_from_mime($res['mime']);
} else {
$res = array_merge($res, yield $this->get_download_info_async($message_media['location']));
$res = \array_merge($res, yield $this->get_download_info_async($message_media['location']));
}
return $res;
@ -691,13 +688,14 @@ trait Files
case 'decryptedMessageMediaExternalDocument':
case 'document':
$message_media = ['_' => 'messageMediaDocument', 'ttl_seconds' => 0, 'document' => $message_media];
// no break
case 'messageMediaDocument':
$res['MessageMedia'] = $message_media;
foreach ($message_media['document']['attributes'] as $attribute) {
switch ($attribute['_']) {
case 'documentAttributeFilename':
$pathinfo = pathinfo($attribute['file_name']);
$pathinfo = \pathinfo($attribute['file_name']);
if (isset($pathinfo['extension'])) {
$res['ext'] = '.'.$pathinfo['extension'];
}
@ -842,7 +840,7 @@ trait Files
}
public function download_to_dir_async($message_media, $dir, $cb = null)
{
if (is_object($dir) && $dir instanceof FileCallbackInterface) {
if (\is_object($dir) && $dir instanceof FileCallbackInterface) {
$cb = $dir;
$dir = $dir->getFile();
}
@ -854,20 +852,20 @@ trait Files
public function download_to_file_async($message_media, $file, $cb = null)
{
if (is_object($file) && $file instanceof FileCallbackInterface) {
if (\is_object($file) && $file instanceof FileCallbackInterface) {
$cb = $file;
$file = $file->getFile();
}
$file = \danog\MadelineProto\Absolute::absolute(preg_replace('|/+|', '/', $file));
$file = \danog\MadelineProto\Absolute::absolute(\preg_replace('|/+|', '/', $file));
if (!yield exists($file)) {
yield touch($file);
yield \touch($file);
}
$file = realpath($file);
$file = \realpath($file);
$message_media = yield $this->get_download_info_async($message_media);
StatCache::clear($file);
$size = (yield stat($file))['size'];
$size = (yield \stat($file))['size'];
$stream = yield open($file, 'cb');
$this->logger->logger('Waiting for lock of file to download...');
@ -887,20 +885,20 @@ trait Files
{
$message_media = yield $this->get_download_info_async($message_media);
if (is_object($stream) && $stream instanceof FileCallbackInterface) {
if (\is_object($stream) && $stream instanceof FileCallbackInterface) {
$cb = $stream;
$stream = $stream->getFile();
}
/** @var $stream \Amp\ByteStream\OutputStream */
if (!is_object($stream)) {
if (!\is_object($stream)) {
$stream = new ResourceOutputStream($stream);
}
if (!$stream instanceof OutputStream) {
throw new Exception("Invalid stream provided");
}
$seekable = false;
if (method_exists($stream, 'seek')) {
if (\method_exists($stream, 'seek')) {
try {
yield $stream->seek($offset);
$seekable = true;
@ -922,12 +920,12 @@ trait Files
{
$message_media = yield $this->get_download_info_async($message_media);
if (is_object($callable) && $callable instanceof FileCallbackInterface) {
if (\is_object($callable) && $callable instanceof FileCallbackInterface) {
$cb = $callable;
$callable = $callable->getFile();
}
if (!is_callable($callable)) {
if (!\is_callable($callable)) {
throw new Exception('Wrong callable provided');
}
if ($cb === null) {
@ -944,13 +942,13 @@ trait Files
$parallel_chunks = $this->settings['download']['parallel_chunks'] ? $this->settings['download']['parallel_chunks'] : 3000;
$datacenter = isset($message_media['InputFileLocation']['dc_id']) ? $message_media['InputFileLocation']['dc_id'] : $this->settings['connection_settings']['default_dc'];
if (isset($this->datacenter->sockets[$datacenter.'_media'])) {
if ($this->datacenter->has($datacenter.'_media')) {
$datacenter .= '_media';
}
if (isset($message_media['key'])) {
$digest = hash('md5', $message_media['key'].$message_media['iv'], true);
$fingerprint = $this->unpack_signed_int(substr($digest, 0, 4) ^ substr($digest, 4, 4));
$digest = \hash('md5', $message_media['key'].$message_media['iv'], true);
$fingerprint = $this->unpack_signed_int(\substr($digest, 0, 4) ^ \substr($digest, 4, 4));
if ($fingerprint !== $message_media['key_fingerprint']) {
throw new \danog\MadelineProto\Exception('Fingerprint mismatch!');
}
@ -995,7 +993,7 @@ trait Files
$cb(100);
return true;
}
$count = count($params);
$count = \count($params);
$cb = function () use ($cb, $count) {
static $cur = 0;
@ -1007,8 +1005,8 @@ trait Files
$params[0]['previous_promise'] = new Success(true);
$start = microtime(true);
$size = yield $this->download_part($message_media, $cdn, $datacenter, $old_dc, $ige, $cb, array_shift($params), $callable, $parallelize);
$start = \microtime(true);
$size = yield $this->download_part($message_media, $cdn, $datacenter, $old_dc, $ige, $cb, \array_shift($params), $callable, $parallelize);
if ($params) {
$previous_promise = new Success(true);
@ -1029,7 +1027,7 @@ trait Files
yield $this->all($promises);
$promises = [];
$time = microtime(true) - $start;
$time = \microtime(true) - $start;
$speed = (int) (($size * 8) / $time) / 1000000;
$this->logger->logger("Partial download time: $time");
$this->logger->logger("Partial download speed: $speed mbps");
@ -1039,7 +1037,7 @@ trait Files
yield $this->all($promises);
}
}
$time = microtime(true) - $start;
$time = \microtime(true) - $start;
$speed = (int) (($size * 8) / $time) / 1000000;
$this->logger->logger("Total download time: $time");
$this->logger->logger("Total download speed: $speed mbps");
@ -1081,7 +1079,7 @@ trait Files
]
);
} catch (\danog\MadelineProto\RPCErrorException $e) {
if (strpos($e->rpc, 'FLOOD_WAIT_') === 0) {
if (\strpos($e->rpc, 'FLOOD_WAIT_') === 0) {
if (isset($message_media['MessageMedia']) && !$this->authorization['user']['bot'] && $this->settings['download']['report_broken_media']) {
try {
yield $this->method_call_async_read('messages.sendMedia', ['peer' => 'support', 'media' => $message_media['MessageMedia'], 'message' => "I can't download this file, could you please help?"], ['datacenter' => $this->datacenter->curdc]);
@ -1110,12 +1108,12 @@ trait Files
$message_media['cdn_iv'] = $res['encryption_iv'];
$old_dc = $datacenter;
$datacenter = $res['dc_id'].'_cdn';
if (!isset($this->datacenter->sockets[$datacenter])) {
if (!$this->datacenter->has($datacenter)) {
$this->config['expires'] = -1;
yield $this->get_config_async([], ['datacenter' => $this->datacenter->curdc]);
}
$this->logger->logger(\danog\MadelineProto\Lang::$current_lang['stored_on_cdn'], \danog\MadelineProto\Logger::NOTICE);
} else if ($res['_'] === 'upload.cdnFileReuploadNeeded') {
} elseif ($res['_'] === 'upload.cdnFileReuploadNeeded') {
$this->logger->logger(\danog\MadelineProto\Lang::$current_lang['cdn_reupload'], \danog\MadelineProto\Logger::NOTICE);
yield $this->get_config_async([], ['datacenter' => $this->datacenter->curdc]);
@ -1139,13 +1137,13 @@ trait Files
while ($cdn === false &&
$res['type']['_'] === 'storage.fileUnknown' &&
$res['bytes'] === '' &&
isset($this->datacenter->sockets[++$datacenter])
$this->datacenter->has(++$datacenter)
) {
$res = yield $this->method_call_async_read('upload.getFile', $basic_param + $offset, ['heavy' => true, 'file' => true, 'FloodWaitLimit' => 0, 'datacenter' => $datacenter]);
}
if (isset($message_media['cdn_key'])) {
$ivec = substr($message_media['cdn_iv'], 0, 12).pack('N', $offset['offset'] >> 4);
$ivec = \substr($message_media['cdn_iv'], 0, 12).\pack('N', $offset['offset'] >> 4);
$res['bytes'] = $this->ctr_encrypt($res['bytes'], $message_media['cdn_key'], $ivec);
$this->check_cdn_hash($message_media['file_token'], $offset['offset'], $res['bytes'], $old_dc);
}
@ -1153,7 +1151,7 @@ trait Files
$res['bytes'] = $ige->decrypt($res['bytes']);
}
if ($offset['part_start_at'] || $offset['part_end_at'] !== $offset['limit']) {
$res['bytes'] = substr($res['bytes'], $offset['part_start_at'], $offset['part_end_at'] - $offset['part_start_at']);
$res['bytes'] = \substr($res['bytes'], $offset['part_start_at'], $offset['part_end_at'] - $offset['part_start_at']);
}
if (!$seekable) {
@ -1179,17 +1177,17 @@ trait Files
private function check_cdn_hash($file, $offset, $data, &$datacenter)
{
while (strlen($data)) {
while (\strlen($data)) {
if (!isset($this->cdn_hashes[$file][$offset])) {
$this->add_cdn_hashes($file, yield $this->method_call_async_read('upload.getCdnFileHashes', ['file_token' => $file, 'offset' => $offset], ['datacenter' => $datacenter]));
}
if (!isset($this->cdn_hashes[$file][$offset])) {
throw new \danog\MadelineProto\Exception('Could not fetch CDN hashes for offset '.$offset);
}
if (hash('sha256', substr($data, 0, $this->cdn_hashes[$file][$offset]['limit']), true) !== $this->cdn_hashes[$file][$offset]['hash']) {
if (\hash('sha256', \substr($data, 0, $this->cdn_hashes[$file][$offset]['limit']), true) !== $this->cdn_hashes[$file][$offset]['hash']) {
throw new \danog\MadelineProto\SecurityException('CDN hash mismatch for offset '.$offset);
}
$data = substr($data, $this->cdn_hashes[$file][$offset]['limit']);
$data = \substr($data, $this->cdn_hashes[$file][$offset]['limit']);
$offset += $this->cdn_hashes[$file][$offset]['limit'];
}

View File

@ -53,6 +53,12 @@ class ConnectionContext
* @var bool
*/
private $media = false;
/**
* Whether to use CDN servers.
*
* @var bool
*/
private $cdn = false;
/**
* The connection URI.
*
@ -232,6 +238,16 @@ class ConnectionContext
return $this->media;
}
/**
* Whether this is a CDN connection
*
* @return bool
*/
public function isCDN(): bool
{
return $this->cdn;
}
/**
* Whether this connection context will only be used by the DNS client
*
@ -292,6 +308,7 @@ class ConnectionContext
}
$this->dc = $dc;
$this->media = strpos($dc, '_media') !== false;
$this->cdn = strpos($dc, '_cdn') !== false;
return $this;
}

View File

@ -19,6 +19,7 @@
namespace danog\MadelineProto\Wrappers;
use danog\MadelineProto\AuthKey\PermAuthKey;
use danog\MadelineProto\MTProtoTools\PasswordCalculator;
/**
@ -26,6 +27,12 @@ use danog\MadelineProto\MTProtoTools\PasswordCalculator;
*/
trait Login
{
/**
* Datacenter instance.
*
* @var \danog\MadelineProto\DataCenter
*/
public $datacenter;
public function logout_async()
{
yield $this->method_call_async_read('auth.logOut', [], ['datacenter' => $this->datacenter->curdc]);
@ -46,7 +53,7 @@ trait Login
$this->authorization = yield $this->method_call_async_read('auth.importBotAuthorization', ['bot_auth_token' => $token, 'api_id' => $this->settings['app_info']['api_id'], 'api_hash' => $this->settings['app_info']['api_hash']], ['datacenter' => $this->datacenter->curdc]);
$this->authorized = self::LOGGED_IN;
$this->authorized_dc = $this->datacenter->curdc;
$this->datacenter->sockets[$this->datacenter->curdc]->authorized = true;
$this->datacenter->getDataCenterConnection($this->datacenter->curdc)->authorized(true);
$this->updates = [];
$this->updates_key = 0;
yield $this->init_authorization_async();
@ -117,7 +124,7 @@ trait Login
}
$this->authorized = self::LOGGED_IN;
$this->authorization = $authorization;
$this->datacenter->sockets[$this->datacenter->curdc]->authorized = true;
$this->datacenter->getDataCenterConnection($this->datacenter->curdc)->authorized(true);
yield $this->init_authorization_async();
yield $this->get_phone_config_async();
$this->startUpdateSystem();
@ -135,20 +142,16 @@ trait Login
}
$this->logger->logger(\danog\MadelineProto\Lang::$current_lang['login_auth_key'], \danog\MadelineProto\Logger::NOTICE);
list($dc_id, $auth_key) = $authorization;
if (!is_array($auth_key)) {
$auth_key = ['auth_key' => $auth_key, 'id' => substr(sha1($auth_key, true), -8), 'server_salt' => ''];
if (!\is_array($auth_key)) {
$auth_key = ['auth_key' => $auth_key];
}
$auth_key = new PermAuthKey($auth_key);
$this->authorized_dc = $dc_id;
$this->datacenter->sockets[$dc_id]->session_id = $this->random(8);
$this->datacenter->sockets[$dc_id]->session_in_seq_no = 0;
$this->datacenter->sockets[$dc_id]->session_out_seq_no = 0;
$this->datacenter->sockets[$dc_id]->auth_key = $auth_key;
$this->datacenter->sockets[$dc_id]->temp_auth_key = null;
$this->datacenter->sockets[$dc_id]->incoming_messages = [];
$this->datacenter->sockets[$dc_id]->outgoing_messages = [];
$this->datacenter->sockets[$dc_id]->new_outgoing = [];
$this->datacenter->sockets[$dc_id]->new_incoming = [];
$this->datacenter->sockets[$dc_id]->authorized = true;
$dataCenterConnection = $this->datacenter->getDataCenterConnection($dc_id);
$dataCenterConnection->resetSession();
$dataCenterConnection->setPermAuthKey($auth_key);
$dataCenterConnection->authorized(true);
$this->authorized = self::LOGGED_IN;
yield $this->init_authorization_async();
yield $this->get_phone_config_async();
@ -168,7 +171,7 @@ trait Login
yield $this->get_self_async();
$this->authorized_dc = $this->datacenter->curdc;
return [$this->datacenter->curdc, $this->datacenter->sockets[$this->datacenter->curdc]->auth_key['auth_key']];
return [$this->datacenter->curdc, $this->datacenter->getDataCenterConnection($this->datacenter->curdc)->getPermAuthKey()->getAuthKey()];
}
public function complete_signup_async($first_name, $last_name)
@ -180,7 +183,7 @@ trait Login
$this->logger->logger(\danog\MadelineProto\Lang::$current_lang['signing_up'], \danog\MadelineProto\Logger::NOTICE);
$this->authorization = yield $this->method_call_async_read('auth.signUp', ['phone_number' => $this->authorization['phone_number'], 'phone_code_hash' => $this->authorization['phone_code_hash'], 'phone_code' => $this->authorization['phone_code'], 'first_name' => $first_name, 'last_name' => $last_name], ['datacenter' => $this->datacenter->curdc]);
$this->authorized = self::LOGGED_IN;
$this->datacenter->sockets[$this->datacenter->curdc]->authorized = true;
$this->datacenter->getDataCenterConnection($this->datacenter->curdc)->authorized(true);
yield $this->init_authorization_async();
yield $this->get_phone_config_async();
@ -201,7 +204,7 @@ trait Login
$this->logger->logger(\danog\MadelineProto\Lang::$current_lang['login_user'], \danog\MadelineProto\Logger::NOTICE);
$this->authorization = yield $this->method_call_async_read('auth.checkPassword', ['password' => $hasher->getCheckPassword($password)], ['datacenter' => $this->datacenter->curdc]);
$this->authorized = self::LOGGED_IN;
$this->datacenter->sockets[$this->datacenter->curdc]->authorized = true;
$this->datacenter->getDataCenterConnection($this->datacenter->curdc)->authorized(true);
yield $this->init_authorization_async();
$this->logger->logger(\danog\MadelineProto\Lang::$current_lang['login_ok'], \danog\MadelineProto\Logger::NOTICE);
yield $this->get_phone_config_async();
@ -211,10 +214,10 @@ trait Login
}
/**
* Update the 2FA password
* Update the 2FA password.
*
* The params array can contain password, new_password, email and hint params.
*
*
* @param array $params The params
* @return void
*/