From ea2b28dbb170ff8cad140c907f0b41a560595276 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Sun, 1 Sep 2019 23:39:29 +0200 Subject: [PATCH] Refactor auth key management --- src/danog/MadelineProto/API.php | 4 +- src/danog/MadelineProto/APIFactory.php | 6 +- src/danog/MadelineProto/Connection.php | 24 +- src/danog/MadelineProto/DataCenter.php | 61 +++- .../MadelineProto/DataCenterConnection.php | 139 +++++++-- .../Loop/Connection/ReadLoop.php | 2 +- src/danog/MadelineProto/MTProto.php | 155 ++++++++-- src/danog/MadelineProto/MTProto/AuthKey.php | 121 ++------ .../MadelineProto/MTProto/PermAuthKey.php | 81 +++++ .../MadelineProto/MTProto/TempAuthKey.php | 173 +++++++++++ .../MTProtoSession/CallHandler.php | 6 +- .../MTProtoSession/ResponseHandler.php | 25 +- .../MadelineProto/MTProtoSession/Session.php | 3 +- .../MTProtoTools/AuthKeyHandler.php | 290 ++++++++++++------ .../MadelineProto/MTProtoTools/Files.php | 150 +++++---- .../Stream/ConnectionContext.php | 17 + src/danog/MadelineProto/Wrappers/Login.php | 41 +-- 17 files changed, 918 insertions(+), 380 deletions(-) create mode 100644 src/danog/MadelineProto/MTProto/PermAuthKey.php create mode 100644 src/danog/MadelineProto/MTProto/TempAuthKey.php diff --git a/src/danog/MadelineProto/API.php b/src/danog/MadelineProto/API.php index 84898137..1e3ab20c 100644 --- a/src/danog/MadelineProto/API.php +++ b/src/danog/MadelineProto/API.php @@ -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) { diff --git a/src/danog/MadelineProto/APIFactory.php b/src/danog/MadelineProto/APIFactory.php index 663ec4e0..dc113fea 100644 --- a/src/danog/MadelineProto/APIFactory.php +++ b/src/danog/MadelineProto/APIFactory.php @@ -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; } diff --git a/src/danog/MadelineProto/Connection.php b/src/danog/MadelineProto/Connection.php index 19d2eb12..4db382ae 100644 --- a/src/danog/MadelineProto/Connection.php +++ b/src/danog/MadelineProto/Connection.php @@ -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 */ diff --git a/src/danog/MadelineProto/DataCenter.php b/src/danog/MadelineProto/DataCenter.php index 4ad11a19..91a39629 100644 --- a/src/danog/MadelineProto/DataCenter.php +++ b/src/danog/MadelineProto/DataCenter.php @@ -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. * diff --git a/src/danog/MadelineProto/DataCenterConnection.php b/src/danog/MadelineProto/DataCenterConnection.php index c5b6a48f..ed98f08e 100644 --- a/src/danog/MadelineProto/DataCenterConnection.php +++ b/src/danog/MadelineProto/DataCenterConnection.php @@ -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']; } } diff --git a/src/danog/MadelineProto/Loop/Connection/ReadLoop.php b/src/danog/MadelineProto/Loop/Connection/ReadLoop.php index 8a7d44e5..326a41a4 100644 --- a/src/danog/MadelineProto/Loop/Connection/ReadLoop.php +++ b/src/danog/MadelineProto/Loop/Connection/ReadLoop.php @@ -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']); diff --git a/src/danog/MadelineProto/MTProto.php b/src/danog/MadelineProto/MTProto.php index 95d628f2..01cf811a 100644 --- a/src/danog/MadelineProto/MTProto.php +++ b/src/danog/MadelineProto/MTProto.php @@ -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 { diff --git a/src/danog/MadelineProto/MTProto/AuthKey.php b/src/danog/MadelineProto/MTProto/AuthKey.php index d226dde3..1d16adae 100644 --- a/src/danog/MadelineProto/MTProto/AuthKey.php +++ b/src/danog/MadelineProto/MTProto/AuthKey.php @@ -1,6 +1,6 @@ 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 - ]; - } -} \ No newline at end of file + abstract public function authorized(bool $authorized); +} diff --git a/src/danog/MadelineProto/MTProto/PermAuthKey.php b/src/danog/MadelineProto/MTProto/PermAuthKey.php new file mode 100644 index 00000000..bda8c2c8 --- /dev/null +++ b/src/danog/MadelineProto/MTProto/PermAuthKey.php @@ -0,0 +1,81 @@ +. + * + * @author Daniil Gentili + * @copyright 2016-2019 Daniil Gentili + * @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 + ]; + } +} diff --git a/src/danog/MadelineProto/MTProto/TempAuthKey.php b/src/danog/MadelineProto/MTProto/TempAuthKey.php new file mode 100644 index 00000000..afde14cd --- /dev/null +++ b/src/danog/MadelineProto/MTProto/TempAuthKey.php @@ -0,0 +1,173 @@ +. + * + * @author Daniil Gentili + * @copyright 2016-2019 Daniil Gentili + * @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 + ]; + } +} diff --git a/src/danog/MadelineProto/MTProtoSession/CallHandler.php b/src/danog/MadelineProto/MTProtoSession/CallHandler.php index d7e5979c..02902766 100644 --- a/src/danog/MadelineProto/MTProtoSession/CallHandler.php +++ b/src/danog/MadelineProto/MTProtoSession/CallHandler.php @@ -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']; } diff --git a/src/danog/MadelineProto/MTProtoSession/ResponseHandler.php b/src/danog/MadelineProto/MTProtoSession/ResponseHandler.php index 4bb2ff16..95b9136f 100644 --- a/src/danog/MadelineProto/MTProtoSession/ResponseHandler.php +++ b/src/danog/MadelineProto/MTProtoSession/ResponseHandler.php @@ -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'])) { diff --git a/src/danog/MadelineProto/MTProtoSession/Session.php b/src/danog/MadelineProto/MTProtoSession/Session.php index 2a6a6b74..19915a64 100644 --- a/src/danog/MadelineProto/MTProtoSession/Session.php +++ b/src/danog/MadelineProto/MTProtoSession/Session.php @@ -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 */ diff --git a/src/danog/MadelineProto/MTProtoTools/AuthKeyHandler.php b/src/danog/MadelineProto/MTProtoTools/AuthKeyHandler.php index 9a52cc5c..ad3e0a71 100644 --- a/src/danog/MadelineProto/MTProtoTools/AuthKeyHandler.php +++ b/src/danog/MadelineProto/MTProtoTools/AuthKeyHandler.php @@ -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 + */ 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 + */ + 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 + */ + 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 + */ + 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 + */ + 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); diff --git a/src/danog/MadelineProto/MTProtoTools/Files.php b/src/danog/MadelineProto/MTProtoTools/Files.php index 76ae3ded..96aca496 100644 --- a/src/danog/MadelineProto/MTProtoTools/Files.php +++ b/src/danog/MadelineProto/MTProtoTools/Files.php @@ -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']; } diff --git a/src/danog/MadelineProto/Stream/ConnectionContext.php b/src/danog/MadelineProto/Stream/ConnectionContext.php index 1504639c..151c800d 100644 --- a/src/danog/MadelineProto/Stream/ConnectionContext.php +++ b/src/danog/MadelineProto/Stream/ConnectionContext.php @@ -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; } diff --git a/src/danog/MadelineProto/Wrappers/Login.php b/src/danog/MadelineProto/Wrappers/Login.php index 8fd6f58a..8bcf1e64 100644 --- a/src/danog/MadelineProto/Wrappers/Login.php +++ b/src/danog/MadelineProto/Wrappers/Login.php @@ -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 */