/* 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/dc_options.h" #include "storage/serialize_common.h" namespace MTP { class DcOptions::WriteLocker { public: WriteLocker(DcOptions *that) : _that(that), _lock(&_that->_useThroughLockers) { } ~WriteLocker() { _that->computeCdnDcIds(); } private: gsl::not_null _that; QWriteLocker _lock; }; class DcOptions::ReadLocker { public: ReadLocker(const DcOptions *that) : _lock(&that->_useThroughLockers) { } private: QReadLocker _lock; }; void DcOptions::readBuiltInPublicKeys() { auto keysCount = 0; auto keys = cPublicRSAKeys(keysCount); for (auto i = 0; i != keysCount; ++i) { auto keyBytes = gsl::as_bytes(gsl::make_span(keys[i], keys[i] + strlen(keys[i]))); auto key = internal::RSAPublicKey(keyBytes); if (key.isValid()) { _publicKeys.emplace(key.getFingerPrint(), std::move(key)); } else { LOG(("MTP Error: could not read this public RSA key:")); LOG((keys[i])); } } } void DcOptions::constructFromBuiltIn() { WriteLocker lock(this); _data.clear(); readBuiltInPublicKeys(); auto bdcs = builtInDcs(); for (auto i = 0, l = builtInDcsCount(); i != l; ++i) { auto flags = MTPDdcOption::Flags(0); auto idWithShift = MTP::shiftDcId(bdcs[i].id, flags); _data.emplace(idWithShift, Option(bdcs[i].id, flags, bdcs[i].ip, bdcs[i].port)); DEBUG_LOG(("MTP Info: adding built in DC %1 connect option: %2:%3").arg(bdcs[i].id).arg(bdcs[i].ip).arg(bdcs[i].port)); } auto bdcsipv6 = builtInDcsIPv6(); for (auto i = 0, l = builtInDcsCountIPv6(); i != l; ++i) { auto flags = MTPDdcOption::Flags(MTPDdcOption::Flag::f_ipv6); auto idWithShift = MTP::shiftDcId(bdcsipv6[i].id, flags); _data.emplace(idWithShift, Option(bdcsipv6[i].id, flags, bdcsipv6[i].ip, bdcsipv6[i].port)); DEBUG_LOG(("MTP Info: adding built in DC %1 IPv6 connect option: %2:%3").arg(bdcsipv6[i].id).arg(bdcsipv6[i].ip).arg(bdcsipv6[i].port)); } } void DcOptions::processFromList(const QVector &options, bool overwrite) { if (options.empty() || _immutable) { return; } auto idsChanged = std::vector(); idsChanged.reserve(options.size()); auto shiftedIdsProcessed = std::vector(); shiftedIdsProcessed.reserve(options.size()); { WriteLocker lock(this); if (overwrite) { idsChanged.reserve(_data.size()); } for (auto &mtpOption : options) { if (mtpOption.type() != mtpc_dcOption) { LOG(("Wrong type in DcOptions: %1").arg(mtpOption.type())); continue; } auto &option = mtpOption.c_dcOption(); auto dcId = option.vid.v; auto flags = option.vflags.v; auto dcIdWithShift = MTP::shiftDcId(dcId, flags); if (base::contains(shiftedIdsProcessed, dcIdWithShift)) { continue; } shiftedIdsProcessed.push_back(dcIdWithShift); auto ip = std::string(option.vip_address.v.constData(), option.vip_address.v.size()); auto port = option.vport.v; if (applyOneGuarded(dcId, flags, ip, port)) { if (!base::contains(idsChanged, dcId)) { idsChanged.push_back(dcId); } } } if (overwrite && shiftedIdsProcessed.size() < _data.size()) { for (auto i = _data.begin(); i != _data.end();) { if (base::contains(shiftedIdsProcessed, i->first)) { ++i; } else { if (!base::contains(idsChanged, i->second.id)) { idsChanged.push_back(i->second.id); } i = _data.erase(i); } } } } if (!idsChanged.empty()) { _changed.notify(std::move(idsChanged)); } } void DcOptions::setFromList(const MTPVector &options) { processFromList(options.v, true); } void DcOptions::addFromList(const MTPVector &options) { processFromList(options.v, false); } void DcOptions::addFromOther(DcOptions &&options) { if (this == &options || _immutable) { return; } auto idsChanged = std::vector(); { ReadLocker lock(&options); if (options._data.empty()) { return; } idsChanged.reserve(options._data.size()); { WriteLocker lock(this); for (auto &item : base::take(options._data)) { auto dcId = item.second.id; auto flags = item.second.flags; auto &ip = item.second.ip; auto port = item.second.port; if (applyOneGuarded(dcId, flags, ip, port)) { if (!base::contains(idsChanged, dcId)) { idsChanged.push_back(dcId); } } } for (auto &keysForDc : options._cdnPublicKeys) { for (auto &entry : keysForDc.second) { _cdnPublicKeys[keysForDc.first].insert(std::move(entry)); } } } } if (!idsChanged.empty()) { _changed.notify(std::move(idsChanged)); } } void DcOptions::constructAddOne(int id, MTPDdcOption::Flags flags, const std::string &ip, int port) { WriteLocker lock(this); applyOneGuarded(bareDcId(id), flags, ip, port); } bool DcOptions::applyOneGuarded(DcId dcId, MTPDdcOption::Flags flags, const std::string &ip, int port) { auto dcIdWithShift = MTP::shiftDcId(dcId, flags); auto i = _data.find(dcIdWithShift); if (i != _data.cend()) { if (i->second.ip == ip && i->second.port == port) { return false; } i->second.ip = ip; i->second.port = port; } else { _data.emplace(dcIdWithShift, Option(dcId, flags, ip, port)); } return true; } QByteArray DcOptions::serialize() const { if (_immutable) { // Don't write the overriden options to our settings. return DcOptions().serialize(); } ReadLocker lock(this); auto size = sizeof(qint32); for (auto &item : _data) { size += sizeof(qint32) + sizeof(qint32) + sizeof(qint32); // id + flags + port size += sizeof(qint32) + item.second.ip.size(); } auto count = 0; for (auto &keysInDc : _cdnPublicKeys) { count += keysInDc.second.size(); } struct SerializedPublicKey { DcId dcId; QByteArray n; QByteArray e; }; std::vector publicKeys; publicKeys.reserve(count); for (auto &keysInDc : _cdnPublicKeys) { for (auto &entry : keysInDc.second) { publicKeys.push_back({ keysInDc.first, entry.second.getN(), entry.second.getE() }); size += sizeof(qint32) + Serialize::bytearraySize(publicKeys.back().n) + Serialize::bytearraySize(publicKeys.back().e); } } auto result = QByteArray(); result.reserve(size); { QBuffer buffer(&result); if (!buffer.open(QIODevice::WriteOnly)) { LOG(("MTP Error: Can't open data for DcOptions::serialize()")); return result; } QDataStream stream(&buffer); stream.setVersion(QDataStream::Qt_5_1); stream << qint32(_data.size()); for (auto &item : _data) { 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()); } stream << qint32(publicKeys.size()); for (auto &key : publicKeys) { stream << qint32(key.dcId) << key.n << key.e; } } return result; } void DcOptions::constructFromSerialized(const QByteArray &serialized) { auto readonly = serialized; QBuffer buffer(&readonly); if (!buffer.open(QIODevice::ReadOnly)) { LOG(("MTP Error: Can't open data for DcOptions::constructFromSerialized()")); return; } QDataStream stream(&buffer); stream.setVersion(QDataStream::Qt_5_1); auto count = qint32(0); stream >> count; if (stream.status() != QDataStream::Ok) { LOG(("MTP Error: Bad data for DcOptions::constructFromSerialized()")); return; } WriteLocker lock(this); _data.clear(); for (auto i = 0; i != count; ++i) { qint32 id = 0, flags = 0, port = 0, ipSize = 0; stream >> id >> flags >> port >> ipSize; std::string ip(ipSize, ' '); stream.readRawData(&ip[0], ipSize); if (stream.status() != QDataStream::Ok) { LOG(("MTP Error: Bad data inside DcOptions::constructFromSerialized()")); return; } applyOneGuarded(DcId(id), MTPDdcOption::Flags(flags), ip, port); } // Read CDN config if (!stream.atEnd()) { auto count = qint32(0); stream >> count; if (stream.status() != QDataStream::Ok) { LOG(("MTP Error: Bad data for CDN config in DcOptions::constructFromSerialized()")); return; } for (auto i = 0; i != count; ++i) { qint32 dcId = 0; QByteArray n, e; stream >> dcId >> n >> e; if (stream.status() != QDataStream::Ok) { LOG(("MTP Error: Bad data for CDN config inside DcOptions::constructFromSerialized()")); return; } auto key = internal::RSAPublicKey(n, e); if (key.isValid()) { _cdnPublicKeys[dcId].emplace(key.getFingerPrint(), std::move(key)); } else { LOG(("MTP Error: Could not read valid CDN public key.")); } } } } DcOptions::Ids DcOptions::configEnumDcIds() const { auto result = Ids(); { ReadLocker lock(this); result.reserve(_data.size()); for (auto &item : _data) { if (!isCdnDc(item.second.flags) && !base::contains(result, item.second.id)) { result.push_back(item.second.id); } } } std::sort(result.begin(), result.end()); return result; } DcType DcOptions::dcType(ShiftedDcId shiftedDcId) const { ReadLocker lock(this); if (_cdnDcIds.find(bareDcId(shiftedDcId)) != _cdnDcIds.cend()) { return DcType::Cdn; } if (isDownloadDcId(shiftedDcId)) { return DcType::MediaDownload; } return DcType::Regular; } void DcOptions::setCDNConfig(const MTPDcdnConfig &config) { WriteLocker lock(this); _cdnPublicKeys.clear(); for_const (auto &publicKey, config.vpublic_keys.v) { Expects(publicKey.type() == mtpc_cdnPublicKey); auto &keyData = publicKey.c_cdnPublicKey(); auto keyBytes = gsl::as_bytes(gsl::make_span(keyData.vpublic_key.v)); auto key = internal::RSAPublicKey(keyBytes); if (key.isValid()) { _cdnPublicKeys[keyData.vdc_id.v].emplace(key.getFingerPrint(), std::move(key)); } else { LOG(("MTP Error: could not read this public RSA key:")); LOG((qs(keyData.vpublic_key))); } } } bool DcOptions::hasCDNKeysForDc(DcId dcId) const { ReadLocker lock(this); return _cdnPublicKeys.find(dcId) != _cdnPublicKeys.cend(); } bool DcOptions::getDcRSAKey(DcId dcId, const QVector &fingerprints, internal::RSAPublicKey *result) const { auto findKey = [&fingerprints, &result](const std::map &keys) { for_const (auto &fingerprint, fingerprints) { auto it = keys.find(static_cast(fingerprint.v)); if (it != keys.cend()) { *result = it->second; return true; } } return false; }; { ReadLocker lock(this); auto it = _cdnPublicKeys.find(dcId); if (it != _cdnPublicKeys.cend()) { return findKey(it->second); } } return findKey(_publicKeys); } DcOptions::Variants DcOptions::lookup(DcId dcId, DcType type) const { auto lookupDesiredFlags = [type](int address, int protocol) -> std::vector { switch (type) { case DcType::Regular: { switch (address) { case Variants::IPv4: { switch (protocol) { case Variants::Tcp: return { // Regular TCP IPv4 qFlags(MTPDdcOption::Flag::f_tcpo_only), MTPDdcOption::Flags(0) }; case Variants::Http: return { // Regular HTTP IPv4 MTPDdcOption::Flags(0), }; } } break; case Variants::IPv6: { switch (protocol) { case Variants::Tcp: return { // Regular TCP IPv6 (MTPDdcOption::Flag::f_tcpo_only | MTPDdcOption::Flag::f_ipv6), qFlags(MTPDdcOption::Flag::f_ipv6), }; case Variants::Http: return { // Regular HTTP IPv6 qFlags(MTPDdcOption::Flag::f_ipv6), }; } } break; } } break; case DcType::MediaDownload: { switch (address) { case Variants::IPv4: { switch (protocol) { case Variants::Tcp: return { // Media download TCP IPv4 (MTPDdcOption::Flag::f_media_only | MTPDdcOption::Flag::f_tcpo_only), qFlags(MTPDdcOption::Flag::f_tcpo_only), qFlags(MTPDdcOption::Flag::f_media_only), MTPDdcOption::Flags(0), }; case Variants::Http: return { // Media download HTTP IPv4 qFlags(MTPDdcOption::Flag::f_media_only), MTPDdcOption::Flags(0), }; } } break; case Variants::IPv6: { switch (protocol) { case Variants::Tcp: return { // Media download TCP IPv6 (MTPDdcOption::Flag::f_media_only | MTPDdcOption::Flag::f_tcpo_only | MTPDdcOption::Flag::f_ipv6), (MTPDdcOption::Flag::f_tcpo_only | MTPDdcOption::Flag::f_ipv6), (MTPDdcOption::Flag::f_media_only | MTPDdcOption::Flag::f_ipv6), qFlags(MTPDdcOption::Flag::f_ipv6) }; case Variants::Http: return { // Media download HTTP IPv6 (MTPDdcOption::Flag::f_media_only | MTPDdcOption::Flag::f_ipv6), qFlags(MTPDdcOption::Flag::f_ipv6), }; } } break; } } break; case DcType::Cdn: { switch (address) { case Variants::IPv4: { switch (protocol) { case Variants::Tcp: return { // CDN TCP IPv4 (MTPDdcOption::Flag::f_cdn | MTPDdcOption::Flag::f_tcpo_only), qFlags(MTPDdcOption::Flag::f_cdn), }; case Variants::Http: return { // CDN HTTP IPv4 qFlags(MTPDdcOption::Flag::f_cdn), }; } } break; case Variants::IPv6: { switch (protocol) { case Variants::Tcp: return { // CDN TCP IPv6 (MTPDdcOption::Flag::f_cdn | MTPDdcOption::Flag::f_tcpo_only | MTPDdcOption::Flag::f_ipv6), (MTPDdcOption::Flag::f_cdn | MTPDdcOption::Flag::f_ipv6), }; case Variants::Http: return { // CDN HTTP IPv6 (MTPDdcOption::Flag::f_cdn | MTPDdcOption::Flag::f_ipv6), }; } } break; } } break; } Unexpected("Bad type / address / protocol"); }; auto result = Variants(); { ReadLocker lock(this); for (auto address = 0; address != Variants::AddressTypeCount; ++address) { for (auto protocol = 0; protocol != Variants::ProtocolCount; ++protocol) { auto desiredFlags = lookupDesiredFlags(address, protocol); for (auto flags : desiredFlags) { auto shift = static_cast(flags); if (shift < 0) continue; auto it = _data.find(shiftDcId(dcId, shift)); if (it != _data.cend()) { result.data[address][protocol].ip = it->second.ip; result.data[address][protocol].flags = it->second.flags; result.data[address][protocol].port = it->second.port; break; } } } } } return result; } void DcOptions::computeCdnDcIds() { _cdnDcIds.clear(); for (auto &item : _data) { if (item.second.flags & MTPDdcOption::Flag::f_cdn) { _cdnDcIds.insert(item.second.id); } } } bool DcOptions::loadFromFile(const QString &path) { QVector options; QFile f(path); if (!f.open(QIODevice::ReadOnly)) { LOG(("MTP Error: could not read '%1'").arg(path)); return false; } QTextStream stream(&f); stream.setCodec("UTF-8"); while (!stream.atEnd()) { auto line = stream.readLine(); auto components = line.split(QRegularExpression(R"(\s)"), QString::SkipEmptyParts); if (components.isEmpty() || components[0].startsWith('#')) { continue; } auto error = [line] { LOG(("MTP Error: in .tdesktop-endpoints expected 'dcId host port [tcpo_only] [media_only]', got '%1'").arg(line)); return false; }; if (components.size() < 3) { return error(); } auto dcId = components[0].toInt(); auto ip = components[1]; auto port = components[2].toInt(); auto host = QHostAddress(); if (dcId <= 0 || dcId >= internal::kDcShift || !host.setAddress(ip) || port <= 0) { return error(); } auto flags = MTPDdcOption::Flags(0); if (host.protocol() == QAbstractSocket::IPv6Protocol) { flags |= MTPDdcOption::Flag::f_ipv6; } for (auto &option : components.mid(3)) { if (option.startsWith('#')) { break; } else if (option == qstr("tcpo_only")) { flags |= MTPDdcOption::Flag::f_tcpo_only; } else if (option == qstr("media_only")) { flags |= MTPDdcOption::Flag::f_media_only; } else { return error(); } } options.push_back(MTP_dcOption(MTP_flags(flags), MTP_int(dcId), MTP_string(ip), MTP_int(port))); } if (options.isEmpty()) { LOG(("MTP Error: in .tdesktop-endpoints expected at least one endpoint being provided.")); return false; } _immutable = false; setFromList(MTP_vector(options)); _immutable = true; return true; } bool DcOptions::writeToFile(const QString &path) const { QFile f(path); if (!f.open(QIODevice::WriteOnly)) { return false; } QTextStream stream(&f); stream.setCodec("UTF-8"); ReadLocker lock(this); for (auto &item : _data) { auto &endpoint = item.second; stream << endpoint.id << ' ' << QString::fromStdString(endpoint.ip) << ' ' << endpoint.port; if (endpoint.flags & MTPDdcOption::Flag::f_tcpo_only) { stream << " tcpo_only"; } if (endpoint.flags & MTPDdcOption::Flag::f_media_only) { stream << " media_only"; } stream << '\n'; } return true; } } // namespace MTP