From 48d358872f630ef944bbfd27eefa71364e97a7c3 Mon Sep 17 00:00:00 2001 From: Chocobo1 Date: Thu, 17 Jan 2019 09:20:27 +0800 Subject: [PATCH 1/3] Cleanup header inclusion --- src/app/main.cpp | 1 + src/base/preferences.cpp | 1 - src/base/preferences.h | 4 ---- src/base/utils/net.cpp | 1 - src/base/utils/net.h | 2 +- 5 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/app/main.cpp b/src/app/main.cpp index caa5d1211..01f4bda4d 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -47,6 +47,7 @@ #include #include #include +#include #ifdef QBT_STATIC_QT #include diff --git a/src/base/preferences.cpp b/src/base/preferences.cpp index 7758d483b..1f42496d1 100644 --- a/src/base/preferences.cpp +++ b/src/base/preferences.cpp @@ -50,7 +50,6 @@ #endif #include "global.h" -#include "logger.h" #include "settingsstorage.h" #include "utils/fs.h" #include "utils/misc.h" diff --git a/src/base/preferences.h b/src/base/preferences.h index ba842f119..fdddef15f 100644 --- a/src/base/preferences.h +++ b/src/base/preferences.h @@ -31,18 +31,14 @@ #define PREFERENCES_H #include -#include #include #include -#include #include #include #include -#include #include #include "base/utils/net.h" -#include "types.h" enum SchedulerDays { diff --git a/src/base/utils/net.cpp b/src/base/utils/net.cpp index 06fe9b375..3a6ab3f5f 100644 --- a/src/base/utils/net.cpp +++ b/src/base/utils/net.cpp @@ -28,7 +28,6 @@ #include "net.h" -#include #include namespace Utils diff --git a/src/base/utils/net.h b/src/base/utils/net.h index cc440326b..62572f5f0 100644 --- a/src/base/utils/net.h +++ b/src/base/utils/net.h @@ -29,10 +29,10 @@ #ifndef BASE_UTILS_NET_H #define BASE_UTILS_NET_H +#include #include #include -class QHostAddress; class QString; namespace Utils From 5cdb3b6a2d1b9fc3c3b36db8a28d48a0428b1cdd Mon Sep 17 00:00:00 2001 From: Chocobo1 Date: Thu, 17 Jan 2019 09:42:01 +0800 Subject: [PATCH 2/3] Load WebUI certificate & key from file path This allow users to update certificate & key more easily, i.e. without the need to import them into qbt. Closes #6675, #7547, #8315, #8564. --- src/base/http/server.cpp | 100 ++++++------- src/base/http/server.h | 17 +-- src/base/preferences.cpp | 16 +-- src/base/preferences.h | 8 +- src/base/utils/net.cpp | 29 ++++ src/base/utils/net.h | 8 ++ src/gui/optionsdialog.cpp | 133 ++++++++---------- src/gui/optionsdialog.h | 18 +-- src/gui/optionsdialog.ui | 78 ++-------- src/webui/api/appcontroller.cpp | 25 +--- src/webui/webui.cpp | 19 ++- .../www/private/preferences_content.html | 20 +-- 12 files changed, 203 insertions(+), 268 deletions(-) diff --git a/src/base/http/server.cpp b/src/base/http/server.cpp index 778b94b39..cf83b0eb0 100644 --- a/src/base/http/server.cpp +++ b/src/base/http/server.cpp @@ -30,55 +30,69 @@ #include "server.h" +#include + #include #include +#include +#include #include #include -#ifndef QT_NO_OPENSSL -#include -#else -#include -#endif +#include "base/utils/net.h" #include "connection.h" -static const int KEEP_ALIVE_DURATION = 7 * 1000; // milliseconds -static const int CONNECTIONS_LIMIT = 500; -static const int CONNECTIONS_SCAN_INTERVAL = 2; // seconds +namespace +{ + const int KEEP_ALIVE_DURATION = 7 * 1000; // milliseconds + const int CONNECTIONS_LIMIT = 500; + const int CONNECTIONS_SCAN_INTERVAL = 2; // seconds + + QList safeCipherList() + { + const QStringList badCiphers = {"idea", "rc4"}; + const QList allCiphers = QSslSocket::supportedCiphers(); + QList safeCiphers; + for (const QSslCipher &cipher : allCiphers) { + bool isSafe = true; + for (const QString &badCipher : badCiphers) { + if (cipher.name().contains(badCipher, Qt::CaseInsensitive)) { + isSafe = false; + break; + } + } + + if (isSafe) + safeCiphers += cipher; + } + + return safeCiphers; + } +} using namespace Http; Server::Server(IRequestHandler *requestHandler, QObject *parent) : QTcpServer(parent) , m_requestHandler(requestHandler) -#ifndef QT_NO_OPENSSL , m_https(false) -#endif { setProxy(QNetworkProxy::NoProxy); -#ifndef QT_NO_OPENSSL QSslSocket::setDefaultCiphers(safeCipherList()); -#endif QTimer *dropConnectionTimer = new QTimer(this); connect(dropConnectionTimer, &QTimer::timeout, this, &Server::dropTimedOutConnection); dropConnectionTimer->start(CONNECTIONS_SCAN_INTERVAL * 1000); } -Server::~Server() -{ -} - void Server::incomingConnection(qintptr socketDescriptor) { if (m_connections.size() >= CONNECTIONS_LIMIT) return; QTcpSocket *serverSocket; -#ifndef QT_NO_OPENSSL if (m_https) serverSocket = new QSslSocket(this); else -#endif serverSocket = new QTcpSocket(this); if (!serverSocket->setSocketDescriptor(socketDescriptor)) { @@ -86,7 +100,6 @@ void Server::incomingConnection(qintptr socketDescriptor) return; } -#ifndef QT_NO_OPENSSL if (m_https) { static_cast(serverSocket)->setProtocol(QSsl::SecureProtocols); static_cast(serverSocket)->setPrivateKey(m_key); @@ -94,7 +107,6 @@ void Server::incomingConnection(qintptr socketDescriptor) static_cast(serverSocket)->setPeerVerifyMode(QSslSocket::VerifyNone); static_cast(serverSocket)->startServerEncryption(); } -#endif Connection *c = new Connection(serverSocket, m_requestHandler, this); m_connections.append(c); @@ -112,26 +124,20 @@ void Server::dropTimedOutConnection() } } -#ifndef QT_NO_OPENSSL -bool Server::setupHttps(const QByteArray &certificates, const QByteArray &key) +bool Server::setupHttps(const QByteArray &certificates, const QByteArray &privateKey) { - QSslKey sslKey(key, QSsl::Rsa); - if (sslKey.isNull()) - sslKey = QSslKey(key, QSsl::Ec); - - const QList certs = QSslCertificate::fromData(certificates); - const bool areCertsValid = !certs.empty() && std::all_of(certs.begin(), certs.end(), [](const QSslCertificate &c) { return !c.isNull(); }); - - if (!sslKey.isNull() && areCertsValid) { - m_key = sslKey; - m_certificates = certs; - m_https = true; - return true; - } - else { + const QList certs {Utils::Net::loadSSLCertificate(certificates)}; + const QSslKey key {Utils::Net::loadSSLKey(privateKey)}; + + if (certs.isEmpty() || key.isNull()) { disableHttps(); return false; } + + m_key = key; + m_certificates = certs; + m_https = true; + return true; } void Server::disableHttps() @@ -140,25 +146,3 @@ void Server::disableHttps() m_certificates.clear(); m_key.clear(); } - -QList Server::safeCipherList() const -{ - const QStringList badCiphers = {"idea", "rc4"}; - const QList allCiphers = QSslSocket::supportedCiphers(); - QList safeCiphers; - for (const QSslCipher &cipher : allCiphers) { - bool isSafe = true; - for (const QString &badCipher : badCiphers) { - if (cipher.name().contains(badCipher, Qt::CaseInsensitive)) { - isSafe = false; - break; - } - } - - if (isSafe) - safeCiphers += cipher; - } - - return safeCiphers; -} -#endif // QT_NO_OPENSSL diff --git a/src/base/http/server.h b/src/base/http/server.h index 91dc26dab..2f13509fe 100644 --- a/src/base/http/server.h +++ b/src/base/http/server.h @@ -31,13 +31,9 @@ #ifndef HTTP_SERVER_H #define HTTP_SERVER_H -#include - -#ifndef QT_NO_OPENSSL #include -#include #include -#endif +#include namespace Http { @@ -50,13 +46,10 @@ namespace Http Q_DISABLE_COPY(Server) public: - Server(IRequestHandler *requestHandler, QObject *parent = nullptr); - ~Server(); + explicit Server(IRequestHandler *requestHandler, QObject *parent = nullptr); -#ifndef QT_NO_OPENSSL - bool setupHttps(const QByteArray &certificates, const QByteArray &key); + bool setupHttps(const QByteArray &certificates, const QByteArray &privateKey); void disableHttps(); -#endif private slots: void dropTimedOutConnection(); @@ -67,13 +60,9 @@ namespace Http IRequestHandler *m_requestHandler; QList m_connections; // for tracking persistent connections -#ifndef QT_NO_OPENSSL - QList safeCipherList() const; - bool m_https; QList m_certificates; QSslKey m_key; -#endif }; } diff --git a/src/base/preferences.cpp b/src/base/preferences.cpp index 1f42496d1..b07aad832 100644 --- a/src/base/preferences.cpp +++ b/src/base/preferences.cpp @@ -633,24 +633,24 @@ void Preferences::setWebUiHttpsEnabled(bool enabled) setValue("Preferences/WebUI/HTTPS/Enabled", enabled); } -QByteArray Preferences::getWebUiHttpsCertificate() const +QString Preferences::getWebUIHttpsCertificatePath() const { - return value("Preferences/WebUI/HTTPS/Certificate").toByteArray(); + return value("Preferences/WebUI/HTTPS/CertificatePath").toString(); } -void Preferences::setWebUiHttpsCertificate(const QByteArray &data) +void Preferences::setWebUIHttpsCertificatePath(const QString &path) { - setValue("Preferences/WebUI/HTTPS/Certificate", data); + setValue("Preferences/WebUI/HTTPS/CertificatePath", path); } -QByteArray Preferences::getWebUiHttpsKey() const +QString Preferences::getWebUIHttpsKeyPath() const { - return value("Preferences/WebUI/HTTPS/Key").toByteArray(); + return value("Preferences/WebUI/HTTPS/KeyPath").toString(); } -void Preferences::setWebUiHttpsKey(const QByteArray &data) +void Preferences::setWebUIHttpsKeyPath(const QString &path) { - setValue("Preferences/WebUI/HTTPS/Key", data); + setValue("Preferences/WebUI/HTTPS/KeyPath", path); } bool Preferences::isAltWebUiEnabled() const diff --git a/src/base/preferences.h b/src/base/preferences.h index fdddef15f..d80eaf19b 100644 --- a/src/base/preferences.h +++ b/src/base/preferences.h @@ -203,10 +203,10 @@ public: // HTTPS bool isWebUiHttpsEnabled() const; void setWebUiHttpsEnabled(bool enabled); - QByteArray getWebUiHttpsCertificate() const; - void setWebUiHttpsCertificate(const QByteArray &data); - QByteArray getWebUiHttpsKey() const; - void setWebUiHttpsKey(const QByteArray &data); + QString getWebUIHttpsCertificatePath() const; + void setWebUIHttpsCertificatePath(const QString &path); + QString getWebUIHttpsKeyPath() const; + void setWebUIHttpsKeyPath(const QString &path); bool isAltWebUiEnabled() const; void setAltWebUiEnabled(bool enabled); QString getWebUiRootFolder() const; diff --git a/src/base/utils/net.cpp b/src/base/utils/net.cpp index 3a6ab3f5f..72811edc8 100644 --- a/src/base/utils/net.cpp +++ b/src/base/utils/net.cpp @@ -28,6 +28,8 @@ #include "net.h" +#include +#include #include namespace Utils @@ -88,5 +90,32 @@ namespace Utils { return subnet.first.toString() + '/' + QString::number(subnet.second); } + + QList loadSSLCertificate(const QByteArray &data) + { + const QList certs {QSslCertificate::fromData(data)}; + if (std::any_of(certs.cbegin(), certs.cend(), [](const QSslCertificate &c) { return c.isNull(); })) + return {}; + return certs; + } + + bool isSSLCertificatesValid(const QByteArray &data) + { + return !loadSSLCertificate(data).isEmpty(); + } + + QSslKey loadSSLKey(const QByteArray &data) + { + // try different formats + QSslKey key {data, QSsl::Rsa}; + if (!key.isNull()) + return key; + return QSslKey(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 62572f5f0..c5f59e828 100644 --- a/src/base/utils/net.h +++ b/src/base/utils/net.h @@ -33,6 +33,8 @@ #include #include +class QSslCertificate; +class QSslKey; class QString; namespace Utils @@ -47,6 +49,12 @@ namespace Utils bool isLoopbackAddress(const QHostAddress &addr); bool isIPInRange(const QHostAddress &addr, const QList &subnets); QString subnetToString(const Subnet &subnet); + + 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/gui/optionsdialog.cpp b/src/gui/optionsdialog.cpp index 577d4cd19..c47204314 100644 --- a/src/gui/optionsdialog.cpp +++ b/src/gui/optionsdialog.cpp @@ -42,13 +42,9 @@ #include #include -#ifndef QT_NO_OPENSSL -#include -#include -#endif - #include "base/bittorrent/session.h" #include "base/global.h" +#include "base/http/server.h" #include "base/net/dnsupdater.h" #include "base/net/portforwarder.h" #include "base/net/proxyconfigurationmanager.h" @@ -59,6 +55,7 @@ #include "base/torrentfileguard.h" #include "base/unicodestrings.h" #include "base/utils/fs.h" +#include "base/utils/net.h" #include "base/utils/password.h" #include "base/utils/random.h" #include "addnewtorrentdialog.h" @@ -196,11 +193,6 @@ OptionsDialog::OptionsDialog(QWidget *parent) } #endif -#if defined(QT_NO_OPENSSL) - m_ui->checkWebUiHttps->setVisible(false); - m_ui->checkSmtpSSL->setVisible(false); -#endif - #ifndef Q_OS_WIN m_ui->checkStartup->setVisible(false); #endif @@ -390,14 +382,23 @@ OptionsDialog::OptionsDialog(QWidget *parent) #ifndef DISABLE_WEBUI // Web UI tab + m_ui->textWebUIHttpsCert->setMode(FileSystemPathEdit::Mode::FileOpen); + m_ui->textWebUIHttpsCert->setFileNameFilter(tr("Certificate") + QLatin1String(" (*.cer *.crt *.pem)")); + m_ui->textWebUIHttpsCert->setDialogCaption(tr("Select certificate")); + m_ui->textWebUIHttpsKey->setMode(FileSystemPathEdit::Mode::FileOpen); + m_ui->textWebUIHttpsKey->setFileNameFilter(tr("Private key") + QLatin1String(" (*.key *.pem)")); + m_ui->textWebUIHttpsKey->setDialogCaption(tr("Select private key")); + connect(m_ui->textServerDomains, &QLineEdit::textChanged, this, &ThisType::enableApplyButton); connect(m_ui->checkWebUi, &QGroupBox::toggled, this, &ThisType::enableApplyButton); connect(m_ui->textWebUiAddress, &QLineEdit::textChanged, this, &ThisType::enableApplyButton); connect(m_ui->spinWebUiPort, qSpinBoxValueChanged, this, &ThisType::enableApplyButton); connect(m_ui->checkWebUIUPnP, &QAbstractButton::toggled, this, &ThisType::enableApplyButton); connect(m_ui->checkWebUiHttps, &QGroupBox::toggled, this, &ThisType::enableApplyButton); - connect(m_ui->btnWebUiKey, &QAbstractButton::clicked, this, &ThisType::enableApplyButton); - connect(m_ui->btnWebUiCrt, &QAbstractButton::clicked, this, &ThisType::enableApplyButton); + connect(m_ui->textWebUIHttpsCert, &FileSystemPathLineEdit::selectedPathChanged, this, &ThisType::enableApplyButton); + connect(m_ui->textWebUIHttpsCert, &FileSystemPathLineEdit::selectedPathChanged, this, [this](const QString &s) { webUIHttpsCertChanged(s, ShowError::Show); }); + connect(m_ui->textWebUIHttpsKey, &FileSystemPathLineEdit::selectedPathChanged, this, &ThisType::enableApplyButton); + connect(m_ui->textWebUIHttpsKey, &FileSystemPathLineEdit::selectedPathChanged, this, [this](const QString &s) { webUIHttpsKeyChanged(s, ShowError::Show); }); connect(m_ui->textWebUiUsername, &QLineEdit::textChanged, this, &ThisType::enableApplyButton); connect(m_ui->textWebUiPassword, &QLineEdit::textChanged, this, &ThisType::enableApplyButton); connect(m_ui->checkBypassLocalAuth, &QAbstractButton::toggled, this, &ThisType::enableApplyButton); @@ -722,11 +723,8 @@ void OptionsDialog::saveOptions() pref->setWebUiPort(m_ui->spinWebUiPort->value()); pref->setUPnPForWebUIPort(m_ui->checkWebUIUPnP->isChecked()); pref->setWebUiHttpsEnabled(m_ui->checkWebUiHttps->isChecked()); - // HTTPS - if (m_ui->checkWebUiHttps->isChecked()) { - pref->setWebUiHttpsCertificate(m_sslCert); - pref->setWebUiHttpsKey(m_sslKey); - } + pref->setWebUIHttpsCertificatePath(m_ui->textWebUIHttpsCert->selectedPath()); + pref->setWebUIHttpsKeyPath(m_ui->textWebUIHttpsKey->selectedPath()); // Authentication pref->setWebUiUsername(webUiUsername()); if (!webUiPassword().isEmpty()) @@ -1089,8 +1087,8 @@ void OptionsDialog::loadOptions() m_ui->spinWebUiPort->setValue(pref->getWebUiPort()); m_ui->checkWebUIUPnP->setChecked(pref->useUPnPForWebUIPort()); m_ui->checkWebUiHttps->setChecked(pref->isWebUiHttpsEnabled()); - setSslCertificate(pref->getWebUiHttpsCertificate()); - setSslKey(pref->getWebUiHttpsKey()); + webUIHttpsCertChanged(pref->getWebUIHttpsCertificatePath(), ShowError::NotShow); + webUIHttpsKeyChanged(pref->getWebUIHttpsKeyPath(), ShowError::NotShow); m_ui->textWebUiUsername->setText(pref->getWebUiUsername()); m_ui->checkBypassLocalAuth->setChecked(!pref->isWebUiLocalAuthEnabled()); m_ui->checkBypassAuthSubnetWhitelist->setChecked(pref->isWebUiAuthSubnetWhitelistEnabled()); @@ -1559,39 +1557,57 @@ QString OptionsDialog::webUiPassword() const return m_ui->textWebUiPassword->text(); } -void OptionsDialog::showConnectionTab() +void OptionsDialog::webUIHttpsCertChanged(const QString &path, const ShowError showError) { - m_ui->tabSelection->setCurrentRow(TAB_CONNECTION); -} + m_ui->textWebUIHttpsCert->setSelectedPath(path); + m_ui->lblSslCertStatus->setPixmap(Utils::Gui::scaledPixmapSvg(":/icons/qbt-theme/security-low.svg", this, 24)); -void OptionsDialog::on_btnWebUiCrt_clicked() -{ - const QString filename = QFileDialog::getOpenFileName(this, tr("Import SSL certificate"), QString(), tr("SSL Certificate") + QLatin1String(" (*.crt *.pem)")); - if (filename.isEmpty()) + if (path.isEmpty()) return; - QFile cert(filename); - if (!cert.open(QIODevice::ReadOnly)) + QFile file(path); + if (!file.open(QIODevice::ReadOnly)) { + if (showError == ShowError::Show) + QMessageBox::warning(this, tr("Invalid path"), file.errorString()); return; + } - bool success = setSslCertificate(cert.read(1024 * 1024)); - if (!success) - QMessageBox::warning(this, tr("Invalid certificate"), tr("This is not a valid SSL certificate.")); + if (!Utils::Net::isSSLCertificatesValid(file.read(Utils::Net::MAX_SSL_FILE_SIZE))) { + if (showError == ShowError::Show) + QMessageBox::warning(this, tr("Invalid certificate"), tr("This is not a valid SSL certificate.")); + return; + } + + m_ui->lblSslCertStatus->setPixmap(Utils::Gui::scaledPixmapSvg(":/icons/qbt-theme/security-high.svg", this, 24)); } -void OptionsDialog::on_btnWebUiKey_clicked() +void OptionsDialog::webUIHttpsKeyChanged(const QString &path, const ShowError showError) { - const QString filename = QFileDialog::getOpenFileName(this, tr("Import SSL key"), QString(), tr("SSL key") + QLatin1String(" (*.key *.pem)")); - if (filename.isEmpty()) + m_ui->textWebUIHttpsKey->setSelectedPath(path); + m_ui->lblSslKeyStatus->setPixmap(Utils::Gui::scaledPixmapSvg(":/icons/qbt-theme/security-low.svg", this, 24)); + + if (path.isEmpty()) return; - QFile key(filename); - if (!key.open(QIODevice::ReadOnly)) + QFile file(path); + if (!file.open(QIODevice::ReadOnly)) { + if (showError == ShowError::Show) + QMessageBox::warning(this, tr("Invalid path"), file.errorString()); return; + } + + if (!Utils::Net::isSSLKeyValid(file.read(Utils::Net::MAX_SSL_FILE_SIZE))) { + if (showError == ShowError::Show) + QMessageBox::warning(this, tr("Invalid key"), tr("This is not a valid SSL key.")); + return; + } + + m_ui->lblSslKeyStatus->setPixmap(Utils::Gui::scaledPixmapSvg(":/icons/qbt-theme/security-high.svg", this, 24)); +} - bool success = setSslKey(key.read(1024 * 1024)); - if (!success) - QMessageBox::warning(this, tr("Invalid key"), tr("This is not a valid SSL key.")); +void OptionsDialog::showConnectionTab() +{ + m_ui->tabSelection->setCurrentRow(TAB_CONNECTION); } void OptionsDialog::on_registerDNSBtn_clicked() @@ -1698,45 +1714,6 @@ QString OptionsDialog::languageToLocalizedString(const QLocale &locale) } } -bool OptionsDialog::setSslKey(const QByteArray &key) -{ -#ifndef QT_NO_OPENSSL - // try different formats - const bool isKeyValid = (!QSslKey(key, QSsl::Rsa).isNull() || !QSslKey(key, QSsl::Ec).isNull()); - if (isKeyValid) { - m_ui->lblSslKeyStatus->setPixmap(Utils::Gui::scaledPixmapSvg(":/icons/qbt-theme/security-high.svg", this, 24)); - m_sslKey = key; - } - else { - m_ui->lblSslKeyStatus->setPixmap(Utils::Gui::scaledPixmapSvg(":/icons/qbt-theme/security-low.svg", this, 24)); - m_sslKey.clear(); - } - return isKeyValid; -#else - Q_UNUSED(key); - return false; -#endif -} - -bool OptionsDialog::setSslCertificate(const QByteArray &cert) -{ -#ifndef QT_NO_OPENSSL - const bool isCertValid = !QSslCertificate(cert).isNull(); - if (isCertValid) { - m_ui->lblSslCertStatus->setPixmap(Utils::Gui::scaledPixmapSvg(":/icons/qbt-theme/security-high.svg", this, 24)); - m_sslCert = cert; - } - else { - m_ui->lblSslCertStatus->setPixmap(Utils::Gui::scaledPixmapSvg(":/icons/qbt-theme/security-low.svg", this, 24)); - m_sslCert.clear(); - } - return isCertValid; -#else - Q_UNUSED(cert); - return false; -#endif -} - bool OptionsDialog::schedTimesOk() { if (m_ui->timeEditScheduleFrom->time() == m_ui->timeEditScheduleTo->time()) { diff --git a/src/gui/optionsdialog.h b/src/gui/optionsdialog.h index 7ab204c6e..4e3a2d841 100644 --- a/src/gui/optionsdialog.h +++ b/src/gui/optionsdialog.h @@ -60,7 +60,6 @@ class OptionsDialog : public QDialog Q_OBJECT using ThisType = OptionsDialog; -private: enum Tabs { TAB_UI, @@ -73,6 +72,12 @@ private: TAB_ADVANCED }; + enum class ShowError + { + NotShow, + Show + }; + public: // Constructor / Destructor OptionsDialog(QWidget *parent = nullptr); @@ -102,10 +107,10 @@ private slots: void on_randomButton_clicked(); void on_addScanFolderButton_clicked(); void on_removeScanFolderButton_clicked(); - void on_btnWebUiCrt_clicked(); - void on_btnWebUiKey_clicked(); void on_registerDNSBtn_clicked(); void setLocale(const QString &localeStr); + void webUIHttpsCertChanged(const QString &path, ShowError showError); + void webUIHttpsKeyChanged(const QString &path, ShowError showError); private: // Methods @@ -164,17 +169,14 @@ private: int getMaxActiveDownloads() const; int getMaxActiveUploads() const; int getMaxActiveTorrents() const; + // WebUI bool isWebUiEnabled() const; QString webUiUsername() const; QString webUiPassword() const; - // WebUI SSL Cert / key - bool setSslKey(const QByteArray &key); - bool setSslCertificate(const QByteArray &cert); - bool schedTimesOk(); bool webUIAuthenticationOk(); bool isAlternativeWebUIPathValid(); - QByteArray m_sslCert, m_sslKey; + bool schedTimesOk(); Ui::OptionsDialog *m_ui; QAbstractButton *m_applyButton; diff --git a/src/gui/optionsdialog.ui b/src/gui/optionsdialog.ui index 8d4fb7c88..4a6ccbdb7 100644 --- a/src/gui/optionsdialog.ui +++ b/src/gui/optionsdialog.ui @@ -2966,80 +2966,26 @@ Specify an IPv4 or IPv6 address. You can specify "0.0.0.0" for any IPv false - - + + + + Key: + + Certificate: - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - Import SSL Certificate - - - - - - - Qt::Horizontal - - - - 138 - 28 - - - - - + + - - - - Key: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - Import SSL Key - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - @@ -3050,6 +2996,12 @@ Specify an IPv4 or IPv6 address. You can specify "0.0.0.0" for any IPv + + + + + + @@ -3464,8 +3416,6 @@ Use ';' to split multiple entries. Can use wildcard '*'. comboRatioLimitAct checkWebUIUPnP checkWebUiHttps - btnWebUiCrt - btnWebUiKey checkBypassLocalAuth checkBypassAuthSubnetWhitelist IPSubnetWhitelistButton diff --git a/src/webui/api/appcontroller.cpp b/src/webui/api/appcontroller.cpp index bc3539035..ad96ebb69 100644 --- a/src/webui/api/appcontroller.cpp +++ b/src/webui/api/appcontroller.cpp @@ -39,11 +39,6 @@ #include #include -#ifndef QT_NO_OPENSSL -#include -#include -#endif - #include "base/bittorrent/session.h" #include "base/global.h" #include "base/net/portforwarder.h" @@ -210,8 +205,8 @@ void AppController::preferencesAction() data["web_ui_port"] = pref->getWebUiPort(); data["web_ui_upnp"] = pref->useUPnPForWebUIPort(); data["use_https"] = pref->isWebUiHttpsEnabled(); - data["ssl_key"] = QString::fromLatin1(pref->getWebUiHttpsKey()); - data["ssl_cert"] = QString::fromLatin1(pref->getWebUiHttpsCertificate()); + data["web_ui_https_cert_path"] = pref->getWebUIHttpsCertificatePath(); + data["web_ui_https_key_path"] = pref->getWebUIHttpsKeyPath(); // Authentication data["web_ui_username"] = pref->getWebUiUsername(); data["bypass_local_auth"] = !pref->isWebUiLocalAuthEnabled(); @@ -505,18 +500,10 @@ void AppController::setPreferencesAction() pref->setUPnPForWebUIPort(m["web_ui_upnp"].toBool()); if (m.contains("use_https")) pref->setWebUiHttpsEnabled(m["use_https"].toBool()); -#ifndef QT_NO_OPENSSL - if (m.contains("ssl_key")) { - QByteArray raw_key = m["ssl_key"].toString().toLatin1(); - if (!QSslKey(raw_key, QSsl::Rsa).isNull()) - pref->setWebUiHttpsKey(raw_key); - } - if (m.contains("ssl_cert")) { - QByteArray raw_cert = m["ssl_cert"].toString().toLatin1(); - if (!QSslCertificate(raw_cert).isNull()) - pref->setWebUiHttpsCertificate(raw_cert); - } -#endif + if ((it = m.find(QLatin1String("web_ui_https_cert_path"))) != m.constEnd()) + pref->setWebUIHttpsCertificatePath(it.value().toString()); + if ((it = m.find(QLatin1String("web_ui_https_key_path"))) != m.constEnd()) + pref->setWebUIHttpsKeyPath(it.value().toString()); // Authentication if (m.contains("web_ui_username")) pref->setWebUiUsername(m["web_ui_username"].toString()); diff --git a/src/webui/webui.cpp b/src/webui/webui.cpp index 6b0e49629..038c975d6 100644 --- a/src/webui/webui.cpp +++ b/src/webui/webui.cpp @@ -28,11 +28,14 @@ #include "webui.h" +#include + #include "base/http/server.h" #include "base/logger.h" #include "base/net/dnsupdater.h" #include "base/net/portforwarder.h" #include "base/preferences.h" +#include "base/utils/net.h" #include "webapplication.h" WebUI::WebUI() @@ -77,11 +80,18 @@ void WebUI::configure() m_httpServer->close(); } -#ifndef QT_NO_OPENSSL if (pref->isWebUiHttpsEnabled()) { - const QByteArray certs = pref->getWebUiHttpsCertificate(); - const QByteArray key = pref->getWebUiHttpsKey(); - bool success = m_httpServer->setupHttps(certs, key); + const auto readData = [](const QString &path) -> QByteArray + { + QFile file(path); + if (!file.open(QIODevice::ReadOnly)) + return {}; + return file.read(Utils::Net::MAX_SSL_FILE_SIZE); + }; + const QByteArray cert = readData(pref->getWebUIHttpsCertificatePath()); + const QByteArray key = readData(pref->getWebUIHttpsKeyPath()); + + const bool success = m_httpServer->setupHttps(cert, key); if (success) logger->addMessage(tr("Web UI: HTTPS setup successful")); else @@ -90,7 +100,6 @@ void WebUI::configure() else { m_httpServer->disableHttps(); } -#endif if (!m_httpServer->isListening()) { const auto address = (serverAddressString == "*" || serverAddressString.isEmpty()) diff --git a/src/webui/www/private/preferences_content.html b/src/webui/www/private/preferences_content.html index 5a4896152..e709a26a2 100644 --- a/src/webui/www/private/preferences_content.html +++ b/src/webui/www/private/preferences_content.html @@ -682,18 +682,18 @@
- + - +
- + - +
@@ -1043,8 +1043,8 @@ // Web UI tab var updateHttpsSettings = function() { var isUseHttpsEnabled = $('use_https_checkbox').getProperty('checked'); - $('ssl_key_textarea').setProperty('disabled', !isUseHttpsEnabled); - $('ssl_cert_textarea').setProperty('disabled', !isUseHttpsEnabled); + $('ssl_cert_text').setProperty('disabled', !isUseHttpsEnabled); + $('ssl_key_text').setProperty('disabled', !isUseHttpsEnabled); }; var updateBypasssAuthSettings = function() { @@ -1330,8 +1330,8 @@ $('webui_port_value').setProperty('value', pref.web_ui_port); $('webui_upnp_checkbox').setProperty('checked', pref.web_ui_upnp); $('use_https_checkbox').setProperty('checked', pref.use_https); - $('ssl_key_textarea').setProperty('value', pref.ssl_key); - $('ssl_cert_textarea').setProperty('value', pref.ssl_cert); + $('ssl_cert_text').setProperty('value', pref.web_ui_https_cert_path); + $('ssl_key_text').setProperty('value', pref.web_ui_https_key_path); updateHttpsSettings(); // Authentication @@ -1646,8 +1646,8 @@ settings.set('web_ui_port', web_ui_port); settings.set('web_ui_upnp', $('webui_upnp_checkbox').getProperty('checked')); settings.set('use_https', $('use_https_checkbox').getProperty('checked')); - settings.set('ssl_key', $('ssl_key_textarea').getProperty('value')); - settings.set('ssl_cert', $('ssl_cert_textarea').getProperty('value')); + settings.set('web_ui_https_cert_path', $('ssl_cert_text').getProperty('value')); + settings.set('web_ui_https_key_path', $('ssl_key_text').getProperty('value')); // Authentication var web_ui_username = $('webui_username_text').getProperty('value'); From 52de31ac79787450888f3c7886b8241f746db5da Mon Sep 17 00:00:00 2001 From: Chocobo1 Date: Mon, 28 Jan 2019 15:36:19 +0800 Subject: [PATCH 3/3] Add migration code for WebUI https related change --- src/app/CMakeLists.txt | 1 + src/app/app.pri | 9 ++--- src/app/upgrade.cpp | 84 ++++++++++++++++++++++++++++++++++++++++++ src/app/upgrade.h | 7 +--- 4 files changed, 90 insertions(+), 11 deletions(-) create mode 100644 src/app/upgrade.cpp diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index 4079dfe93..45dc52ae4 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -7,6 +7,7 @@ application.cpp cmdoptions.cpp filelogger.cpp main.cpp +upgrade.cpp ) target_include_directories(qBittorrent PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) diff --git a/src/app/app.pri b/src/app/app.pri index bd9b03d45..7473ef625 100644 --- a/src/app/app.pri +++ b/src/app/app.pri @@ -17,13 +17,15 @@ usesystemqtsingleapplication { HEADERS += \ $$PWD/application.h \ $$PWD/cmdoptions.h \ - $$PWD/filelogger.h + $$PWD/filelogger.h \ + $$PWD/upgrade.h SOURCES += \ $$PWD/application.cpp \ $$PWD/cmdoptions.cpp \ $$PWD/filelogger.cpp \ - $$PWD/main.cpp + $$PWD/main.cpp \ + $$PWD/upgrade.cpp stacktrace { unix { @@ -37,6 +39,3 @@ stacktrace { } } } - -# upgrade code -HEADERS += $$PWD/upgrade.h diff --git a/src/app/upgrade.cpp b/src/app/upgrade.cpp new file mode 100644 index 000000000..99bcde1a0 --- /dev/null +++ b/src/app/upgrade.cpp @@ -0,0 +1,84 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2019 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 "upgrade.h" + +#include + +#include "base/logger.h" +#include "base/profile.h" +#include "base/settingsstorage.h" +#include "base/utils/fs.h" + +namespace +{ + void exportWebUIHttpsFiles() + { + const auto migrate = [](const QString &oldKey, const QString &newKey, const QString &savePath) { + SettingsStorage *settingsStorage {SettingsStorage::instance()}; + const QByteArray oldData {settingsStorage->loadValue(oldKey).toByteArray()}; + const QString newData {settingsStorage->loadValue(newKey).toString()}; + const QString errorMsgFormat {QObject::tr("Migrate preferences failed: WebUI https, file: \"%1\", error: \"%2\"")}; + + if (!newData.isEmpty() || oldData.isEmpty()) + return; + + QFile file(savePath); + if (!file.open(QIODevice::WriteOnly)) { + LogMsg(errorMsgFormat.arg(savePath, file.errorString()) , Log::WARNING); + return; + } + if (file.write(oldData) != oldData.size()) { + file.close(); + Utils::Fs::forceRemove(savePath); + LogMsg(errorMsgFormat.arg(savePath, QLatin1String("Write incomplete.")) , Log::WARNING); + return; + } + + settingsStorage->storeValue(newKey, savePath); + settingsStorage->removeValue(oldKey); + + LogMsg(QObject::tr("Migrated preferences: WebUI https, exported data to file: \"%1\"").arg(savePath) + , Log::INFO); + }; + + const QString configPath {specialFolderLocation(SpecialFolder::Config)}; + migrate(QLatin1String("Preferences/WebUI/HTTPS/Certificate") + , QLatin1String("Preferences/WebUI/HTTPS/CertificatePath") + , Utils::Fs::toNativePath(configPath + QLatin1String("WebUICertificate.crt"))); + migrate(QLatin1String("Preferences/WebUI/HTTPS/Key") + , QLatin1String("Preferences/WebUI/HTTPS/KeyPath") + , Utils::Fs::toNativePath(configPath + QLatin1String("WebUIPrivateKey.pem"))); + } +} + +bool upgrade(const bool /*ask*/) +{ + exportWebUIHttpsFiles(); + return true; +} diff --git a/src/app/upgrade.h b/src/app/upgrade.h index 570bb2651..28c336fd2 100644 --- a/src/app/upgrade.h +++ b/src/app/upgrade.h @@ -28,9 +28,4 @@ #pragma once -bool upgrade(bool /*ask*/ = true) -{ - // Intentionally left no-op as a placeholder for future use - - return true; -} +bool upgrade(bool ask = true);