From 8ae159dd66599ee8bea55af854bd126e05a72af4 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 26 Jun 2017 20:38:16 +0300 Subject: [PATCH] Add special dc options config request. --- Telegram/Resources/scheme.tl | 3 + .../codegen/scheme/codegen_scheme.py | 19 +- Telegram/SourceFiles/core/utils.h | 27 +- Telegram/SourceFiles/messenger.cpp | 8 +- Telegram/SourceFiles/messenger.h | 4 + .../SourceFiles/mtproto/config_loader.cpp | 176 +++++++++++++ Telegram/SourceFiles/mtproto/config_loader.h | 80 ++++++ Telegram/SourceFiles/mtproto/connection.cpp | 42 ++-- .../mtproto/connection_abstract.cpp | 8 +- .../SourceFiles/mtproto/connection_abstract.h | 2 +- Telegram/SourceFiles/mtproto/dc_options.cpp | 16 +- Telegram/SourceFiles/mtproto/dc_options.h | 1 + Telegram/SourceFiles/mtproto/dcenter.cpp | 60 +---- Telegram/SourceFiles/mtproto/dcenter.h | 38 +-- Telegram/SourceFiles/mtproto/facade.h | 15 ++ Telegram/SourceFiles/mtproto/mtp_instance.cpp | 68 +++-- Telegram/SourceFiles/mtproto/mtp_instance.h | 15 +- .../SourceFiles/mtproto/rsa_public_key.cpp | 28 ++- Telegram/SourceFiles/mtproto/rsa_public_key.h | 3 + Telegram/SourceFiles/mtproto/session.cpp | 26 +- Telegram/SourceFiles/mtproto/session.h | 48 ++-- .../mtproto/special_config_request.cpp | 236 ++++++++++++++++++ .../mtproto/special_config_request.h | 51 ++++ .../SourceFiles/storage/serialize_common.h | 1 + Telegram/gyp/telegram_sources.txt | 4 + 25 files changed, 777 insertions(+), 202 deletions(-) create mode 100644 Telegram/SourceFiles/mtproto/config_loader.cpp create mode 100644 Telegram/SourceFiles/mtproto/config_loader.h create mode 100644 Telegram/SourceFiles/mtproto/special_config_request.cpp create mode 100644 Telegram/SourceFiles/mtproto/special_config_request.h diff --git a/Telegram/Resources/scheme.tl b/Telegram/Resources/scheme.tl index 5ba86e13b..ffff090d9 100644 --- a/Telegram/Resources/scheme.tl +++ b/Telegram/Resources/scheme.tl @@ -106,6 +106,9 @@ new_session_created#9ec20908 first_msg_id:long unique_id:long server_salt:long = http_wait#9299359f max_delay:int wait_after:int max_wait:int = HttpWait; +ipPort ipv4:int port:int = IpPort; +help.configSimple#d997c3c5 date:int expires:int dc_id:int ip_port_list:Vector = help.ConfigSimple; + ---functions--- rpc_drop_answer#58e4a740 req_msg_id:long = RpcDropAnswer; diff --git a/Telegram/SourceFiles/codegen/scheme/codegen_scheme.py b/Telegram/SourceFiles/codegen/scheme/codegen_scheme.py index b559c15f2..2dc15d935 100644 --- a/Telegram/SourceFiles/codegen/scheme/codegen_scheme.py +++ b/Telegram/SourceFiles/codegen/scheme/codegen_scheme.py @@ -106,7 +106,7 @@ with open(input_file) as f: if (re.match(r'^\s*$', line)): continue; - nametype = re.match(r'([a-zA-Z\.0-9_]+)#([0-9a-f]+)([^=]*)=\s*([a-zA-Z\.<>0-9_]+);', line); + nametype = re.match(r'([a-zA-Z\.0-9_]+)(#[0-9a-f]+)?([^=]*)=\s*([a-zA-Z\.<>0-9_]+);', line); if (not nametype): if (not re.match(r'vector#1cb5c415 \{t:Type\} # \[ t \] = Vector t;', line)): print('Bad line found: ' + line); @@ -120,11 +120,10 @@ with open(input_file) as f: else: Name = name[0:1].upper() + name[1:]; typeid = nametype.group(2); - while (len(typeid) > 0 and typeid[0] == '0'): + if (typeid and len(typeid) > 0): + typeid = typeid[1:]; # Skip '#' + while (typeid and len(typeid) > 0 and typeid[0] == '0'): typeid = typeid[1:]; - if (len(typeid) == 0): - typeid = '0'; - typeid = '0x' + typeid; cleanline = nametype.group(1) + nametype.group(3) + '= ' + nametype.group(4); cleanline = re.sub(r' [a-zA-Z0-9_]+\:flags\.[0-9]+\?true', '', cleanline); @@ -139,9 +138,13 @@ with open(input_file) as f: if (countTypeId < 0): countTypeId += 2 ** 32; countTypeId = '0x' + re.sub(r'^0x|L$', '', hex(countTypeId)); - if (typeid != countTypeId): - print('Warning: counted ' + countTypeId + ' mismatch with provided ' + typeid + ' (' + cleanline + ')'); - continue; + if (typeid and len(typeid) > 0): + typeid = '0x' + typeid; + if (typeid != countTypeId): + print('Warning: counted ' + countTypeId + ' mismatch with provided ' + typeid + ' (' + cleanline + ')'); + continue; + else: + typeid = countTypeId; params = nametype.group(3); restype = nametype.group(4); diff --git a/Telegram/SourceFiles/core/utils.h b/Telegram/SourceFiles/core/utils.h index ab1e3099b..d3e14b568 100644 --- a/Telegram/SourceFiles/core/utils.h +++ b/Telegram/SourceFiles/core/utils.h @@ -214,6 +214,11 @@ inline void copy_bytes(byte_span destination, const_byte_span source) { memcpy(destination.data(), source.data(), source.size()); } +inline void move_bytes(byte_span destination, const_byte_span source) { + Expects(destination.size() >= source.size()); + memmove(destination.data(), source.data(), source.size()); +} + inline void set_bytes(byte_span destination, gsl::byte value) { memset(destination.data(), gsl::to_integer(value), destination.size()); } @@ -432,23 +437,29 @@ inline void memsetrnd_bad(T &value) { class ReadLockerAttempt { public: - - ReadLockerAttempt(QReadWriteLock *_lock) : success(_lock->tryLockForRead()), lock(_lock) { + ReadLockerAttempt(gsl::not_null lock) : _lock(lock), _locked(_lock->tryLockForRead()) { + } + ReadLockerAttempt(const ReadLockerAttempt &other) = delete; + ReadLockerAttempt &operator=(const ReadLockerAttempt &other) = delete; + ReadLockerAttempt(ReadLockerAttempt &&other) : _lock(other._lock), _locked(base::take(other._locked)) { + } + ReadLockerAttempt &operator=(ReadLockerAttempt &&other) { + _lock = other._lock; + _locked = base::take(other._locked); } ~ReadLockerAttempt() { - if (success) { - lock->unlock(); + if (_locked) { + _lock->unlock(); } } operator bool() const { - return success; + return _locked; } private: - - bool success; - QReadWriteLock *lock; + gsl::not_null _lock; + bool _locked = false; }; diff --git a/Telegram/SourceFiles/messenger.cpp b/Telegram/SourceFiles/messenger.cpp index a6a17a978..a270bec03 100644 --- a/Telegram/SourceFiles/messenger.cpp +++ b/Telegram/SourceFiles/messenger.cpp @@ -380,9 +380,11 @@ void Messenger::startLocalStorage() { } }); subscribe(authSessionChanged(), [this] { - if (_mtproto) { - _mtproto->requestConfig(); - } + InvokeQueued(this, [this] { + if (_mtproto) { + _mtproto->requestConfig(); + } + }); }); } diff --git a/Telegram/SourceFiles/messenger.h b/Telegram/SourceFiles/messenger.h index ebe077fee..f89cf105a 100644 --- a/Telegram/SourceFiles/messenger.h +++ b/Telegram/SourceFiles/messenger.h @@ -21,6 +21,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #pragma once #include "base/observer.h" +#include "mtproto/auth_key.h" namespace App { void quit(); @@ -29,6 +30,9 @@ void quit(); namespace MTP { class DcOptions; class Instance; +class AuthKey; +using AuthKeyPtr = std::shared_ptr; +using AuthKeysList = std::vector; } // namespace MTP class AuthSession; diff --git a/Telegram/SourceFiles/mtproto/config_loader.cpp b/Telegram/SourceFiles/mtproto/config_loader.cpp new file mode 100644 index 000000000..cd66d61ae --- /dev/null +++ b/Telegram/SourceFiles/mtproto/config_loader.cpp @@ -0,0 +1,176 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org +*/ +#include "mtproto/config_loader.h" + +#include "mtproto/dc_options.h" +#include "mtproto/mtp_instance.h" +#include "mtproto/special_config_request.h" + +namespace MTP { +namespace internal { +namespace { + +constexpr auto kEnumerateDcTimeout = 8000; // 8 seconds timeout for help_getConfig to work (then move to other dc) +constexpr auto kSpecialRequestTimeoutMs = 6000; // 4 seconds timeout for it to work in a specially requested dc. + +} // namespace + +ConfigLoader::ConfigLoader(gsl::not_null instance, RPCDoneHandlerPtr onDone, RPCFailHandlerPtr onFail) : _instance(instance) + , _doneHandler(onDone) + , _failHandler(onFail) { + _enumDCTimer.setCallback([this] { enumerate(); }); + _specialEnumTimer.setCallback([this] { sendSpecialRequest(); }); +} + +void ConfigLoader::load() { + if (!_instance->isKeysDestroyer()) { + sendRequest(_instance->mainDcId()); + _enumDCTimer.callOnce(kEnumerateDcTimeout); + } else { + auto ids = _instance->dcOptions()->configEnumDcIds(); + t_assert(!ids.empty()); + _enumCurrent = ids.front(); + enumerate(); + } +} + +mtpRequestId ConfigLoader::sendRequest(ShiftedDcId shiftedDcId) { + return _instance->send(MTPhelp_GetConfig(), _doneHandler, _failHandler, shiftedDcId); +} + +DcId ConfigLoader::specialToRealDcId(DcId specialDcId) { + return Instance::Config::kTemporaryMainDc + specialDcId; +} + +void ConfigLoader::terminateRequest() { + if (_enumRequest) { + _instance->cancel(base::take(_enumRequest)); + } + if (_enumCurrent) { + _instance->killSession(MTP::configDcId(_enumCurrent)); + } +} + +void ConfigLoader::terminateSpecialRequest() { + if (_specialEnumRequest) { + _instance->cancel(base::take(_specialEnumRequest)); + } + if (_specialEnumCurrent) { + _instance->killSession(_specialEnumCurrent); + } +} + +ConfigLoader::~ConfigLoader() { + terminateRequest(); + terminateSpecialRequest(); +} + +void ConfigLoader::enumerate() { + terminateRequest(); + if (!_enumCurrent) { + _enumCurrent = _instance->mainDcId(); + } + auto ids = _instance->dcOptions()->configEnumDcIds(); + t_assert(!ids.empty()); + + auto i = std::find(ids.cbegin(), ids.cend(), _enumCurrent); + if (i == ids.cend() || (++i) == ids.cend()) { + _enumCurrent = ids.front(); + } else { + _enumCurrent = *i; + } + _enumRequest = sendRequest(MTP::configDcId(_enumCurrent)); + + _enumDCTimer.callOnce(kEnumerateDcTimeout); + + createSpecialLoader(); +} + +void ConfigLoader::createSpecialLoader() { + if (Global::ConnectionType() != dbictAuto) { + _specialLoader.reset(); + return; + } + if (!_specialLoader || (!_specialEnumRequest && _specialEndpoints.empty())) { + _specialLoader = std::make_unique([this](DcId dcId, const std::string &ip, int port) { + addSpecialEndpoint(dcId, ip, port); + }); + _triedSpecialEndpoints.clear(); + } +} + +void ConfigLoader::addSpecialEndpoint(DcId dcId, const std::string &ip, int port) { + auto endpoint = SpecialEndpoint { dcId, ip, port }; + if (base::contains(_specialEndpoints, endpoint) + || base::contains(_triedSpecialEndpoints, endpoint)) { + return; + } + DEBUG_LOG(("MTP Info: Special endpoint received, '%1:%2'").arg(ip.c_str()).arg(port)); + _specialEndpoints.push_back(endpoint); + + if (!_specialEnumTimer.isActive()) { + _specialEnumTimer.callOnce(1); + } +} + +void ConfigLoader::sendSpecialRequest() { + terminateSpecialRequest(); + if (Global::ConnectionType() != dbictAuto) { + _specialLoader.reset(); + return; + } + if (_specialEndpoints.empty()) { + createSpecialLoader(); + return; + } + + auto weak = base::weak_unique_ptr(this); + auto index = rand_value() % uint32(_specialEndpoints.size()); + auto endpoint = _specialEndpoints.begin() + index; + _specialEnumCurrent = specialToRealDcId(endpoint->dcId); + _instance->dcOptions()->constructAddOne(_specialEnumCurrent, MTPDdcOption::Flag::f_tcpo_only, endpoint->ip, endpoint->port); + _specialEnumRequest = _instance->send(MTPhelp_GetConfig(), rpcDone([weak](const MTPConfig &result) { + if (!weak) { + return; + } + weak->specialConfigLoaded(result); + }), _failHandler, _specialEnumCurrent); + _triedSpecialEndpoints.push_back(*endpoint); + _specialEndpoints.erase(endpoint); + + _specialEnumTimer.callOnce(kSpecialRequestTimeoutMs); +} + +void ConfigLoader::specialConfigLoaded(const MTPConfig &result) { + Expects(result.type() == mtpc_config); + auto &data = result.c_config(); + if (data.vdc_options.v.empty()) { + LOG(("MTP Error: config with empty dc_options received!")); + return; + } + + // We use special config only for dc options. + // For everything else we wait for normal config from main dc. + _instance->dcOptions()->setFromList(data.vdc_options); +} + +} // namespace internal +} // namespace MTP diff --git a/Telegram/SourceFiles/mtproto/config_loader.h b/Telegram/SourceFiles/mtproto/config_loader.h new file mode 100644 index 000000000..6df54a954 --- /dev/null +++ b/Telegram/SourceFiles/mtproto/config_loader.h @@ -0,0 +1,80 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org +*/ +#pragma once + +#include "base/timer.h" +#include "base/weak_unique_ptr.h" +#include "mtproto/rpc_sender.h" + +namespace MTP { + +class SpecialConfigRequest; +class Instance; + +namespace internal { + +class ConfigLoader : public base::enable_weak_from_this { +public: + ConfigLoader(gsl::not_null instance, RPCDoneHandlerPtr onDone, RPCFailHandlerPtr onFail); + ~ConfigLoader(); + + void load(); + +private: + mtpRequestId sendRequest(ShiftedDcId shiftedDcId); + void addSpecialEndpoint(DcId dcId, const std::string &ip, int port); + void sendSpecialRequest(); + void enumerate(); + void createSpecialLoader(); + DcId specialToRealDcId(DcId specialDcId); + void specialConfigLoaded(const MTPConfig &result); + void terminateRequest(); + void terminateSpecialRequest(); + + gsl::not_null _instance; + base::Timer _enumDCTimer; + DcId _enumCurrent = 0; + mtpRequestId _enumRequest = 0; + + struct SpecialEndpoint { + DcId dcId; + std::string ip; + int port; + }; + friend bool operator==(const SpecialEndpoint &a, const SpecialEndpoint &b); + std::unique_ptr _specialLoader; + std::vector _specialEndpoints; + std::vector _triedSpecialEndpoints; + base::Timer _specialEnumTimer; + DcId _specialEnumCurrent = 0; + mtpRequestId _specialEnumRequest = 0; + + RPCDoneHandlerPtr _doneHandler; + RPCFailHandlerPtr _failHandler; + +}; + +inline bool operator==(const ConfigLoader::SpecialEndpoint &a, const ConfigLoader::SpecialEndpoint &b) { + return (a.dcId == b.dcId) && (a.ip == b.ip) && (a.port == b.port); +} + +} // namespace internal +} // namespace MTP diff --git a/Telegram/SourceFiles/mtproto/connection.cpp b/Telegram/SourceFiles/mtproto/connection.cpp index c92e96518..369e3096b 100644 --- a/Telegram/SourceFiles/mtproto/connection.cpp +++ b/Telegram/SourceFiles/mtproto/connection.cpp @@ -20,20 +20,19 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org */ #include "mtproto/connection.h" +#include "mtproto/rsa_public_key.h" +#include "mtproto/rpc_sender.h" +#include "mtproto/dc_options.h" +#include "mtproto/connection_abstract.h" +#include "zlib.h" +#include "lang/lang_keys.h" +#include "base/openssl_help.h" #include #include #include #include #include #include -#include "zlib.h" -#include "lang/lang_keys.h" -#include "base/openssl_help.h" - -#include "mtproto/rsa_public_key.h" -#include "messenger.h" -#include "mtproto/dc_options.h" -#include "mtproto/connection_abstract.h" namespace MTP { namespace internal { @@ -307,13 +306,13 @@ void ConnectionPrivate::createConn(bool createIPv4, bool createIPv6) { destroyConn(); if (createIPv4) { QWriteLocker lock(&stateConnMutex); - _conn4 = AbstractConnection::create(thread()); + _conn4 = AbstractConnection::create(_dcType, thread()); connect(_conn4, SIGNAL(error(qint32)), this, SLOT(onError4(qint32))); connect(_conn4, SIGNAL(receivedSome()), this, SLOT(onReceivedSome())); } if (createIPv6) { QWriteLocker lock(&stateConnMutex); - _conn6 = AbstractConnection::create(thread()); + _conn6 = AbstractConnection::create(_dcType, thread()); connect(_conn6, SIGNAL(error(qint32)), this, SLOT(onError6(qint32))); connect(_conn6, SIGNAL(receivedSome()), this, SLOT(onReceivedSome())); } @@ -997,14 +996,14 @@ void ConnectionPrivate::connectToServer(bool afterConfig) { return; } auto bareDc = bareDcId(_shiftedDcId); - _dcType = Messenger::Instance().dcOptions()->dcType(_shiftedDcId); + _dcType = _instance->dcOptions()->dcType(_shiftedDcId); if (_dcType == DcType::MediaDownload) { // using media_only addresses only if key for this dc is already created QReadLocker lockFinished(&sessionDataMutex); if (!sessionData || !sessionData->getKey()) { _dcType = DcType::Regular; } } else if (_dcType == DcType::Cdn && !_instance->isKeysDestroyer()) { - if (!Messenger::Instance().dcOptions()->hasCDNKeysForDc(bareDc)) { + if (!_instance->dcOptions()->hasCDNKeysForDc(bareDc)) { requestCDNConfig(); return; } @@ -1015,9 +1014,9 @@ void ConnectionPrivate::connectToServer(bool afterConfig) { auto kIPv6 = Variants::IPv6; auto kTcp = Variants::Tcp; auto kHttp = Variants::Http; - auto variants = Messenger::Instance().dcOptions()->lookup(bareDc, _dcType); - auto noIPv4 = (variants.data[kIPv4][kHttp].port == 0); - auto noIPv6 = (!Global::TryIPv6() || (variants.data[kIPv6][kHttp].port == 0)); + auto variants = _instance->dcOptions()->lookup(bareDc, _dcType); + auto noIPv4 = (_dcType == DcType::Temporary) ? (variants.data[kIPv4][kTcp].port == 0) : (variants.data[kIPv4][kHttp].port == 0); + auto noIPv6 = (_dcType == DcType::Temporary) ? true : (!Global::TryIPv6() || (variants.data[kIPv6][kHttp].port == 0)); if (noIPv4 && noIPv6) { if (_instance->isKeysDestroyer()) { LOG(("MTP Error: DC %1 options for IPv4 over HTTP not found for auth key destruction!").arg(_shiftedDcId)); @@ -2337,7 +2336,7 @@ void ConnectionPrivate::updateAuthKey() { DEBUG_LOG(("AuthKey Info: No key in updateAuthKey(), will be creating auth_key")); lockKey(); - const AuthKeyPtr &key(sessionData->getKey()); + auto &key = sessionData->getKey(); if (key) { if (keyId != key->keyId()) clearMessages(); keyId = key->keyId(); @@ -2388,7 +2387,7 @@ void ConnectionPrivate::pqAnswered() { } auto rsaKey = internal::RSAPublicKey(); - if (!Messenger::Instance().dcOptions()->getDcRSAKey(bareDcId(_shiftedDcId), res_pq.c_resPQ().vserver_public_key_fingerprints.v, &rsaKey)) { + if (!_instance->dcOptions()->getDcRSAKey(bareDcId(_shiftedDcId), res_pq.c_resPQ().vserver_public_key_fingerprints.v, &rsaKey)) { if (_dcType == DcType::Cdn) { LOG(("Warning: CDN public RSA key not found")); requestCDNConfig(); @@ -2926,10 +2925,10 @@ bool ConnectionPrivate::sendRequest(mtpRequest &request, bool needAnyResponse, Q uint32 fullSize = request->size(); if (fullSize < 9) return false; - uint32 messageSize = mtpRequestData::messageSize(request); + auto messageSize = mtpRequestData::messageSize(request); if (messageSize < 5 || fullSize < messageSize + 4) return false; - ReadLockerAttempt lock(sessionData->keyMutex()); + auto lock = ReadLockerAttempt(sessionData->keyMutex()); if (!lock) { DEBUG_LOG(("MTP Info: could not lock key for read in sendBuffer(), dc %1, restarting...").arg(_shiftedDcId)); @@ -2947,12 +2946,13 @@ bool ConnectionPrivate::sendRequest(mtpRequest &request, bool needAnyResponse, Q return false; } - uint64 session(sessionData->getSession()), salt(sessionData->getSalt()); + auto session = sessionData->getSession(); + auto salt = sessionData->getSalt(); memcpy(request->data() + 0, &salt, 2 * sizeof(mtpPrime)); memcpy(request->data() + 2, &session, 2 * sizeof(mtpPrime)); - const mtpPrime *from = request->constData() + 4; + auto from = request->constData() + 4; MTP_LOG(_shiftedDcId, ("Send: ") + mtpTextSerialize(from, from + messageSize)); #ifdef TDESKTOP_MTPROTO_OLD diff --git a/Telegram/SourceFiles/mtproto/connection_abstract.cpp b/Telegram/SourceFiles/mtproto/connection_abstract.cpp index a75df4551..7b843d7b5 100644 --- a/Telegram/SourceFiles/mtproto/connection_abstract.cpp +++ b/Telegram/SourceFiles/mtproto/connection_abstract.cpp @@ -75,11 +75,11 @@ MTPResPQ AbstractConnection::readPQFakeReply(const mtpBuffer &buffer) { return response; } -AbstractConnection *AbstractConnection::create(QThread *thread) { - if (Global::ConnectionType() == dbictHttpProxy) { - return new HTTPConnection(thread); - } else if (Global::ConnectionType() == dbictTcpProxy) { +AbstractConnection *AbstractConnection::create(DcType type, QThread *thread) { + if ((type == DcType::Temporary) || (Global::ConnectionType() == dbictTcpProxy)) { return new TCPConnection(thread); + } else if (Global::ConnectionType() == dbictHttpProxy) { + return new HTTPConnection(thread); } return new AutoConnection(thread); } diff --git a/Telegram/SourceFiles/mtproto/connection_abstract.h b/Telegram/SourceFiles/mtproto/connection_abstract.h index 53b66779d..b2aeab3df 100644 --- a/Telegram/SourceFiles/mtproto/connection_abstract.h +++ b/Telegram/SourceFiles/mtproto/connection_abstract.h @@ -38,7 +38,7 @@ public: virtual ~AbstractConnection() = 0; // virtual constructor - static AbstractConnection *create(QThread *thread); + static AbstractConnection *create(DcType type, QThread *thread); void setSentEncrypted() { _sentEncrypted = true; diff --git a/Telegram/SourceFiles/mtproto/dc_options.cpp b/Telegram/SourceFiles/mtproto/dc_options.cpp index d56a8ce62..809931f3d 100644 --- a/Telegram/SourceFiles/mtproto/dc_options.cpp +++ b/Telegram/SourceFiles/mtproto/dc_options.cpp @@ -219,6 +219,9 @@ QByteArray DcOptions::serialize() const { auto size = sizeof(qint32); for (auto &item : _data) { + if (isTemporaryDcId(item.first)) { + continue; + } size += sizeof(qint32) + sizeof(qint32) + sizeof(qint32); // id + flags + port size += sizeof(qint32) + item.second.ip.size(); } @@ -254,6 +257,9 @@ QByteArray DcOptions::serialize() const { stream.setVersion(QDataStream::Qt_5_1); stream << qint32(_data.size()); for (auto &item : _data) { + if (isTemporaryDcId(item.first)) { + continue; + } stream << qint32(item.second.id) << qint32(item.second.flags) << qint32(item.second.port); stream << qint32(item.second.ip.size()); stream.writeRawData(item.second.ip.data(), item.second.ip.size()); @@ -332,7 +338,9 @@ DcOptions::Ids DcOptions::configEnumDcIds() const { ReadLocker lock(this); result.reserve(_data.size()); for (auto &item : _data) { - if (!isCdnDc(item.second.flags) && !base::contains(result, item.second.id)) { + if (!isCdnDc(item.second.flags) + && !isTemporaryDcId(item.first) + && !base::contains(result, item.second.id)) { result.push_back(item.second.id); } } @@ -342,6 +350,9 @@ DcOptions::Ids DcOptions::configEnumDcIds() const { } DcType DcOptions::dcType(ShiftedDcId shiftedDcId) const { + if (isTemporaryDcId(shiftedDcId)) { + return DcType::Temporary; + } ReadLocker lock(this); if (_cdnDcIds.find(bareDcId(shiftedDcId)) != _cdnDcIds.cend()) { return DcType::Cdn; @@ -398,7 +409,8 @@ bool DcOptions::getDcRSAKey(DcId dcId, const QVector &fingerprints, int DcOptions::Variants DcOptions::lookup(DcId dcId, DcType type) const { auto lookupDesiredFlags = [type](int address, int protocol) -> std::vector { switch (type) { - case DcType::Regular: { + case DcType::Regular: + case DcType::Temporary: { switch (address) { case Variants::IPv4: { switch (protocol) { diff --git a/Telegram/SourceFiles/mtproto/dc_options.h b/Telegram/SourceFiles/mtproto/dc_options.h index c693f3763..6a2fd55c9 100644 --- a/Telegram/SourceFiles/mtproto/dc_options.h +++ b/Telegram/SourceFiles/mtproto/dc_options.h @@ -30,6 +30,7 @@ namespace MTP { enum class DcType { Regular, + Temporary, MediaDownload, Cdn, }; diff --git a/Telegram/SourceFiles/mtproto/dcenter.cpp b/Telegram/SourceFiles/mtproto/dcenter.cpp index 6fcb17c67..b4750f6df 100644 --- a/Telegram/SourceFiles/mtproto/dcenter.cpp +++ b/Telegram/SourceFiles/mtproto/dcenter.cpp @@ -21,8 +21,10 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "mtproto/dcenter.h" #include "mtproto/facade.h" +#include "mtproto/auth_key.h" #include "mtproto/dc_options.h" #include "mtproto/mtp_instance.h" +#include "mtproto/special_config_request.h" #include "storage/localstorage.h" namespace MTP { @@ -30,10 +32,11 @@ namespace internal { namespace { constexpr auto kEnumerateDcTimeout = 8000; // 8 seconds timeout for help_getConfig to work (then move to other dc) +constexpr auto kSpecialRequestTimeoutMs = 6000; // 4 seconds timeout for it to work in a specially requested dc. } // namespace -Dcenter::Dcenter(Instance *instance, DcId dcId, AuthKeyPtr &&key) +Dcenter::Dcenter(gsl::not_null instance, DcId dcId, AuthKeyPtr &&key) : _instance(instance) , _id(dcId) , _key(std::move(key)) { @@ -67,60 +70,5 @@ void Dcenter::destroyKey() { setKey(AuthKeyPtr()); } -ConfigLoader::ConfigLoader(Instance *instance, RPCDoneHandlerPtr onDone, RPCFailHandlerPtr onFail) : _instance(instance) -, _doneHandler(onDone) -, _failHandler(onFail) { - connect(&_enumDCTimer, SIGNAL(timeout()), this, SLOT(enumDC())); -} - -void ConfigLoader::load() { - if (!_instance->isKeysDestroyer()) { - sendRequest(_instance->mainDcId()); - _enumDCTimer.start(kEnumerateDcTimeout); - } else { - auto ids = _instance->dcOptions()->configEnumDcIds(); - t_assert(!ids.empty()); - _enumCurrent = ids.front(); - enumDC(); - } -} - -mtpRequestId ConfigLoader::sendRequest(ShiftedDcId shiftedDcId) { - return _instance->send(MTPhelp_GetConfig(), _doneHandler, _failHandler, shiftedDcId); -} - -ConfigLoader::~ConfigLoader() { - if (_enumRequest) { - _instance->cancel(_enumRequest); - } - if (_enumCurrent) { - _instance->killSession(MTP::configDcId(_enumCurrent)); - } -} - -void ConfigLoader::enumDC() { - if (_enumRequest) { - _instance->cancel(_enumRequest); - } - - if (!_enumCurrent) { - _enumCurrent = _instance->mainDcId(); - } else { - _instance->killSession(MTP::configDcId(_enumCurrent)); - } - auto ids = _instance->dcOptions()->configEnumDcIds(); - t_assert(!ids.empty()); - - auto i = std::find(ids.cbegin(), ids.cend(), _enumCurrent); - if (i == ids.cend() || (++i) == ids.cend()) { - _enumCurrent = ids.front(); - } else { - _enumCurrent = *i; - } - _enumRequest = sendRequest(MTP::configDcId(_enumCurrent)); - - _enumDCTimer.start(kEnumerateDcTimeout); -} - } // namespace internal } // namespace MTP diff --git a/Telegram/SourceFiles/mtproto/dcenter.h b/Telegram/SourceFiles/mtproto/dcenter.h index 9be724fa4..59a2aa87e 100644 --- a/Telegram/SourceFiles/mtproto/dcenter.h +++ b/Telegram/SourceFiles/mtproto/dcenter.h @@ -20,13 +20,11 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org */ #pragma once -#include "core/single_timer.h" -#include "mtproto/rpc_sender.h" -#include "mtproto/auth_key.h" - namespace MTP { class Instance; +class AuthKey; +using AuthKeyPtr = std::shared_ptr; namespace internal { @@ -34,7 +32,7 @@ class Dcenter : public QObject { Q_OBJECT public: - Dcenter(Instance *instance, DcId dcId, AuthKeyPtr &&key); + Dcenter(gsl::not_null instance, DcId dcId, AuthKeyPtr &&key); QReadWriteLock *keyMutex() const; const AuthKeyPtr &getKey() const; @@ -61,40 +59,12 @@ private slots: private: mutable QReadWriteLock keyLock; mutable QMutex initLock; - Instance *_instance = nullptr; + gsl::not_null _instance; DcId _id = 0; AuthKeyPtr _key; bool _connectionInited = false; }; -using DcenterPtr = std::shared_ptr; -using DcenterMap = std::map; - -class ConfigLoader : public QObject { - Q_OBJECT - -public: - ConfigLoader(Instance *instance, RPCDoneHandlerPtr onDone, RPCFailHandlerPtr onFail); - ~ConfigLoader(); - - void load(); - -public slots: - void enumDC(); - -private: - mtpRequestId sendRequest(ShiftedDcId shiftedDcId); - - Instance *_instance = nullptr; - SingleTimer _enumDCTimer; - DcId _enumCurrent = 0; - mtpRequestId _enumRequest = 0; - - RPCDoneHandlerPtr _doneHandler; - RPCFailHandlerPtr _failHandler; - -}; - } // namespace internal } // namespace MTP diff --git a/Telegram/SourceFiles/mtproto/facade.h b/Telegram/SourceFiles/mtproto/facade.h index 3331b0d74..9e0c24c7b 100644 --- a/Telegram/SourceFiles/mtproto/facade.h +++ b/Telegram/SourceFiles/mtproto/facade.h @@ -112,6 +112,21 @@ inline bool isCdnDc(MTPDdcOption::Flags flags) { return (flags & MTPDdcOption::Flag::f_cdn); } +inline bool isTemporaryDcId(ShiftedDcId shiftedDcId) { + auto dcId = bareDcId(shiftedDcId); + return (dcId >= Instance::Config::kTemporaryMainDc); +} + +inline DcId getRealIdFromTemporaryDcId(ShiftedDcId shiftedDcId) { + auto dcId = bareDcId(shiftedDcId); + return (dcId >= Instance::Config::kTemporaryMainDc) ? (dcId - Instance::Config::kTemporaryMainDc) : 0; +} + +inline DcId getTemporaryIdFromRealDcId(ShiftedDcId shiftedDcId) { + auto dcId = bareDcId(shiftedDcId); + return (dcId < Instance::Config::kTemporaryMainDc) ? (dcId + Instance::Config::kTemporaryMainDc) : 0; +} + namespace internal { constexpr ShiftedDcId uploadDcId(DcId dcId, int index) { diff --git a/Telegram/SourceFiles/mtproto/mtp_instance.cpp b/Telegram/SourceFiles/mtproto/mtp_instance.cpp index ab8cd146e..894e7ba19 100644 --- a/Telegram/SourceFiles/mtproto/mtp_instance.cpp +++ b/Telegram/SourceFiles/mtproto/mtp_instance.cpp @@ -21,13 +21,15 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "mtproto/mtp_instance.h" #include "mtproto/dc_options.h" +#include "mtproto/dcenter.h" +#include "mtproto/config_loader.h" +#include "mtproto/connection.h" +#include "mtproto/sender.h" +#include "mtproto/rsa_public_key.h" #include "storage/localstorage.h" #include "auth_session.h" #include "apiwrap.h" #include "messenger.h" -#include "mtproto/connection.h" -#include "mtproto/sender.h" -#include "mtproto/rsa_public_key.h" #include "lang/lang_instance.h" #include "lang/lang_cloud_manager.h" #include "base/timer.h" @@ -36,7 +38,7 @@ namespace MTP { class Instance::Private : private Sender { public: - Private(Instance *instance, DcOptions *options, Instance::Mode mode); + Private(gsl::not_null instance, gsl::not_null options, Instance::Mode mode); void start(Config &&config); @@ -48,7 +50,7 @@ public: AuthKeysList getKeysForWrite() const; void addKeysForDestroy(AuthKeysList &&keys); - DcOptions *dcOptions(); + gsl::not_null dcOptions(); void requestConfig(); void requestCDNConfig(); @@ -66,7 +68,7 @@ public: void reInitConnection(DcId dcId); void logout(RPCDoneHandlerPtr onDone, RPCFailHandlerPtr onFail); - internal::DcenterPtr getDcById(ShiftedDcId shiftedDcId); + std::shared_ptr getDcById(ShiftedDcId shiftedDcId); void unpaused(); void queueQuittingConnection(std::unique_ptr connection); @@ -100,9 +102,15 @@ public: internal::Session *getSession(ShiftedDcId shiftedDcId); + bool isNormal() const { + return (_mode == Instance::Mode::Normal); + } bool isKeysDestroyer() const { return (_mode == Instance::Mode::KeysDestroyer); } + bool isSpecialConfigRequester() const { + return (_mode == Instance::Mode::SpecialConfigRequester); + } void scheduleKeyDestroy(ShiftedDcId shiftedDcId); void performKeyDestroy(ShiftedDcId shiftedDcId); @@ -129,13 +137,13 @@ private: void checkDelayedRequests(); - Instance *_instance = nullptr; - DcOptions *_dcOptions = nullptr; + gsl::not_null _instance; + gsl::not_null _dcOptions; Instance::Mode _mode = Instance::Mode::Normal; DcId _mainDcId = Config::kDefaultMainDc; bool _mainDcIdForced = false; - internal::DcenterMap _dcenters; + std::map> _dcenters; internal::Session *_mainSession = nullptr; std::map> _sessions; @@ -186,7 +194,8 @@ private: }; -Instance::Private::Private(Instance *instance, DcOptions *options, Instance::Mode mode) : Sender(), _instance(instance) +Instance::Private::Private(gsl::not_null instance, gsl::not_null options, Instance::Mode mode) : Sender() +, _instance(instance) , _dcOptions(options) , _mode(mode) { } @@ -194,7 +203,7 @@ Instance::Private::Private(Instance *instance, DcOptions *options, Instance::Mod void Instance::Private::start(Config &&config) { if (isKeysDestroyer()) { _instance->connect(_instance, SIGNAL(keyDestroyed(qint32)), _instance, SLOT(onKeyDestroyed(qint32)), Qt::QueuedConnection); - } else { + } else if (isNormal()) { unixtimeInit(); } @@ -478,20 +487,29 @@ bool Instance::Private::logoutGuestDone(mtpRequestId requestId) { return false; } -internal::DcenterPtr Instance::Private::getDcById(ShiftedDcId shiftedDcId) { +std::shared_ptr Instance::Private::getDcById(ShiftedDcId shiftedDcId) { auto it = _dcenters.find(shiftedDcId); if (it == _dcenters.cend()) { auto dcId = bareDcId(shiftedDcId); + if (isTemporaryDcId(dcId)) { + if (auto realDcId = getRealIdFromTemporaryDcId(dcId)) { + dcId = realDcId; + } + } it = _dcenters.find(dcId); if (it == _dcenters.cend()) { auto result = std::make_shared(_instance, dcId, AuthKeyPtr()); - it = _dcenters.emplace(dcId, std::move(result)).first; + return _dcenters.emplace(dcId, std::move(result)).first->second; } } return it->second; } void Instance::Private::setKeyForWrite(DcId dcId, const AuthKeyPtr &key) { + if (isTemporaryDcId(dcId)) { + return; + } + QWriteLocker lock(&_keysForWriteLock); if (key) { _keysForWrite[dcId] = key; @@ -512,7 +530,7 @@ AuthKeysList Instance::Private::getKeysForWrite() const { } void Instance::Private::addKeysForDestroy(AuthKeysList &&keys) { - t_assert(isKeysDestroyer()); + Expects(isKeysDestroyer()); for (auto &key : keys) { auto dcId = key->dcId(); @@ -538,7 +556,7 @@ void Instance::Private::addKeysForDestroy(AuthKeysList &&keys) { } } -DcOptions *Instance::Private::dcOptions() { +gsl::not_null Instance::Private::dcOptions() { return _dcOptions; } @@ -560,16 +578,12 @@ void Instance::Private::connectionFinished(internal::Connection *connection) { } void Instance::Private::configLoadDone(const MTPConfig &result) { + Expects(result.type() == mtpc_config); + _configLoader.reset(); - if (result.type() != mtpc_config) { - LOG(("MTP Error: wrong config constructor: %1").arg(result.type())); - return; - } auto &data = result.c_config(); - DEBUG_LOG(("MTP Info: got config, chat_size_max: %1, date: %2, test_mode: %3, this_dc: %4, dc_options.length: %5").arg(data.vchat_size_max.v).arg(data.vdate.v).arg(mtpIsTrue(data.vtest_mode)).arg(data.vthis_dc.v).arg(data.vdc_options.v.size())); - if (data.vdc_options.v.empty()) { LOG(("MTP Error: config with empty dc_options received!")); } else { @@ -1175,7 +1189,7 @@ internal::Session *Instance::Private::getSession(ShiftedDcId shiftedDcId) { } void Instance::Private::scheduleKeyDestroy(ShiftedDcId shiftedDcId) { - t_assert(isKeysDestroyer()); + Expects(isKeysDestroyer()); _instance->send(MTPauth_LogOut(), rpcDone([this, shiftedDcId](const MTPBool &result) { performKeyDestroy(shiftedDcId); @@ -1187,7 +1201,7 @@ void Instance::Private::scheduleKeyDestroy(ShiftedDcId shiftedDcId) { } void Instance::Private::performKeyDestroy(ShiftedDcId shiftedDcId) { - t_assert(isKeysDestroyer()); + Expects(isKeysDestroyer()); _instance->send(MTPDestroy_auth_key(), rpcDone([this, shiftedDcId](const MTPDestroyAuthKeyRes &result) { switch (result.type()) { @@ -1207,7 +1221,7 @@ void Instance::Private::performKeyDestroy(ShiftedDcId shiftedDcId) { } void Instance::Private::completedKeyDestroy(ShiftedDcId shiftedDcId) { - t_assert(isKeysDestroyer()); + Expects(isKeysDestroyer()); _dcenters.erase(shiftedDcId); { @@ -1257,7 +1271,7 @@ void Instance::Private::prepareToDestroy() { MustNotCreateSessions = true; } -Instance::Instance(DcOptions *options, Mode mode, Config &&config) : QObject() +Instance::Instance(gsl::not_null options, Mode mode, Config &&config) : QObject() , _private(std::make_unique(this, options, mode)) { _private->start(std::move(config)); } @@ -1338,7 +1352,7 @@ void Instance::logout(RPCDoneHandlerPtr onDone, RPCFailHandlerPtr onFail) { _private->logout(onDone, onFail); } -internal::DcenterPtr Instance::getDcById(ShiftedDcId shiftedDcId) { +std::shared_ptr Instance::getDcById(ShiftedDcId shiftedDcId) { return _private->getDcById(shiftedDcId); } @@ -1354,7 +1368,7 @@ void Instance::addKeysForDestroy(AuthKeysList &&keys) { _private->addKeysForDestroy(std::move(keys)); } -DcOptions *Instance::dcOptions() { +gsl::not_null Instance::dcOptions() { return _private->dcOptions(); } diff --git a/Telegram/SourceFiles/mtproto/mtp_instance.h b/Telegram/SourceFiles/mtproto/mtp_instance.h index 491878369..ef76313fc 100644 --- a/Telegram/SourceFiles/mtproto/mtp_instance.h +++ b/Telegram/SourceFiles/mtproto/mtp_instance.h @@ -20,14 +20,19 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org */ #pragma once -#include "mtproto/dcenter.h" #include #include namespace MTP { +namespace internal { +class Dcenter; +} // namespace internal class DcOptions; class Session; +class AuthKey; +using AuthKeyPtr = std::shared_ptr; +using AuthKeysList = std::vector; class Instance : public QObject { Q_OBJECT @@ -37,15 +42,17 @@ public: static constexpr auto kNoneMainDc = -1; static constexpr auto kNotSetMainDc = 0; static constexpr auto kDefaultMainDc = 2; + static constexpr auto kTemporaryMainDc = 1000; DcId mainDcId = kNotSetMainDc; AuthKeysList keys; }; enum class Mode { Normal, + SpecialConfigRequester, KeysDestroyer, }; - Instance(DcOptions *options, Mode mode, Config &&config); + Instance(gsl::not_null options, Mode mode, Config &&config); Instance(const Instance &other) = delete; Instance &operator=(const Instance &other) = delete; @@ -60,7 +67,7 @@ public: AuthKeysList getKeysForWrite() const; void addKeysForDestroy(AuthKeysList &&keys); - DcOptions *dcOptions(); + gsl::not_null dcOptions(); template mtpRequestId send(const TRequest &request, RPCResponseHandler callbacks = RPCResponseHandler(), ShiftedDcId dcId = 0, TimeMs msCanWait = 0, mtpRequestId after = 0) { @@ -93,7 +100,7 @@ public: void reInitConnection(DcId dcId); void logout(RPCDoneHandlerPtr onDone, RPCFailHandlerPtr onFail); - internal::DcenterPtr getDcById(ShiftedDcId shiftedDcId); + std::shared_ptr getDcById(ShiftedDcId shiftedDcId); void unpaused(); void queueQuittingConnection(std::unique_ptr connection); diff --git a/Telegram/SourceFiles/mtproto/rsa_public_key.cpp b/Telegram/SourceFiles/mtproto/rsa_public_key.cpp index a512d7ad2..4a088cf7a 100644 --- a/Telegram/SourceFiles/mtproto/rsa_public_key.cpp +++ b/Telegram/SourceFiles/mtproto/rsa_public_key.cpp @@ -69,10 +69,31 @@ public: constexpr auto kEncryptSize = 256; auto result = base::byte_vector(kEncryptSize, gsl::byte {}); auto res = RSA_public_encrypt(kEncryptSize, reinterpret_cast(data.data()), reinterpret_cast(result.data()), _rsa, RSA_NO_PADDING); - if (res != kEncryptSize) { + if (res < 0 || res > kEncryptSize) { ERR_load_crypto_strings(); LOG(("RSA Error: RSA_public_encrypt failed, key fp: %1, result: %2, error: %3").arg(getFingerPrint()).arg(res).arg(ERR_error_string(ERR_get_error(), 0))); return base::byte_vector(); + } else if (auto zeroBytes = kEncryptSize - res) { + auto resultBytes = gsl::make_span(result); + base::move_bytes(resultBytes.subspan(zeroBytes, res), resultBytes.subspan(0, res)); + base::set_bytes(resultBytes.subspan(0, zeroBytes), gsl::byte {}); + } + return result; + } + base::byte_vector decrypt(base::const_byte_span data) const { + Expects(isValid()); + + constexpr auto kDecryptSize = 256; + auto result = base::byte_vector(kDecryptSize, gsl::byte {}); + auto res = RSA_public_decrypt(kDecryptSize, reinterpret_cast(data.data()), reinterpret_cast(result.data()), _rsa, RSA_NO_PADDING); + if (res < 0 || res > kDecryptSize) { + ERR_load_crypto_strings(); + LOG(("RSA Error: RSA_public_encrypt failed, key fp: %1, result: %2, error: %3").arg(getFingerPrint()).arg(res).arg(ERR_error_string(ERR_get_error(), 0))); + return base::byte_vector(); + } else if (auto zeroBytes = kDecryptSize - res) { + auto resultBytes = gsl::make_span(result); + base::move_bytes(resultBytes.subspan(zeroBytes - res, res), resultBytes.subspan(0, res)); + base::set_bytes(resultBytes.subspan(0, zeroBytes - res), gsl::byte {}); } return result; } @@ -133,5 +154,10 @@ base::byte_vector RSAPublicKey::encrypt(base::const_byte_span data) const { return _private->encrypt(data); } +base::byte_vector RSAPublicKey::decrypt(base::const_byte_span data) const { + Expects(isValid()); + return _private->decrypt(data); +} + } // namespace internal } // namespace MTP diff --git a/Telegram/SourceFiles/mtproto/rsa_public_key.h b/Telegram/SourceFiles/mtproto/rsa_public_key.h index 7b66e9df7..16beb8b2d 100644 --- a/Telegram/SourceFiles/mtproto/rsa_public_key.h +++ b/Telegram/SourceFiles/mtproto/rsa_public_key.h @@ -43,6 +43,9 @@ public: // data has exactly 256 chars to be encrypted base::byte_vector encrypt(base::const_byte_span data) const; + // data has exactly 256 chars to be decrypted + base::byte_vector decrypt(base::const_byte_span data) const; + private: class Private; std::shared_ptr _private; diff --git a/Telegram/SourceFiles/mtproto/session.cpp b/Telegram/SourceFiles/mtproto/session.cpp index 4ca848767..f93a5bac5 100644 --- a/Telegram/SourceFiles/mtproto/session.cpp +++ b/Telegram/SourceFiles/mtproto/session.cpp @@ -21,10 +21,27 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "mtproto/session.h" #include "mtproto/connection.h" +#include "mtproto/dcenter.h" +#include "mtproto/auth_key.h" namespace MTP { namespace internal { +void SessionData::setKey(const AuthKeyPtr &key) { + if (_authKey != key) { + uint64 session = rand_value(); + _authKey = key; + + DEBUG_LOG(("MTP Info: new auth key set in SessionData, id %1, setting random server_session %2").arg(key ? key->keyId() : 0).arg(session)); + QWriteLocker locker(&_lock); + if (_session != session) { + _session = session; + _messagesSent = 0; + } + _layerInited = false; + } +} + void SessionData::clear(Instance *instance) { RPCCallbackClears clearCallbacks; { @@ -97,10 +114,11 @@ void Session::createDcData() { } dc = _instance->getDcById(dcWithShift); - ReadLockerAttempt lock(keyMutex()); - data.setKey(lock ? dc->getKey() : AuthKeyPtr()); - if (lock && dc->connectionInited()) { - data.setLayerWasInited(true); + if (auto lock = ReadLockerAttempt(keyMutex())) { + data.setKey(dc->getKey()); + if (dc->connectionInited()) { + data.setLayerWasInited(true); + } } connect(dc.get(), SIGNAL(authKeyCreated()), this, SLOT(authKeyCreatedForDC()), Qt::QueuedConnection); connect(dc.get(), SIGNAL(layerWasInited(bool)), this, SLOT(layerWasInitedForDC(bool)), Qt::QueuedConnection); diff --git a/Telegram/SourceFiles/mtproto/session.h b/Telegram/SourceFiles/mtproto/session.h index 92d027cee..2ff8c675f 100644 --- a/Telegram/SourceFiles/mtproto/session.h +++ b/Telegram/SourceFiles/mtproto/session.h @@ -20,15 +20,18 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org */ #pragma once -#include "mtproto/dcenter.h" #include "core/single_timer.h" +#include "mtproto/rpc_sender.h" namespace MTP { class Instance; +class AuthKey; +using AuthKeyPtr = std::shared_ptr; namespace internal { +class Dcenter; class Connection; class ReceivedMsgIds { @@ -98,7 +101,7 @@ inline bool ResponseNeedsAck(const SerializedMessage &response) { class Session; class SessionData { public: - SessionData(Session *creator) : _owner(creator) { + SessionData(gsl::not_null creator) : _owner(creator) { } void setSession(uint64 session) { @@ -152,20 +155,7 @@ public: const AuthKeyPtr &getKey() const { return _authKey; } - void setKey(const AuthKeyPtr &key) { - if (_authKey != key) { - uint64 session = rand_value(); - _authKey = key; - - DEBUG_LOG(("MTP Info: new auth key set in SessionData, id %1, setting random server_session %2").arg(key ? key->keyId() : 0).arg(session)); - QWriteLocker locker(&_lock); - if (_session != session) { - _session = session; - _messagesSent = 0; - } - _layerInited = false; - } - } + void setKey(const AuthKeyPtr &key); bool isCheckedKey() const { QReadLocker locker(&_lock); @@ -176,27 +166,27 @@ public: _keyChecked = checked; } - QReadWriteLock *keyMutex() const; + gsl::not_null keyMutex() const; - QReadWriteLock *toSendMutex() const { + gsl::not_null toSendMutex() const { return &_toSendLock; } - QReadWriteLock *haveSentMutex() const { + gsl::not_null haveSentMutex() const { return &_haveSentLock; } - QReadWriteLock *toResendMutex() const { + gsl::not_null toResendMutex() const { return &_toResendLock; } - QReadWriteLock *wereAckedMutex() const { + gsl::not_null wereAckedMutex() const { return &_wereAckedLock; } - QReadWriteLock *receivedIdsMutex() const { + gsl::not_null receivedIdsMutex() const { return &_receivedIdsLock; } - QReadWriteLock *haveReceivedMutex() const { + gsl::not_null haveReceivedMutex() const { return &_haveReceivedLock; } - QReadWriteLock *stateRequestMutex() const { + gsl::not_null stateRequestMutex() const { return &_stateRequestLock; } @@ -249,10 +239,10 @@ public: return _stateRequest; } - Session *owner() { + gsl::not_null owner() { return _owner; } - const Session *owner() const { + gsl::not_null owner() const { return _owner; } @@ -271,7 +261,7 @@ private: uint32 _messagesSent = 0; - Session *_owner = nullptr; + gsl::not_null _owner; AuthKeyPtr _authKey; bool _keyChecked = false; @@ -376,7 +366,7 @@ private: SessionData data; ShiftedDcId dcWithShift = 0; - DcenterPtr dc; + std::shared_ptr dc; TimeMs msSendCall = 0; TimeMs msWait = 0; @@ -388,7 +378,7 @@ private: }; -inline QReadWriteLock *SessionData::keyMutex() const { +inline gsl::not_null SessionData::keyMutex() const { return _owner->keyMutex(); } diff --git a/Telegram/SourceFiles/mtproto/special_config_request.cpp b/Telegram/SourceFiles/mtproto/special_config_request.cpp new file mode 100644 index 000000000..7223614ff --- /dev/null +++ b/Telegram/SourceFiles/mtproto/special_config_request.cpp @@ -0,0 +1,236 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org +*/ +#include "mtproto/special_config_request.h" + +#include "mtproto/rsa_public_key.h" +#include "mtproto/dc_options.h" +#include "mtproto/auth_key.h" +#include "base/openssl_help.h" +#include + +namespace MTP { +namespace { + +constexpr auto kPublicKey = str_const("\ +-----BEGIN RSA PUBLIC KEY-----\n\ +MIIBCgKCAQEAyr+18Rex2ohtVy8sroGPBwXD3DOoKCSpjDqYoXgCqB7ioln4eDCF\n\ +fOBUlfXUEvM/fnKCpF46VkAftlb4VuPDeQSS/ZxZYEGqHaywlroVnXHIjgqoxiAd\n\ +192xRGreuXIaUKmkwlM9JID9WS2jUsTpzQ91L8MEPLJ/4zrBwZua8W5fECwCCh2c\n\ +9G5IzzBm+otMS/YKwmR1olzRCyEkyAEjXWqBI9Ftv5eG8m0VkBzOG655WIYdyV0H\n\ +fDK/NWcvGqa0w/nriMD6mDjKOryamw0OP9QuYgMN0C9xMW9y8SmP4h92OAWodTYg\n\ +Y1hZCxdv6cs5UnW9+PWvS+WIbkh+GaWYxwIDAQAB\n\ +-----END RSA PUBLIC KEY-----\ +"); + +} // namespace + +SpecialConfigRequest::SpecialConfigRequest(base::lambda callback) : _callback(std::move(callback)) { + App::setProxySettings(_manager); + + performAppRequest(); + performDnsRequest(); +} + +void SpecialConfigRequest::performAppRequest() { + auto appUrl = QUrl(); + appUrl.setScheme(qsl("https")); + appUrl.setHost(qsl("google.com")); + if (cTestMode()) { + appUrl.setPath(qsl("/test/")); + } + auto appRequest = QNetworkRequest(appUrl); + appRequest.setRawHeader("Host", "dns-telegram.appspot.com"); + _appReply.reset(_manager.get(appRequest)); + connect(_appReply.get(), &QNetworkReply::finished, this, [this] { appFinished(); }); +} + +void SpecialConfigRequest::performDnsRequest() { + auto dnsUrl = QUrl(); + dnsUrl.setScheme(qsl("https")); + dnsUrl.setHost(qsl("google.com")); + dnsUrl.setPath(qsl("/resolve")); + dnsUrl.setQuery(qsl("name=%1.stel.com&type=16").arg(cTestMode() ? qsl("tap") : qsl("ap"))); + auto dnsRequest = QNetworkRequest(QUrl(dnsUrl)); + dnsRequest.setRawHeader("Host", "dns.google.com"); + _dnsReply.reset(_manager.get(dnsRequest)); + connect(_dnsReply.get(), &QNetworkReply::finished, this, [this] { dnsFinished(); }); +} + +void SpecialConfigRequest::appFinished() { + if (!_appReply) { + return; + } + auto result = _appReply->readAll(); + _appReply.release()->deleteLater(); + handleResponse(result); +} + +void SpecialConfigRequest::dnsFinished() { + if (!_dnsReply) { + return; + } + auto result = _dnsReply->readAll(); + _dnsReply.release()->deleteLater(); + + // Read and store to "entries" map all the data bytes from this response: + // { .., "Answer": [ { .., "data": "bytes1", .. }, { .., "data": "bytes2", .. } ], .. } + auto entries = QMap(); + auto error = QJsonParseError { 0, QJsonParseError::NoError }; + auto document = QJsonDocument::fromJson(result, &error); + if (error.error != QJsonParseError::NoError) { + LOG(("Config Error: Faild to parse dns response JSON, error: %1").arg(error.errorString())); + } else if (!document.isObject()) { + LOG(("Config Error: Not an object received in dns response JSON.")); + } else { + auto response = document.object(); + auto answerIt = response.find(qsl("Answer")); + if (answerIt == response.constEnd()) { + LOG(("Config Error: Could not find Answer in dns response JSON.")); + } else if (!answerIt->isArray()) { + LOG(("Config Error: Not an array received in Answer in dns response JSON.")); + } else { + for (auto &elem : answerIt->toArray()) { + if (!elem.isObject()) { + LOG(("Config Error: Not an object found in Answer array in dns response JSON.")); + } else { + auto object = elem.toObject(); + auto dataIt = object.find(qsl("data")); + if (dataIt == object.constEnd()) { + LOG(("Config Error: Could not find data in Answer array entry in dns response JSON.")); + } else if (!dataIt->isString()) { + LOG(("Config Error: Not a string data found in Answer array entry in dns response JSON.")); + } else { + auto data = dataIt->toString(); + entries.insertMulti(INT_MAX - data.size(), data); + } + } + } + } + } + auto text = QStringList(entries.values()).join(QString()); + handleResponse(text.toLatin1()); +} + +bool SpecialConfigRequest::decryptSimpleConfig(const QByteArray &bytes) { + auto cleanBytes = bytes; + auto removeFrom = std::remove_if(cleanBytes.begin(), cleanBytes.end(), [](char ch) { + auto isGoodBase64 = (ch == '+') || (ch == '=') || (ch == '/') + || (ch >= 'a' && ch <= 'z') + || (ch >= 'A' && ch <= 'Z') + || (ch >= '0' && ch <= '9'); + return !isGoodBase64; + }); + if (removeFrom != cleanBytes.end()) { + cleanBytes.remove(removeFrom - cleanBytes.begin(), cleanBytes.end() - removeFrom); + } + + constexpr auto kGoodSizeBase64 = 344; + if (cleanBytes.size() != kGoodSizeBase64) { + LOG(("Config Error: Bad data size %1 required %2").arg(cleanBytes.size()).arg(kGoodSizeBase64)); + return false; + } + constexpr auto kGoodSizeData = 256; + auto decodedBytes = QByteArray::fromBase64(cleanBytes, QByteArray::Base64Encoding); + if (decodedBytes.size() != kGoodSizeData) { + LOG(("Config Error: Bad data size %1 required %2").arg(decodedBytes.size()).arg(kGoodSizeData)); + return false; + } + + auto publicKey = internal::RSAPublicKey(gsl::as_bytes(gsl::make_span(kPublicKey.c_str(), kPublicKey.size()))); + auto decrypted = publicKey.decrypt(gsl::as_bytes(gsl::make_span(decodedBytes))); + auto decryptedBytes = gsl::make_span(decrypted); + + constexpr auto kAesKeySize = CTRState::KeySize; + constexpr auto kAesIvecSize = CTRState::IvecSize; + auto aesEncryptedBytes = decryptedBytes.subspan(kAesKeySize); + base::byte_array aesivec; + base::copy_bytes(aesivec, decryptedBytes.subspan(CTRState::KeySize - CTRState::IvecSize, CTRState::IvecSize)); + AES_KEY aeskey; + AES_set_decrypt_key(reinterpret_cast(decryptedBytes.data()), kAesKeySize * CHAR_BIT, &aeskey); + AES_cbc_encrypt(reinterpret_cast(aesEncryptedBytes.data()), reinterpret_cast(aesEncryptedBytes.data()), aesEncryptedBytes.size(), &aeskey, reinterpret_cast(aesivec.data()), AES_DECRYPT); + + constexpr auto kDigestSize = 16; + auto dataSize = aesEncryptedBytes.size() - kDigestSize; + auto data = aesEncryptedBytes.subspan(0, dataSize); + auto hash = openssl::Sha256(data); + if (base::compare_bytes(gsl::make_span(hash).subspan(0, kDigestSize), aesEncryptedBytes.subspan(dataSize)) != 0) { + LOG(("Config Error: Bad digest.")); + return false; + } + + mtpBuffer buffer; + buffer.resize(data.size() / sizeof(mtpPrime)); + base::copy_bytes(gsl::as_writeable_bytes(gsl::make_span(buffer)), data); + auto from = &*buffer.cbegin(); + auto end = from + buffer.size(); + auto realLength = *from++; + if (realLength <= 0 || realLength > dataSize || (realLength & 0x03)) { + LOG(("Config Error: Bad length %1.").arg(realLength)); + return false; + } + + try { + _simpleConfig.read(from, end); + } catch (...) { + LOG(("Config Error: Could not read configSimple.")); + return false; + } + if ((end - from) * sizeof(mtpPrime) != (dataSize - realLength)) { + LOG(("Config Error: Bad read length %1, should be %2.").arg((end - from) * sizeof(mtpPrime)).arg(dataSize - realLength)); + return false; + } + return true; +} + +void SpecialConfigRequest::handleResponse(const QByteArray &bytes) { + if (!decryptSimpleConfig(bytes)) { + return; + } + t_assert(_simpleConfig.type() == mtpc_help_configSimple); + auto &config = _simpleConfig.c_help_configSimple(); + auto now = unixtime(); + if (now < config.vdate.v || now > config.vexpires.v) { + LOG(("Config Error: Bad date frame for simple config: %1-%2, our time is %3.").arg(config.vdate.v).arg(config.vexpires.v).arg(now)); + return; + } + if (config.vip_port_list.v.empty()) { + LOG(("Config Error: Empty simple config received.")); + return; + } + for (auto &entry : config.vip_port_list.v) { + t_assert(entry.type() == mtpc_ipPort); + auto &ipPort = entry.c_ipPort(); + auto ip = *reinterpret_cast(&ipPort.vipv4.v); + auto ipString = qsl("%1.%2.%3.%4").arg((ip >> 24) & 0xFF).arg((ip >> 16) & 0xFF).arg((ip >> 8) & 0xFF).arg(ip & 0xFF); + _callback(config.vdc_id.v, ipString.toStdString(), ipPort.vport.v); + } +} + +SpecialConfigRequest::~SpecialConfigRequest() { + if (_appReply) { + _appReply->abort(); + } + if (_dnsReply) { + _dnsReply->abort(); + } +} + +} // namespace MTP diff --git a/Telegram/SourceFiles/mtproto/special_config_request.h b/Telegram/SourceFiles/mtproto/special_config_request.h new file mode 100644 index 000000000..47253a48b --- /dev/null +++ b/Telegram/SourceFiles/mtproto/special_config_request.h @@ -0,0 +1,51 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org +*/ +#pragma once + +namespace MTP { + +class SpecialConfigRequest : public QObject { +public: + SpecialConfigRequest(base::lambda callback); + + ~SpecialConfigRequest(); + +private: + void performAppRequest(); + void performDnsRequest(); + void appFinished(); + void dnsFinished(); + void handleResponse(const QByteArray &bytes); + bool decryptSimpleConfig(const QByteArray &bytes); + + base::lambda _callback; + MTPhelp_ConfigSimple _simpleConfig; + + QNetworkAccessManager _manager; + std::unique_ptr _appReply; + std::unique_ptr _dnsReply; + + std::unique_ptr _localOptions; + std::unique_ptr _localInstance; + +}; + +} // namespace MTP diff --git a/Telegram/SourceFiles/storage/serialize_common.h b/Telegram/SourceFiles/storage/serialize_common.h index 26296609c..933fc17c4 100644 --- a/Telegram/SourceFiles/storage/serialize_common.h +++ b/Telegram/SourceFiles/storage/serialize_common.h @@ -21,6 +21,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #pragma once #include "ui/images.h" +#include "mtproto/auth_key.h" namespace Serialize { diff --git a/Telegram/gyp/telegram_sources.txt b/Telegram/gyp/telegram_sources.txt index d4dd9c26b..c7177ecda 100644 --- a/Telegram/gyp/telegram_sources.txt +++ b/Telegram/gyp/telegram_sources.txt @@ -235,6 +235,8 @@ <(src_loc)/media/media_clip_reader.h <(src_loc)/mtproto/auth_key.cpp <(src_loc)/mtproto/auth_key.h +<(src_loc)/mtproto/config_loader.cpp +<(src_loc)/mtproto/config_loader.h <(src_loc)/mtproto/connection.cpp <(src_loc)/mtproto/connection.h <(src_loc)/mtproto/connection_abstract.cpp @@ -262,6 +264,8 @@ <(src_loc)/mtproto/sender.h <(src_loc)/mtproto/session.cpp <(src_loc)/mtproto/session.h +<(src_loc)/mtproto/special_config_request.cpp +<(src_loc)/mtproto/special_config_request.h <(src_loc)/overview/overview_layout.cpp <(src_loc)/overview/overview_layout.h <(src_loc)/platform/linux/linux_desktop_environment.cpp