diff --git a/src/base/CMakeLists.txt b/src/base/CMakeLists.txt index 6d50a4faa..2040408ca 100644 --- a/src/base/CMakeLists.txt +++ b/src/base/CMakeLists.txt @@ -33,6 +33,7 @@ add_library(qbt_base STATIC bittorrent/sessionimpl.h bittorrent/sessionstatus.h bittorrent/speedmonitor.h + bittorrent/sslparameters.h bittorrent/torrent.h bittorrent/torrentcontenthandler.h bittorrent/torrentcontentlayout.h @@ -105,6 +106,7 @@ add_library(qbt_base STATIC utils/os.h utils/password.h utils/random.h + utils/sslkey.h utils/string.h utils/thread.h utils/version.h @@ -135,6 +137,7 @@ add_library(qbt_base STATIC bittorrent/resumedatastorage.cpp bittorrent/sessionimpl.cpp bittorrent/speedmonitor.cpp + bittorrent/sslparameters.cpp bittorrent/torrent.cpp bittorrent/torrentcontenthandler.cpp bittorrent/torrentcreator.cpp @@ -194,6 +197,7 @@ add_library(qbt_base STATIC utils/os.cpp utils/password.cpp utils/random.cpp + utils/sslkey.cpp utils/string.cpp utils/thread.cpp utils/version.cpp diff --git a/src/base/bittorrent/addtorrentparams.cpp b/src/base/bittorrent/addtorrentparams.cpp index d2e17ab74..14d62e422 100644 --- a/src/base/bittorrent/addtorrentparams.cpp +++ b/src/base/bittorrent/addtorrentparams.cpp @@ -32,6 +32,7 @@ #include #include +#include "base/utils/sslkey.h" #include "base/utils/string.h" const QString PARAM_CATEGORY = u"category"_s; @@ -51,6 +52,9 @@ const QString PARAM_DOWNLOADLIMIT = u"download_limit"_s; const QString PARAM_SEEDINGTIMELIMIT = u"seeding_time_limit"_s; const QString PARAM_INACTIVESEEDINGTIMELIMIT = u"inactive_seeding_time_limit"_s; const QString PARAM_RATIOLIMIT = u"ratio_limit"_s; +const QString PARAM_SSL_CERTIFICATE = u"ssl_certificate"_s; +const QString PARAM_SSL_PRIVATEKEY = u"ssl_private_key"_s; +const QString PARAM_SSL_DHPARAMS = u"ssl_dh_params"_s; namespace { @@ -122,7 +126,13 @@ BitTorrent::AddTorrentParams BitTorrent::parseAddTorrentParams(const QJsonObject .downloadLimit = jsonObj.value(PARAM_DOWNLOADLIMIT).toInt(-1), .seedingTimeLimit = jsonObj.value(PARAM_SEEDINGTIMELIMIT).toInt(Torrent::USE_GLOBAL_SEEDING_TIME), .inactiveSeedingTimeLimit = jsonObj.value(PARAM_INACTIVESEEDINGTIMELIMIT).toInt(Torrent::USE_GLOBAL_INACTIVE_SEEDING_TIME), - .ratioLimit = jsonObj.value(PARAM_RATIOLIMIT).toDouble(Torrent::USE_GLOBAL_RATIO) + .ratioLimit = jsonObj.value(PARAM_RATIOLIMIT).toDouble(Torrent::USE_GLOBAL_RATIO), + .sslParameters = + { + .certificate = QSslCertificate(jsonObj.value(PARAM_SSL_CERTIFICATE).toString().toLatin1()), + .privateKey = Utils::SSLKey::load(jsonObj.value(PARAM_SSL_PRIVATEKEY).toString().toLatin1()), + .dhParams = jsonObj.value(PARAM_SSL_DHPARAMS).toString().toLatin1() + } }; return params; } @@ -136,13 +146,16 @@ QJsonObject BitTorrent::serializeAddTorrentParams(const AddTorrentParams ¶ms {PARAM_SAVEPATH, params.savePath.data()}, {PARAM_DOWNLOADPATH, params.downloadPath.data()}, {PARAM_OPERATINGMODE, Utils::String::fromEnum(params.addForced - ? TorrentOperatingMode::Forced : TorrentOperatingMode::AutoManaged)}, + ? TorrentOperatingMode::Forced : TorrentOperatingMode::AutoManaged)}, {PARAM_SKIPCHECKING, params.skipChecking}, {PARAM_UPLOADLIMIT, params.uploadLimit}, {PARAM_DOWNLOADLIMIT, params.downloadLimit}, {PARAM_SEEDINGTIMELIMIT, params.seedingTimeLimit}, {PARAM_INACTIVESEEDINGTIMELIMIT, params.inactiveSeedingTimeLimit}, - {PARAM_RATIOLIMIT, params.ratioLimit} + {PARAM_RATIOLIMIT, params.ratioLimit}, + {PARAM_SSL_CERTIFICATE, QString::fromLatin1(params.sslParameters.certificate.toPem())}, + {PARAM_SSL_PRIVATEKEY, QString::fromLatin1(params.sslParameters.privateKey.toPem())}, + {PARAM_SSL_DHPARAMS, QString::fromLatin1(params.sslParameters.dhParams)} }; if (params.addToQueueTop) diff --git a/src/base/bittorrent/addtorrentparams.h b/src/base/bittorrent/addtorrentparams.h index 809310744..b68475a31 100644 --- a/src/base/bittorrent/addtorrentparams.h +++ b/src/base/bittorrent/addtorrentparams.h @@ -36,6 +36,7 @@ #include "base/path.h" #include "base/tagset.h" +#include "sslparameters.h" #include "torrent.h" #include "torrentcontentlayout.h" @@ -69,6 +70,7 @@ namespace BitTorrent int seedingTimeLimit = Torrent::USE_GLOBAL_SEEDING_TIME; int inactiveSeedingTimeLimit = Torrent::USE_GLOBAL_INACTIVE_SEEDING_TIME; qreal ratioLimit = Torrent::USE_GLOBAL_RATIO; + SSLParameters sslParameters; friend bool operator==(const AddTorrentParams &lhs, const AddTorrentParams &rhs) = default; }; diff --git a/src/base/bittorrent/bencoderesumedatastorage.cpp b/src/base/bittorrent/bencoderesumedatastorage.cpp index f2bdd10e9..97d80ff07 100644 --- a/src/base/bittorrent/bencoderesumedatastorage.cpp +++ b/src/base/bittorrent/bencoderesumedatastorage.cpp @@ -49,6 +49,7 @@ #include "base/tagset.h" #include "base/utils/fs.h" #include "base/utils/io.h" +#include "base/utils/sslkey.h" #include "base/utils/string.h" #include "infohash.h" #include "loadtorrentparams.h" @@ -73,12 +74,22 @@ namespace BitTorrent namespace { + const char KEY_SSL_CERTIFICATE[] = "qBt-sslCertificate"; + const char KEY_SSL_PRIVATE_KEY[] = "qBt-sslPrivateKey"; + const char KEY_SSL_DH_PARAMS[] = "qBt-sslDhParams"; + template QString fromLTString(const LTStr &str) { return QString::fromUtf8(str.data(), static_cast(str.size())); } + template + QByteArray toByteArray(const LTStr &str) + { + return {str.data(), static_cast(str.size())}; + } + using ListType = lt::entry::list_type; ListType setToEntryList(const TagSet &input) @@ -251,6 +262,12 @@ BitTorrent::LoadResumeDataResult BitTorrent::BencodeResumeDataStorage::loadTorre torrentParams.stopCondition = Utils::String::toEnum( fromLTString(resumeDataRoot.dict_find_string_value("qBt-stopCondition")), Torrent::StopCondition::None); + torrentParams.sslParameters = + { + .certificate = QSslCertificate(toByteArray(resumeDataRoot.dict_find_string_value(KEY_SSL_CERTIFICATE))), + .privateKey = Utils::SSLKey::load(toByteArray(resumeDataRoot.dict_find_string_value(KEY_SSL_PRIVATE_KEY))), + .dhParams = toByteArray(resumeDataRoot.dict_find_string_value(KEY_SSL_DH_PARAMS)) + }; const lt::string_view ratioLimitString = resumeDataRoot.dict_find_string_value("qBt-ratioLimit"); if (ratioLimitString.empty()) @@ -409,6 +426,13 @@ void BitTorrent::BencodeResumeDataStorage::Worker::store(const TorrentID &id, co data["qBt-firstLastPiecePriority"] = resumeData.firstLastPiecePriority; data["qBt-stopCondition"] = Utils::String::fromEnum(resumeData.stopCondition).toStdString(); + if (!resumeData.sslParameters.certificate.isNull()) + data[KEY_SSL_CERTIFICATE] = resumeData.sslParameters.certificate.toPem().toStdString(); + if (!resumeData.sslParameters.privateKey.isNull()) + data[KEY_SSL_PRIVATE_KEY] = resumeData.sslParameters.privateKey.toPem().toStdString(); + if (!resumeData.sslParameters.dhParams.isEmpty()) + data[KEY_SSL_DH_PARAMS] = resumeData.sslParameters.dhParams.toStdString(); + if (!resumeData.useAutoTMM) { data["qBt-savePath"] = Profile::instance()->toPortablePath(resumeData.savePath).data().toStdString(); diff --git a/src/base/bittorrent/dbresumedatastorage.cpp b/src/base/bittorrent/dbresumedatastorage.cpp index 3d8e5ece3..d2a9bc7ad 100644 --- a/src/base/bittorrent/dbresumedatastorage.cpp +++ b/src/base/bittorrent/dbresumedatastorage.cpp @@ -58,6 +58,7 @@ #include "base/preferences.h" #include "base/profile.h" #include "base/utils/fs.h" +#include "base/utils/sslkey.h" #include "base/utils/string.h" #include "infohash.h" #include "loadtorrentparams.h" @@ -66,7 +67,7 @@ namespace { const QString DB_CONNECTION_NAME = u"ResumeDataStorage"_s; - const int DB_VERSION = 5; + const int DB_VERSION = 6; const QString DB_TABLE_META = u"meta"_s; const QString DB_TABLE_TORRENTS = u"torrents"_s; @@ -121,7 +122,8 @@ namespace Column makeColumn(const char *columnName) { - return {QString::fromLatin1(columnName), (u':' + QString::fromLatin1(columnName))}; + const QString name = QString::fromLatin1(columnName); + return {.name = name, .placeholder = (u':' + name)}; } const Column DB_COLUMN_ID = makeColumn("id"); @@ -141,6 +143,9 @@ namespace const Column DB_COLUMN_OPERATING_MODE = makeColumn("operating_mode"); const Column DB_COLUMN_STOPPED = makeColumn("stopped"); const Column DB_COLUMN_STOP_CONDITION = makeColumn("stop_condition"); + const Column DB_COLUMN_SSL_CERTIFICATE = makeColumn("ssl_certificate"); + const Column DB_COLUMN_SSL_PRIVATE_KEY = makeColumn("ssl_private_key"); + const Column DB_COLUMN_SSL_DH_PARAMS = makeColumn("ssl_dh_params"); const Column DB_COLUMN_RESUMEDATA = makeColumn("libtorrent_resume_data"); const Column DB_COLUMN_METADATA = makeColumn("metadata"); const Column DB_COLUMN_VALUE = makeColumn("value"); @@ -154,7 +159,6 @@ namespace QString quoted(const QString &name) { const QChar quote = u'`'; - return (quote + name + quote); } @@ -237,6 +241,12 @@ namespace resumeData.stopped = query.value(DB_COLUMN_STOPPED.name).toBool(); resumeData.stopCondition = Utils::String::toEnum( query.value(DB_COLUMN_STOP_CONDITION.name).toString(), Torrent::StopCondition::None); + resumeData.sslParameters = + { + .certificate = QSslCertificate(query.value(DB_COLUMN_SSL_CERTIFICATE.name).toByteArray()), + .privateKey = Utils::SSLKey::load(query.value(DB_COLUMN_SSL_PRIVATE_KEY.name).toByteArray()), + .dhParams = query.value(DB_COLUMN_SSL_DH_PARAMS.name).toByteArray() + }; resumeData.savePath = Profile::instance()->fromPortablePath( Path(query.value(DB_COLUMN_TARGET_SAVE_PATH.name).toString())); @@ -535,6 +545,9 @@ void BitTorrent::DBResumeDataStorage::createDB() const makeColumnDefinition(DB_COLUMN_OPERATING_MODE, "TEXT NOT NULL"), makeColumnDefinition(DB_COLUMN_STOPPED, "INTEGER NOT NULL"), makeColumnDefinition(DB_COLUMN_STOP_CONDITION, "TEXT NOT NULL DEFAULT `None`"), + makeColumnDefinition(DB_COLUMN_SSL_CERTIFICATE, "TEXT"), + makeColumnDefinition(DB_COLUMN_SSL_PRIVATE_KEY, "TEXT"), + makeColumnDefinition(DB_COLUMN_SSL_DH_PARAMS, "TEXT"), makeColumnDefinition(DB_COLUMN_RESUMEDATA, "BLOB NOT NULL"), makeColumnDefinition(DB_COLUMN_METADATA, "BLOB") }; @@ -574,31 +587,22 @@ void BitTorrent::DBResumeDataStorage::updateDB(const int fromVersion) const try { - if (fromVersion == 1) + const auto addColumn = [&query](const QString &table, const Column &column, const char *definition) { - const auto testQuery = u"SELECT COUNT(%1) FROM %2;"_s - .arg(quoted(DB_COLUMN_DOWNLOAD_PATH.name), quoted(DB_TABLE_TORRENTS)); - if (!query.exec(testQuery)) - { - const auto alterTableTorrentsQuery = u"ALTER TABLE %1 ADD %2"_s - .arg(quoted(DB_TABLE_TORRENTS), makeColumnDefinition(DB_COLUMN_DOWNLOAD_PATH, "TEXT")); - if (!query.exec(alterTableTorrentsQuery)) - throw RuntimeError(query.lastError().text()); - } - } + const auto testQuery = u"SELECT COUNT(%1) FROM %2;"_s.arg(quoted(column.name), quoted(table)); + if (query.exec(testQuery)) + return; + + const auto alterTableQuery = u"ALTER TABLE %1 ADD %2"_s.arg(quoted(table), makeColumnDefinition(column, definition)); + if (!query.exec(alterTableQuery)) + throw RuntimeError(query.lastError().text()); + }; + + if (fromVersion <= 1) + addColumn(DB_TABLE_TORRENTS, DB_COLUMN_DOWNLOAD_PATH, "TEXT"); if (fromVersion <= 2) - { - const auto testQuery = u"SELECT COUNT(%1) FROM %2;"_s - .arg(quoted(DB_COLUMN_STOP_CONDITION.name), quoted(DB_TABLE_TORRENTS)); - if (!query.exec(testQuery)) - { - const auto alterTableTorrentsQuery = u"ALTER TABLE %1 ADD %2"_s - .arg(quoted(DB_TABLE_TORRENTS), makeColumnDefinition(DB_COLUMN_STOP_CONDITION, "TEXT NOT NULL DEFAULT `None`")); - if (!query.exec(alterTableTorrentsQuery)) - throw RuntimeError(query.lastError().text()); - } - } + addColumn(DB_TABLE_TORRENTS, DB_COLUMN_STOP_CONDITION, "TEXT NOT NULL DEFAULT `None`"); if (fromVersion <= 3) { @@ -610,16 +614,13 @@ void BitTorrent::DBResumeDataStorage::updateDB(const int fromVersion) const } if (fromVersion <= 4) + addColumn(DB_TABLE_TORRENTS, DB_COLUMN_INACTIVE_SEEDING_TIME_LIMIT, "INTEGER NOT NULL DEFAULT -2"); + + if (fromVersion <= 5) { - const auto testQuery = u"SELECT COUNT(%1) FROM %2;"_s - .arg(quoted(DB_COLUMN_INACTIVE_SEEDING_TIME_LIMIT.name), quoted(DB_TABLE_TORRENTS)); - if (!query.exec(testQuery)) - { - const auto alterTableTorrentsQuery = u"ALTER TABLE %1 ADD %2"_s - .arg(quoted(DB_TABLE_TORRENTS), makeColumnDefinition(DB_COLUMN_INACTIVE_SEEDING_TIME_LIMIT, "INTEGER NOT NULL DEFAULT -2")); - if (!query.exec(alterTableTorrentsQuery)) - throw RuntimeError(query.lastError().text()); - } + addColumn(DB_TABLE_TORRENTS, DB_COLUMN_SSL_CERTIFICATE, "TEXT"); + addColumn(DB_TABLE_TORRENTS, DB_COLUMN_SSL_PRIVATE_KEY, "TEXT"); + addColumn(DB_TABLE_TORRENTS, DB_COLUMN_SSL_DH_PARAMS, "TEXT"); } const QString updateMetaVersionQuery = makeUpdateStatement(DB_TABLE_META, {DB_COLUMN_NAME, DB_COLUMN_VALUE}); @@ -805,6 +806,9 @@ namespace DB_COLUMN_OPERATING_MODE, DB_COLUMN_STOPPED, DB_COLUMN_STOP_CONDITION, + DB_COLUMN_SSL_CERTIFICATE, + DB_COLUMN_SSL_PRIVATE_KEY, + DB_COLUMN_SSL_DH_PARAMS, DB_COLUMN_RESUMEDATA }; @@ -864,6 +868,9 @@ namespace query.bindValue(DB_COLUMN_OPERATING_MODE.placeholder, Utils::String::fromEnum(m_resumeData.operatingMode)); query.bindValue(DB_COLUMN_STOPPED.placeholder, m_resumeData.stopped); query.bindValue(DB_COLUMN_STOP_CONDITION.placeholder, Utils::String::fromEnum(m_resumeData.stopCondition)); + query.bindValue(DB_COLUMN_SSL_CERTIFICATE.placeholder, QString::fromLatin1(m_resumeData.sslParameters.certificate.toPem())); + query.bindValue(DB_COLUMN_SSL_PRIVATE_KEY.placeholder, QString::fromLatin1(m_resumeData.sslParameters.privateKey.toPem())); + query.bindValue(DB_COLUMN_SSL_DH_PARAMS.placeholder, QString::fromLatin1(m_resumeData.sslParameters.dhParams)); if (!m_resumeData.useAutoTMM) { diff --git a/src/base/bittorrent/loadtorrentparams.h b/src/base/bittorrent/loadtorrentparams.h index 97e091f9a..63c7ce7d4 100644 --- a/src/base/bittorrent/loadtorrentparams.h +++ b/src/base/bittorrent/loadtorrentparams.h @@ -34,6 +34,7 @@ #include "base/path.h" #include "base/tagset.h" +#include "sslparameters.h" #include "torrent.h" #include "torrentcontentlayout.h" @@ -61,5 +62,6 @@ namespace BitTorrent qreal ratioLimit = Torrent::USE_GLOBAL_RATIO; int seedingTimeLimit = Torrent::USE_GLOBAL_SEEDING_TIME; int inactiveSeedingTimeLimit = Torrent::USE_GLOBAL_INACTIVE_SEEDING_TIME; + SSLParameters sslParameters; }; } diff --git a/src/base/bittorrent/session.h b/src/base/bittorrent/session.h index 39f78bce5..54296562d 100644 --- a/src/base/bittorrent/session.h +++ b/src/base/bittorrent/session.h @@ -263,6 +263,10 @@ namespace BitTorrent virtual void setSaveResumeDataInterval(int value) = 0; virtual int port() const = 0; virtual void setPort(int port) = 0; + virtual bool isSSLEnabled() const = 0; + virtual void setSSLEnabled(bool enabled) = 0; + virtual int sslPort() const = 0; + virtual void setSSLPort(int port) = 0; virtual QString networkInterface() const = 0; virtual void setNetworkInterface(const QString &iface) = 0; virtual QString networkInterfaceName() const = 0; diff --git a/src/base/bittorrent/sessionimpl.cpp b/src/base/bittorrent/sessionimpl.cpp index e27e25a57..356a2f13e 100644 --- a/src/base/bittorrent/sessionimpl.cpp +++ b/src/base/bittorrent/sessionimpl.cpp @@ -479,6 +479,8 @@ SessionImpl::SessionImpl(QObject *parent) , m_isPerformanceWarningEnabled(BITTORRENT_SESSION_KEY(u"PerformanceWarning"_s), false) , m_saveResumeDataInterval(BITTORRENT_SESSION_KEY(u"SaveResumeDataInterval"_s), 60) , m_port(BITTORRENT_SESSION_KEY(u"Port"_s), -1) + , m_sslEnabled(BITTORRENT_SESSION_KEY(u"SSL/Enabled"_s), false) + , m_sslPort(BITTORRENT_SESSION_KEY(u"SSL/Port"_s), -1) , m_networkInterface(BITTORRENT_SESSION_KEY(u"Interface"_s)) , m_networkInterfaceName(BITTORRENT_SESSION_KEY(u"InterfaceName"_s)) , m_networkInterfaceAddress(BITTORRENT_SESSION_KEY(u"InterfaceAddress"_s)) @@ -529,6 +531,12 @@ SessionImpl::SessionImpl(QObject *parent) if (port() < 0) m_port = Utils::Random::rand(1024, 65535); + if (sslPort() < 0) + { + m_sslPort = Utils::Random::rand(1024, 65535); + while (m_sslPort == port()) + m_sslPort = Utils::Random::rand(1024, 65535); + } m_recentErroredTorrentsTimer->setSingleShot(true); m_recentErroredTorrentsTimer->setInterval(1s); @@ -2022,7 +2030,9 @@ void SessionImpl::applyNetworkInterfacesSettings(lt::settings_pack &settingsPack QStringList endpoints; QStringList outgoingInterfaces; - const QString portString = u':' + QString::number(port()); + QStringList portStrings = {u':' + QString::number(port())}; + if (isSSLEnabled()) + portStrings.append(u':' + QString::number(sslPort()) + u's'); for (const QString &ip : asConst(getListeningIPs())) { @@ -2034,7 +2044,8 @@ void SessionImpl::applyNetworkInterfacesSettings(lt::settings_pack &settingsPack ? Utils::Net::canonicalIPv6Addr(addr).toString() : addr.toString(); - endpoints << ((isIPv6 ? (u'[' + ip + u']') : ip) + portString); + for (const QString &portString : asConst(portStrings)) + endpoints << ((isIPv6 ? (u'[' + ip + u']') : ip) + portString); if ((ip != u"0.0.0.0") && (ip != u"::")) outgoingInterfaces << ip; @@ -2049,7 +2060,8 @@ void SessionImpl::applyNetworkInterfacesSettings(lt::settings_pack &settingsPack const QString guid = convertIfaceNameToGuid(ip); if (!guid.isEmpty()) { - endpoints << (guid + portString); + for (const QString &portString : asConst(portStrings)) + endpoints << (guid + portString); outgoingInterfaces << guid; } else @@ -2057,11 +2069,13 @@ void SessionImpl::applyNetworkInterfacesSettings(lt::settings_pack &settingsPack LogMsg(tr("Could not find GUID of network interface. Interface: \"%1\"").arg(ip), Log::WARNING); // Since we can't get the GUID, we'll pass the interface name instead. // Otherwise an empty string will be passed to outgoing_interface which will cause IP leak. - endpoints << (ip + portString); + for (const QString &portString : asConst(portStrings)) + endpoints << (ip + portString); outgoingInterfaces << ip; } #else - endpoints << (ip + portString); + for (const QString &portString : asConst(portStrings)) + endpoints << (ip + portString); outgoingInterfaces << ip; #endif } @@ -2640,6 +2654,7 @@ LoadTorrentParams SessionImpl::initLoadTorrentParams(const AddTorrentParams &add loadTorrentParams.ratioLimit = addTorrentParams.ratioLimit; loadTorrentParams.seedingTimeLimit = addTorrentParams.seedingTimeLimit; loadTorrentParams.inactiveSeedingTimeLimit = addTorrentParams.inactiveSeedingTimeLimit; + loadTorrentParams.sslParameters = addTorrentParams.sslParameters; const QString category = addTorrentParams.category; if (!category.isEmpty() && !m_categories.contains(category) && !addCategory(category)) @@ -3525,6 +3540,40 @@ void SessionImpl::setPort(const int port) } } +bool SessionImpl::isSSLEnabled() const +{ + return m_sslEnabled; +} + +void SessionImpl::setSSLEnabled(const bool enabled) +{ + if (enabled == isSSLEnabled()) + return; + + m_sslEnabled = enabled; + configureListeningInterface(); + + if (isReannounceWhenAddressChangedEnabled()) + reannounceToAllTrackers(); +} + +int SessionImpl::sslPort() const +{ + return m_sslPort; +} + +void SessionImpl::setSSLPort(const int port) +{ + if (port == sslPort()) + return; + + m_sslPort = port; + configureListeningInterface(); + + if (isReannounceWhenAddressChangedEnabled()) + reannounceToAllTrackers(); +} + QString SessionImpl::networkInterface() const { return m_networkInterface; @@ -5449,6 +5498,9 @@ void SessionImpl::handleAlert(const lt::alert *a) case lt::torrent_delete_failed_alert::alert_type: handleTorrentDeleteFailedAlert(static_cast(a)); break; + case lt::torrent_need_cert_alert::alert_type: + handleTorrentNeedCertAlert(static_cast(a)); + break; case lt::portmap_error_alert::alert_type: handlePortmapWarningAlert(static_cast(a)); break; @@ -5652,6 +5704,26 @@ void SessionImpl::handleTorrentDeleteFailedAlert(const lt::torrent_delete_failed m_removingTorrents.erase(removingTorrentDataIter); } +void SessionImpl::handleTorrentNeedCertAlert(const lt::torrent_need_cert_alert *a) +{ +#ifdef QBT_USES_LIBTORRENT2 + const InfoHash infoHash {a->handle.info_hashes()}; +#else + const InfoHash infoHash {a->handle.info_hash()}; +#endif + const auto torrentID = TorrentID::fromInfoHash(infoHash); + + TorrentImpl *const torrent = m_torrents.value(torrentID); + if (!torrent) [[unlikely]] + return; + + if (!torrent->applySSLParameters()) + { + LogMsg(tr("Torrent is missing SSL parameters. Torrent: \"%1\". Message: \"%2\"").arg(torrent->name(), QString::fromStdString(a->message())) + , Log::WARNING); + } +} + void SessionImpl::handleMetadataReceivedAlert(const lt::metadata_received_alert *p) { const TorrentID torrentID {p->handle.info_hash()}; diff --git a/src/base/bittorrent/sessionimpl.h b/src/base/bittorrent/sessionimpl.h index 1f5dadedb..d99a46af5 100644 --- a/src/base/bittorrent/sessionimpl.h +++ b/src/base/bittorrent/sessionimpl.h @@ -230,6 +230,10 @@ namespace BitTorrent void setSaveResumeDataInterval(int value) override; int port() const override; void setPort(int port) override; + bool isSSLEnabled() const override; + void setSSLEnabled(bool enabled) override; + int sslPort() const override; + void setSSLPort(int port) override; QString networkInterface() const override; void setNetworkInterface(const QString &iface) override; QString networkInterfaceName() const override; @@ -543,6 +547,7 @@ namespace BitTorrent void handleTorrentRemovedAlert(const lt::torrent_removed_alert *p); void handleTorrentDeletedAlert(const lt::torrent_deleted_alert *p); void handleTorrentDeleteFailedAlert(const lt::torrent_delete_failed_alert *p); + void handleTorrentNeedCertAlert(const lt::torrent_need_cert_alert *a); void handlePortmapWarningAlert(const lt::portmap_error_alert *p); void handlePortmapAlert(const lt::portmap_alert *p); void handlePeerBlockedAlert(const lt::peer_blocked_alert *p); @@ -676,6 +681,8 @@ namespace BitTorrent CachedSettingValue m_isPerformanceWarningEnabled; CachedSettingValue m_saveResumeDataInterval; CachedSettingValue m_port; + CachedSettingValue m_sslEnabled; + CachedSettingValue m_sslPort; CachedSettingValue m_networkInterface; CachedSettingValue m_networkInterfaceName; CachedSettingValue m_networkInterfaceAddress; diff --git a/src/base/bittorrent/sslparameters.cpp b/src/base/bittorrent/sslparameters.cpp new file mode 100644 index 000000000..ba7ecce2a --- /dev/null +++ b/src/base/bittorrent/sslparameters.cpp @@ -0,0 +1,34 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2024 Mike Tzou (Chocobo1) + * + * This program 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 2 + * of the License, or (at your option) any later version. + * + * This program 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give permission to + * link this program with the OpenSSL project's "OpenSSL" library (or with + * modified versions of it that use the same license as the "OpenSSL" library), + * and distribute the linked executables. You must obey the GNU General Public + * License in all respects for all of the code used other than "OpenSSL". If you + * modify file(s), you may extend this exception to your version of the file(s), + * but you are not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + */ + +#include "sslparameters.h" + +bool BitTorrent::SSLParameters::isValid() const +{ + return (!certificate.isNull() && !privateKey.isNull() && !dhParams.isEmpty()); +} diff --git a/src/base/bittorrent/sslparameters.h b/src/base/bittorrent/sslparameters.h new file mode 100644 index 000000000..8033b9164 --- /dev/null +++ b/src/base/bittorrent/sslparameters.h @@ -0,0 +1,47 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2024 Mike Tzou (Chocobo1) + * + * This program 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 2 + * of the License, or (at your option) any later version. + * + * This program 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give permission to + * link this program with the OpenSSL project's "OpenSSL" library (or with + * modified versions of it that use the same license as the "OpenSSL" library), + * and distribute the linked executables. You must obey the GNU General Public + * License in all respects for all of the code used other than "OpenSSL". If you + * modify file(s), you may extend this exception to your version of the file(s), + * but you are not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + */ + +#pragma once + +#include +#include +#include + +namespace BitTorrent +{ + struct SSLParameters + { + QSslCertificate certificate {{}}; + QSslKey privateKey; + QByteArray dhParams; + + bool isValid() const; + + friend bool operator==(const SSLParameters &left, const SSLParameters &right) = default; + }; +} diff --git a/src/base/bittorrent/torrent.h b/src/base/bittorrent/torrent.h index 31347138a..01c1a1492 100644 --- a/src/base/bittorrent/torrent.h +++ b/src/base/bittorrent/torrent.h @@ -53,6 +53,7 @@ namespace BitTorrent class TorrentID; class TorrentInfo; struct PeerAddress; + struct SSLParameters; struct TrackerEntry; // Using `Q_ENUM_NS()` without a wrapper namespace in our case is not advised @@ -306,6 +307,8 @@ namespace BitTorrent virtual StopCondition stopCondition() const = 0; virtual void setStopCondition(StopCondition stopCondition) = 0; + virtual SSLParameters getSSLParameters() const = 0; + virtual void setSSLParameters(const SSLParameters &sslParams) = 0; virtual QString createMagnetURI() const = 0; virtual nonstd::expected exportToBuffer() const = 0; diff --git a/src/base/bittorrent/torrentimpl.cpp b/src/base/bittorrent/torrentimpl.cpp index 24d469e9b..3e3bb090a 100644 --- a/src/base/bittorrent/torrentimpl.cpp +++ b/src/base/bittorrent/torrentimpl.cpp @@ -304,6 +304,7 @@ TorrentImpl::TorrentImpl(SessionImpl *session, lt::session *nativeSession , m_hasFirstLastPiecePriority(params.firstLastPiecePriority) , m_useAutoTMM(params.useAutoTMM) , m_isStopped(params.stopped) + , m_sslParams(params.sslParameters) , m_ltAddTorrentParams(params.ltAddTorrentParams) , m_downloadLimit(cleanLimitValue(m_ltAddTorrentParams.download_limit)) , m_uploadLimit(cleanLimitValue(m_ltAddTorrentParams.upload_limit)) @@ -2123,26 +2124,27 @@ void TorrentImpl::prepareResumeData(const lt::add_torrent_params ¶ms) // We shouldn't save upload_mode flag to allow torrent operate normally on next run m_ltAddTorrentParams.flags &= ~lt::torrent_flags::upload_mode; - LoadTorrentParams resumeData; - resumeData.name = m_name; - resumeData.category = m_category; - resumeData.tags = m_tags; - resumeData.contentLayout = m_contentLayout; - resumeData.ratioLimit = m_ratioLimit; - resumeData.seedingTimeLimit = m_seedingTimeLimit; - resumeData.inactiveSeedingTimeLimit = m_inactiveSeedingTimeLimit; - resumeData.firstLastPiecePriority = m_hasFirstLastPiecePriority; - resumeData.hasFinishedStatus = m_hasFinishedStatus; - resumeData.stopped = m_isStopped; - resumeData.stopCondition = m_stopCondition; - resumeData.operatingMode = m_operatingMode; - resumeData.ltAddTorrentParams = m_ltAddTorrentParams; - resumeData.useAutoTMM = m_useAutoTMM; - if (!resumeData.useAutoTMM) + const LoadTorrentParams resumeData { - resumeData.savePath = m_savePath; - resumeData.downloadPath = m_downloadPath; - } + .ltAddTorrentParams = m_ltAddTorrentParams, + .name = m_name, + .category = m_category, + .tags = m_tags, + .savePath = (!m_useAutoTMM ? m_savePath : Path()), + .downloadPath = (!m_useAutoTMM ? m_downloadPath : Path()), + .contentLayout = m_contentLayout, + .operatingMode = m_operatingMode, + .useAutoTMM = m_useAutoTMM, + .firstLastPiecePriority = m_hasFirstLastPiecePriority, + .hasFinishedStatus = m_hasFinishedStatus, + .stopped = m_isStopped, + .stopCondition = m_stopCondition, + .addToQueueTop = false, + .ratioLimit = m_ratioLimit, + .seedingTimeLimit = m_seedingTimeLimit, + .inactiveSeedingTimeLimit = m_inactiveSeedingTimeLimit, + .sslParameters = m_sslParams + }; m_session->handleTorrentResumeDataReady(this, resumeData); } @@ -2476,6 +2478,32 @@ void TorrentImpl::setStopCondition(const StopCondition stopCondition) m_stopCondition = stopCondition; } +SSLParameters TorrentImpl::getSSLParameters() const +{ + return m_sslParams; +} + +void TorrentImpl::setSSLParameters(const SSLParameters &sslParams) +{ + if (sslParams == getSSLParameters()) + return; + + m_sslParams = sslParams; + applySSLParameters(); + + deferredRequestResumeData(); +} + +bool TorrentImpl::applySSLParameters() +{ + if (!m_sslParams.isValid()) + return false; + + m_nativeHandle.set_ssl_certificate_buffer(m_sslParams.certificate.toPem().toStdString() + , m_sslParams.privateKey.toPem().toStdString(), m_sslParams.dhParams.toStdString()); + return true; +} + bool TorrentImpl::isMoveInProgress() const { return m_storageIsMoving; diff --git a/src/base/bittorrent/torrentimpl.h b/src/base/bittorrent/torrentimpl.h index 0a0c55e16..2a4d49030 100644 --- a/src/base/bittorrent/torrentimpl.h +++ b/src/base/bittorrent/torrentimpl.h @@ -51,6 +51,7 @@ #include "base/tagset.h" #include "infohash.h" #include "speedmonitor.h" +#include "sslparameters.h" #include "torrent.h" #include "torrentcontentlayout.h" #include "torrentinfo.h" @@ -243,6 +244,9 @@ namespace BitTorrent StopCondition stopCondition() const override; void setStopCondition(StopCondition stopCondition) override; + SSLParameters getSSLParameters() const override; + void setSSLParameters(const SSLParameters &sslParams) override; + bool applySSLParameters(); QString createMagnetURI() const override; nonstd::expected exportToBuffer() const override; @@ -363,6 +367,7 @@ namespace BitTorrent bool m_useAutoTMM; bool m_isStopped; StopCondition m_stopCondition = StopCondition::None; + SSLParameters m_sslParams; bool m_unchecked = false; diff --git a/src/base/http/server.cpp b/src/base/http/server.cpp index 525feed10..1e2320e11 100644 --- a/src/base/http/server.cpp +++ b/src/base/http/server.cpp @@ -43,6 +43,7 @@ #include "base/algorithm.h" #include "base/global.h" #include "base/utils/net.h" +#include "base/utils/sslkey.h" #include "connection.h" using namespace std::chrono_literals; @@ -161,7 +162,7 @@ void Server::dropTimedOutConnection() bool Server::setupHttps(const QByteArray &certificates, const QByteArray &privateKey) { const QList certs {Utils::Net::loadSSLCertificate(certificates)}; - const QSslKey key {Utils::Net::loadSSLKey(privateKey)}; + const QSslKey key {Utils::SSLKey::load(privateKey)}; if (certs.isEmpty() || key.isNull()) { diff --git a/src/base/utils/net.cpp b/src/base/utils/net.cpp index 63f8f2eb7..da570090f 100644 --- a/src/base/utils/net.cpp +++ b/src/base/utils/net.cpp @@ -136,19 +136,5 @@ namespace Utils { return !loadSSLCertificate(data).isEmpty(); } - - QSslKey loadSSLKey(const QByteArray &data) - { - // try different formats - const QSslKey key {data, QSsl::Rsa}; - if (!key.isNull()) - return key; - return {data, QSsl::Ec}; - } - - bool isSSLKeyValid(const QByteArray &data) - { - return !loadSSLKey(data).isNull(); - } } } diff --git a/src/base/utils/net.h b/src/base/utils/net.h index ef035bfd2..54b585317 100644 --- a/src/base/utils/net.h +++ b/src/base/utils/net.h @@ -53,6 +53,4 @@ namespace Utils::Net inline const int MAX_SSL_FILE_SIZE = 1024 * 1024; QList loadSSLCertificate(const QByteArray &data); bool isSSLCertificatesValid(const QByteArray &data); - QSslKey loadSSLKey(const QByteArray &data); - bool isSSLKeyValid(const QByteArray &data); } diff --git a/src/base/utils/sslkey.cpp b/src/base/utils/sslkey.cpp new file mode 100644 index 000000000..7a6583ba5 --- /dev/null +++ b/src/base/utils/sslkey.cpp @@ -0,0 +1,40 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2024 Mike Tzou (Chocobo1) + * + * This program 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 2 + * of the License, or (at your option) any later version. + * + * This program 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give permission to + * link this program with the OpenSSL project's "OpenSSL" library (or with + * modified versions of it that use the same license as the "OpenSSL" library), + * and distribute the linked executables. You must obey the GNU General Public + * License in all respects for all of the code used other than "OpenSSL". If you + * modify file(s), you may extend this exception to your version of the file(s), + * but you are not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + */ + +#include "sslkey.h" + +#include +#include + +QSslKey Utils::SSLKey::load(const QByteArray &data) +{ + // try different formats + if (const QSslKey key {data, QSsl::Ec}; !key.isNull()) + return key; + return {data, QSsl::Rsa}; +} diff --git a/src/base/utils/sslkey.h b/src/base/utils/sslkey.h new file mode 100644 index 000000000..afbd9486e --- /dev/null +++ b/src/base/utils/sslkey.h @@ -0,0 +1,37 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2024 Mike Tzou (Chocobo1) + * + * This program 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 2 + * of the License, or (at your option) any later version. + * + * This program 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give permission to + * link this program with the OpenSSL project's "OpenSSL" library (or with + * modified versions of it that use the same license as the "OpenSSL" library), + * and distribute the linked executables. You must obey the GNU General Public + * License in all respects for all of the code used other than "OpenSSL". If you + * modify file(s), you may extend this exception to your version of the file(s), + * but you are not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + */ + +#pragma once + +class QByteArray; +class QSslKey; + +namespace Utils::SSLKey +{ + QSslKey load(const QByteArray &data); +} diff --git a/src/gui/optionsdialog.cpp b/src/gui/optionsdialog.cpp index b606463a2..2f669a1e1 100644 --- a/src/gui/optionsdialog.cpp +++ b/src/gui/optionsdialog.cpp @@ -61,6 +61,7 @@ #include "base/utils/os.h" #include "base/utils/password.h" #include "base/utils/random.h" +#include "base/utils/sslkey.h" #include "addnewtorrentdialog.h" #include "advancedsettings.h" #include "banlistoptionsdialog.h" @@ -1865,7 +1866,7 @@ Path OptionsDialog::getFilter() const void OptionsDialog::webUIHttpsCertChanged(const Path &path) { const auto readResult = Utils::IO::readFile(path, Utils::Net::MAX_SSL_FILE_SIZE); - const bool isCertValid = Utils::Net::isSSLCertificatesValid(readResult.value_or(QByteArray())); + const bool isCertValid = !Utils::SSLKey::load(readResult.value_or(QByteArray())).isNull(); m_ui->textWebUIHttpsCert->setSelectedPath(path); m_ui->lblSslCertStatus->setPixmap(UIThemeManager::instance()->getScaledPixmap( @@ -1875,7 +1876,7 @@ void OptionsDialog::webUIHttpsCertChanged(const Path &path) void OptionsDialog::webUIHttpsKeyChanged(const Path &path) { const auto readResult = Utils::IO::readFile(path, Utils::Net::MAX_SSL_FILE_SIZE); - const bool isKeyValid = Utils::Net::isSSLKeyValid(readResult.value_or(QByteArray())); + const bool isKeyValid = !Utils::SSLKey::load(readResult.value_or(QByteArray())).isNull(); m_ui->textWebUIHttpsKey->setSelectedPath(path); m_ui->lblSslKeyStatus->setPixmap(UIThemeManager::instance()->getScaledPixmap( diff --git a/src/webui/api/appcontroller.cpp b/src/webui/api/appcontroller.cpp index ea367cb19..8ea29d568 100644 --- a/src/webui/api/appcontroller.cpp +++ b/src/webui/api/appcontroller.cpp @@ -201,6 +201,8 @@ void AppController::preferencesAction() // Connection // Listening Port data[u"listen_port"_s] = session->port(); + data[u"ssl_enabled"_s] = session->isSSLEnabled(); + data[u"ssl_listen_port"_s] = session->sslPort(); data[u"random_port"_s] = (session->port() == 0); // deprecated data[u"upnp"_s] = Net::PortForwarder::instance()->isEnabled(); // Connections Limits @@ -654,6 +656,11 @@ void AppController::setPreferencesAction() { session->setPort(it.value().toInt()); } + // SSL Torrents + if (hasKey(u"ssl_enabled"_s)) + session->setSSLEnabled(it.value().toBool()); + if (hasKey(u"ssl_listen_port"_s)) + session->setSSLPort(it.value().toInt()); if (hasKey(u"upnp"_s)) Net::PortForwarder::instance()->setEnabled(it.value().toBool()); // Connections Limits diff --git a/src/webui/api/torrentscontroller.cpp b/src/webui/api/torrentscontroller.cpp index 8dd5c8319..44c1b719a 100644 --- a/src/webui/api/torrentscontroller.cpp +++ b/src/webui/api/torrentscontroller.cpp @@ -45,6 +45,7 @@ #include "base/bittorrent/peeraddress.h" #include "base/bittorrent/peerinfo.h" #include "base/bittorrent/session.h" +#include "base/bittorrent/sslparameters.h" #include "base/bittorrent/torrent.h" #include "base/bittorrent/torrentdescriptor.h" #include "base/bittorrent/trackerentry.h" @@ -55,6 +56,7 @@ #include "base/torrentfilter.h" #include "base/utils/datetime.h" #include "base/utils/fs.h" +#include "base/utils/sslkey.h" #include "base/utils/string.h" #include "apierror.h" #include "serialize/serialize_torrent.h" @@ -108,6 +110,9 @@ const QString KEY_PROP_SAVE_PATH = u"save_path"_s; const QString KEY_PROP_DOWNLOAD_PATH = u"download_path"_s; const QString KEY_PROP_COMMENT = u"comment"_s; const QString KEY_PROP_ISPRIVATE = u"is_private"_s; +const QString KEY_PROP_SSL_CERTIFICATE = u"ssl_certificate"_s; +const QString KEY_PROP_SSL_PRIVATEKEY = u"ssl_private_key"_s; +const QString KEY_PROP_SSL_DHPARAMS = u"ssl_dh_params"_s; // File keys const QString KEY_FILE_INDEX = u"index"_s; @@ -721,27 +726,38 @@ void TorrentsController::addAction() } } - BitTorrent::AddTorrentParams addTorrentParams; - // TODO: Check if destination actually exists - addTorrentParams.skipChecking = skipChecking; - addTorrentParams.sequential = seqDownload; - addTorrentParams.firstLastPiecePriority = firstLastPiece; - addTorrentParams.addToQueueTop = addToQueueTop; - addTorrentParams.addPaused = addPaused; - addTorrentParams.stopCondition = stopCondition; - addTorrentParams.contentLayout = contentLayout; - addTorrentParams.savePath = Path(savepath); - addTorrentParams.downloadPath = Path(downloadPath); - addTorrentParams.useDownloadPath = useDownloadPath; - addTorrentParams.category = category; - addTorrentParams.tags.insert(tags.cbegin(), tags.cend()); - addTorrentParams.name = torrentName; - addTorrentParams.uploadLimit = upLimit; - addTorrentParams.downloadLimit = dlLimit; - addTorrentParams.seedingTimeLimit = seedingTimeLimit; - addTorrentParams.inactiveSeedingTimeLimit = inactiveSeedingTimeLimit; - addTorrentParams.ratioLimit = ratioLimit; - addTorrentParams.useAutoTMM = autoTMM; + const BitTorrent::AddTorrentParams addTorrentParams + { + // TODO: Check if destination actually exists + .name = torrentName, + .category = category, + .tags = {tags.cbegin(), tags.cend()}, + .savePath = Path(savepath), + .useDownloadPath = useDownloadPath, + .downloadPath = Path(downloadPath), + .sequential = seqDownload, + .firstLastPiecePriority = firstLastPiece, + .addForced = false, + .addToQueueTop = addToQueueTop, + .addPaused = addPaused, + .stopCondition = stopCondition, + .filePaths = {}, + .filePriorities = {}, + .skipChecking = skipChecking, + .contentLayout = contentLayout, + .useAutoTMM = autoTMM, + .uploadLimit = upLimit, + .downloadLimit = dlLimit, + .seedingTimeLimit = seedingTimeLimit, + .inactiveSeedingTimeLimit = inactiveSeedingTimeLimit, + .ratioLimit = ratioLimit, + .sslParameters = + { + .certificate = QSslCertificate(params()[KEY_PROP_SSL_CERTIFICATE].toLatin1()), + .privateKey = Utils::SSLKey::load(params()[KEY_PROP_SSL_PRIVATEKEY].toLatin1()), + .dhParams = params()[KEY_PROP_SSL_DHPARAMS].toLatin1() + } + }; bool partialSuccess = false; for (QString url : asConst(urls.split(u'\n'))) @@ -1444,3 +1460,43 @@ void TorrentsController::exportAction() setResult(result.value(), u"application/x-bittorrent"_s, (id.toString() + u".torrent")); } + +void TorrentsController::SSLParametersAction() +{ + requireParams({u"hash"_s}); + + const auto id = BitTorrent::TorrentID::fromString(params()[u"hash"_s]); + const BitTorrent::Torrent *torrent = BitTorrent::Session::instance()->getTorrent(id); + if (!torrent) + throw APIError(APIErrorType::NotFound); + + const BitTorrent::SSLParameters sslParams = torrent->getSSLParameters(); + const QJsonObject ret + { + {KEY_PROP_SSL_CERTIFICATE, QString::fromLatin1(sslParams.certificate.toPem())}, + {KEY_PROP_SSL_PRIVATEKEY, QString::fromLatin1(sslParams.privateKey.toPem())}, + {KEY_PROP_SSL_DHPARAMS, QString::fromLatin1(sslParams.dhParams)} + }; + setResult(ret); +} + +void TorrentsController::setSSLParametersAction() +{ + requireParams({u"hash"_s, KEY_PROP_SSL_CERTIFICATE, KEY_PROP_SSL_PRIVATEKEY, KEY_PROP_SSL_DHPARAMS}); + + const auto id = BitTorrent::TorrentID::fromString(params()[u"hash"_s]); + BitTorrent::Torrent *const torrent = BitTorrent::Session::instance()->getTorrent(id); + if (!torrent) + throw APIError(APIErrorType::NotFound); + + const BitTorrent::SSLParameters sslParams + { + .certificate = QSslCertificate(params()[KEY_PROP_SSL_CERTIFICATE].toLatin1()), + .privateKey = Utils::SSLKey::load(params()[KEY_PROP_SSL_PRIVATEKEY].toLatin1()), + .dhParams = params()[KEY_PROP_SSL_DHPARAMS].toLatin1() + }; + if (!sslParams.isValid()) + throw APIError(APIErrorType::BadData); + + torrent->setSSLParameters(sslParams); +} diff --git a/src/webui/api/torrentscontroller.h b/src/webui/api/torrentscontroller.h index e961505f8..03cc286fc 100644 --- a/src/webui/api/torrentscontroller.h +++ b/src/webui/api/torrentscontroller.h @@ -89,4 +89,6 @@ private slots: void renameFileAction(); void renameFolderAction(); void exportAction(); + void SSLParametersAction(); + void setSSLParametersAction(); }; diff --git a/src/webui/webapplication.h b/src/webui/webapplication.h index 57651e9e7..a6f2ede6a 100644 --- a/src/webui/webapplication.h +++ b/src/webui/webapplication.h @@ -204,6 +204,7 @@ private: {{u"torrents"_s, u"setLocation"_s}, Http::METHOD_POST}, {{u"torrents"_s, u"setSavePath"_s}, Http::METHOD_POST}, {{u"torrents"_s, u"setShareLimits"_s}, Http::METHOD_POST}, + {{u"torrents"_s, u"setSSLParameters"_s}, Http::METHOD_POST}, {{u"torrents"_s, u"setSuperSeeding"_s}, Http::METHOD_POST}, {{u"torrents"_s, u"setUploadLimit"_s}, Http::METHOD_POST}, {{u"torrents"_s, u"toggleFirstLastPiecePrio"_s}, Http::METHOD_POST},