From 31fec7488f74dc2fe38f0a8515b415c21f3e2109 Mon Sep 17 00:00:00 2001 From: jenli669 Date: Fri, 16 Aug 2019 16:55:29 +0200 Subject: [PATCH] feat(proxy): provide commandline tools for proxy settings --- src/main.cpp | 113 +++++++++++++++++++++++++++- src/nexus.cpp | 18 +++-- src/nexus.h | 5 +- src/persistence/profile.cpp | 9 ++- src/persistence/profile.h | 7 +- src/persistence/settings.cpp | 138 ++++++++++++++++++++++++++++++++++- src/persistence/settings.h | 5 +- 7 files changed, 278 insertions(+), 17 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 6efe9ff4c..2c18bb744 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -58,6 +58,96 @@ static QList* logBuffer = QMutex* logBufferMutex = new QMutex(); #endif +/** + * Verifies that commandline proxy settings are at least reasonable. Does not verify provided IP + * or hostname addresses are valid. Code duplication with Settings::applyCommandLineOptions, which + * also verifies arguments, should be removed in a future refactor. + * @param parser QCommandLineParser instance + */ +bool verifyProxySettings(QCommandLineParser& parser) +{ + QString IPv6Setting = parser.value("I"); + QString LANSetting = parser.value("L"); + QString UDPSetting = parser.value("U"); + QString proxySettingString = parser.value("proxy"); + QStringList proxySettings = proxySettingString.split(":"); + // Check for incompatible settings + bool activeProxyType = false; + + if (parser.isSet("P")) { + activeProxyType = proxySettings[0].compare(QString("SOCKS5"), Qt::CaseInsensitive) == 0 + || proxySettings[0].compare(QString("HTTP"), Qt::CaseInsensitive) == 0; + } + + + if (activeProxyType && (UDPSetting.compare(QString("on"), Qt::CaseInsensitive) == 0)) { + qCritical() << "Cannot set UDP on with proxy."; + return false; + } + + if (activeProxyType && (LANSetting.compare(QString("on"), Qt::CaseInsensitive) == 0)) { + qCritical() << "Cannot set LAN discovery on with proxy."; + return false; + } + + if ((LANSetting.compare(QString("on"), Qt::CaseInsensitive) == 0) + && (UDPSetting.compare(QString("off"), Qt::CaseInsensitive) == 0)) { + qCritical() << "Incompatible UDP/LAN settings."; + return false; + } + + if (parser.isSet("I")) { + if (!(IPv6Setting.compare(QString("on"), Qt::CaseInsensitive) == 0 + || IPv6Setting.compare(QString("off"), Qt::CaseInsensitive) == 0)) { + qCritical() << "Unable to parse IPv6 setting."; + return false; + } + } + + if (parser.isSet("U")) { + if (!(UDPSetting.compare(QString("on"), Qt::CaseInsensitive) == 0 + || UDPSetting.compare(QString("off"), Qt::CaseInsensitive) == 0)) { + qCritical() << "Unable to parse UDP setting."; + return false; + } + } + + if (parser.isSet("L")) { + if (!(LANSetting.compare(QString("on"), Qt::CaseInsensitive) == 0 + || LANSetting.compare(QString("off"), Qt::CaseInsensitive) == 0)) { + qCritical() << "Unable to parse LAN setting."; + return false; + } + } + + if (parser.isSet("P")) { + if (proxySettings[0].compare(QString("NONE"), Qt::CaseInsensitive) == 0) { + return true; + // slightly lazy check here, accepting 'NONE[:.*]' is fine since no other + // arguments will be investigated when proxy settings are applied. + } + // Since the first argument isn't 'none', verify format of remaining arguments + if (proxySettings.size() != 3) { + qCritical() << "Invalid number of proxy arguments."; + return false; + } + + if (!(proxySettings[0].compare(QString("SOCKS5"), Qt::CaseInsensitive) == 0 + || proxySettings[0].compare(QString("HTTP"), Qt::CaseInsensitive) == 0)) { + qCritical() << "Unable to parse proxy type."; + return false; + } + + // Kriby: Sanity checking IPv4+IPv6/hostnames sure is a lot of work! + + int portNumber = proxySettings[2].toInt(); + if (!(portNumber > 0 && portNumber < 65536)) { + qCritical() << "Invalid port number range."; + } + } + return true; +} + void cleanup() { // force save early even though destruction saves, because Windows OS will @@ -215,6 +305,21 @@ int main(int argc, char* argv[]) QCommandLineOption(QStringList() << "l" << "login", QObject::tr("Starts new instance and opens the login screen."))); + parser.addOption(QCommandLineOption(QStringList() << "I" + << "IPv6", + QObject::tr("Sets IPv6 /"), QObject::tr("on/off"))); + parser.addOption(QCommandLineOption(QStringList() << "U" + << "UDP", + QObject::tr("Sets UDP /"), QObject::tr("on/off"))); + parser.addOption( + QCommandLineOption(QStringList() << "L" + << "LAN", + QObject::tr("Sets LAN discovery /. UDP off overrides."), + QObject::tr("on/off"))); + parser.addOption(QCommandLineOption(QStringList() << "P" + << "proxy", + QObject::tr("Sets proxy settings."), + QObject::tr("(SOCKS5/HTTP/NONE):(ADDRESS):(PORT)"))); parser.process(*a); uint32_t profileId = settings.getCurrentProfileId(); @@ -330,6 +435,10 @@ int main(int argc, char* argv[]) } } + if (!verifyProxySettings(parser)) { + return -1; + } + // TODO(sudden6): remove once we get rid of Nexus Nexus& nexus = Nexus::getInstance(); // TODO(kriby): Consider moving application initializing variables into a globalSettings object @@ -342,16 +451,16 @@ int main(int argc, char* argv[]) // Further: generate view instances separately (loginScreen, mainGUI, audio) Profile* profile = nullptr; if (autoLogin && Profile::exists(profileName) && !Profile::isEncrypted(profileName)) { - profile = Profile::loadProfile(profileName); + profile = Profile::loadProfile(profileName, &parser); if (!profile) { QMessageBox::information(nullptr, QObject::tr("Error"), QObject::tr("Failed to load profile automatically.")); } } if (profile) { - settings.updateProfileData(profile); nexus.bootstrapWithProfile(profile); } else { + nexus.setParser(&parser); int returnval = nexus.showLogin(profileName); if (returnval != 0) { return returnval; diff --git a/src/nexus.cpp b/src/nexus.cpp index ccc66fc45..5cef77d3c 100644 --- a/src/nexus.cpp +++ b/src/nexus.cpp @@ -30,6 +30,7 @@ #include "widget/gui.h" #include "widget/loginscreen.h" #include +#include #include #include #include @@ -183,14 +184,10 @@ void Nexus::bootstrapWithProfile(Profile* p) void Nexus::setSettings(Settings* settings) { if (this->settings) { - QObject::disconnect(this, &Nexus::currentProfileChanged, this->settings, - &Settings::updateProfileData); QObject::disconnect(this, &Nexus::saveGlobal, this->settings, &Settings::saveGlobal); } this->settings = settings; if (this->settings) { - QObject::connect(this, &Nexus::currentProfileChanged, this->settings, - &Settings::updateProfileData); QObject::connect(this, &Nexus::saveGlobal, this->settings, &Settings::saveGlobal); } } @@ -292,7 +289,8 @@ Profile* Nexus::getProfile() */ void Nexus::onCreateNewProfile(const QString& name, const QString& pass) { - setProfile(Profile::createProfile(name, pass)); + setProfile(Profile::createProfile(name, parser, pass)); + parser = nullptr; // only apply cmdline proxy settings once } /** @@ -300,7 +298,8 @@ void Nexus::onCreateNewProfile(const QString& name, const QString& pass) */ void Nexus::onLoadProfile(const QString& name, const QString& pass) { - setProfile(Profile::loadProfile(name, pass)); + setProfile(Profile::loadProfile(name, parser, pass)); + parser = nullptr; // only apply cmdline proxy settings once } /** * Changes the loaded profile and notifies listeners. @@ -319,6 +318,11 @@ void Nexus::setProfile(Profile* p) emit currentProfileChanged(p); } +void Nexus::setParser(QCommandLineParser* parser) +{ + this->parser = parser; +} + /** * @brief Get desktop GUI widget. * @return nullptr if not started, desktop widget otherwise. @@ -389,7 +393,7 @@ void Nexus::updateWindowsArg(QWindow* closedWindow) QAction* action = windowActions->addAction(windowList[i]->title()); action->setCheckable(true); action->setChecked(windowList[i] == activeWindow); - connect(action, &QAction::triggered, [=] { onOpenWindow(windowList[i]);}); + connect(action, &QAction::triggered, [=] { onOpenWindow(windowList[i]); }); windowMenu->addAction(action); dockMenu->insertAction(dockLast, action); } diff --git a/src/nexus.h b/src/nexus.h index 85c1b7483..3cd7c5f07 100644 --- a/src/nexus.h +++ b/src/nexus.h @@ -30,6 +30,7 @@ class Profile; class Settings; class LoginScreen; class Core; +class QCommandLineParser; #ifdef Q_OS_MAC class QMenuBar; @@ -47,6 +48,7 @@ public: void start(); void showMainGUI(); void setSettings(Settings* settings); + void setParser(QCommandLineParser* parser); static Nexus& getInstance(); static void destroyInstance(); static Core* getCore(); @@ -89,7 +91,7 @@ public slots: void onCreateNewProfile(const QString& name, const QString& pass); void onLoadProfile(const QString& name, const QString& pass); int showLogin(const QString& profileName = QString()); - void bootstrapWithProfile(Profile *p); + void bootstrapWithProfile(Profile* p); private: explicit Nexus(QObject* parent = nullptr); @@ -102,6 +104,7 @@ private: Settings* settings; Widget* widget; std::unique_ptr audioControl; + QCommandLineParser* parser = nullptr; }; #endif // NEXUS_H diff --git a/src/persistence/profile.cpp b/src/persistence/profile.cpp index 8c42b6117..203333f32 100644 --- a/src/persistence/profile.cpp +++ b/src/persistence/profile.cpp @@ -123,7 +123,7 @@ Profile::Profile(QString name, const QString& password, bool isNewProfile, * * @example If the profile is already in use return nullptr. */ -Profile* Profile::loadProfile(QString name, const QString& password) +Profile* Profile::loadProfile(QString name, const QCommandLineParser* parser, const QString& password) { if (ProfileLocker::hasLock()) { qCritical() << "Tried to load profile " << name << ", but another profile is already locked!"; @@ -140,7 +140,8 @@ Profile* Profile::loadProfile(QString name, const QString& password) Profile* p = nullptr; qint64 fileSize = 0; - QString path = Settings::getInstance().getSettingsDirPath() + name + ".tox"; + Settings& s = Settings::getInstance(); + QString path = s.getSettingsDirPath() + name + ".tox"; QFile saveFile(path); qDebug() << "Loading tox save " << path; @@ -186,6 +187,8 @@ Profile* Profile::loadProfile(QString name, const QString& password) saveFile.close(); p = new Profile(name, password, false, data, std::move(tmpKey)); + + s.updateProfileData(p, parser); return p; // cleanup in case of error @@ -203,7 +206,7 @@ fail: * * @note If the profile is already in use return nullptr. */ -Profile* Profile::createProfile(QString name, QString password) +Profile* Profile::createProfile(QString name, const QCommandLineParser* parser, QString password) { std::unique_ptr tmpKey; if (!password.isEmpty()) { diff --git a/src/persistence/profile.h b/src/persistence/profile.h index 6484bd720..3476f95c9 100644 --- a/src/persistence/profile.h +++ b/src/persistence/profile.h @@ -34,13 +34,16 @@ #include #include +class Settings; +class QCommandLineParser; + class Profile : public QObject { Q_OBJECT public: - static Profile* loadProfile(QString name, const QString& password = QString()); - static Profile* createProfile(QString name, QString password); + static Profile* loadProfile(QString name, const QCommandLineParser* parser, const QString& password = QString()); + static Profile* createProfile(QString name, const QCommandLineParser* parser, QString password); ~Profile(); Core* getCore(); diff --git a/src/persistence/settings.cpp b/src/persistence/settings.cpp index e81b092cb..63cbd1036 100644 --- a/src/persistence/settings.cpp +++ b/src/persistence/settings.cpp @@ -45,6 +45,7 @@ #include #include #include +#include /** * @var QHash Settings::widgetSettings @@ -279,7 +280,7 @@ bool Settings::isToxPortable() return result; } -void Settings::updateProfileData(Profile *profile) +void Settings::updateProfileData(Profile* profile, const QCommandLineParser* parser) { QMutexLocker locker{&bigLock}; @@ -290,6 +291,141 @@ void Settings::updateProfileData(Profile *profile) setCurrentProfile(profile->getName()); saveGlobal(); loadPersonal(profile->getName(), profile->getPasskey()); + if (parser) { + applyCommandLineOptions(parser); + } +} +/** + * Applies command line options on top of loaded settings. Fails without changes if attempting to + * apply contradicting settings. + * @param parser + * @return Success indicator (success = true) + */ +bool Settings::applyCommandLineOptions(const QCommandLineParser* parser) +{ + QString IPv6Setting = parser->value("I"); + QString LANSetting = parser->value("L"); + QString UDPSetting = parser->value("U"); + QString proxySettingString = parser->value("proxy"); + QStringList proxySettings = proxySettingString.split(":"); + // Check for incompatible settings + bool activeProxyType = false; + + if (parser->isSet("P")) { + activeProxyType = proxySettings[0].compare(QString("SOCKS5"), Qt::CaseInsensitive) == 0 + || proxySettings[0].compare(QString("HTTP"), Qt::CaseInsensitive) == 0; + } + + + if (activeProxyType && (UDPSetting.compare(QString("on"), Qt::CaseInsensitive) == 0)) { + qCritical() << "Cannot set UDP on with proxy."; + return false; + } + + if (activeProxyType && (LANSetting.compare(QString("on"), Qt::CaseInsensitive) == 0)) { + qCritical() << "Cannot set LAN discovery on with proxy."; + return false; + } + + if ((LANSetting.compare(QString("on"), Qt::CaseInsensitive) == 0) + && (UDPSetting.compare(QString("off"), Qt::CaseInsensitive) == 0)) { + qCritical() << "Incompatible UDP/LAN settings."; + return false; + } + + if (parser->isSet("I")) { + if (IPv6Setting.compare(QString("on"), Qt::CaseInsensitive) == 0) { + enableIPv6 = true; + qDebug() << "Setting IPv6 ON."; + } else if (IPv6Setting.compare(QString("off"), Qt::CaseInsensitive) == 0) { + enableIPv6 = false; + qDebug() << "Setting IPv6 OFF."; + } else { + qCritical() << "Unable to parse IPv6 setting."; + return false; + } + } + + if (parser->isSet("U")) { + if (UDPSetting.compare(QString("on"), Qt::CaseInsensitive) == 0) { + forceTCP = false; + qDebug() << "Setting UDP ON."; + if (proxyType != ICoreSettings::ProxyType::ptNone) { + qDebug() << "Cannot use UDP with proxy; disabling proxy settings."; + proxyType = ICoreSettings::ProxyType::ptNone; + } + } else if (UDPSetting.compare(QString("off"), Qt::CaseInsensitive) == 0) { + forceTCP = true; + qDebug() << "Setting UDP OFF."; + } else { + qCritical() << "Unable to parse UDP setting."; + return false; + } + } + + if (parser->isSet("L")) { + if (LANSetting.compare(QString("on"), Qt::CaseInsensitive) == 0) { + qDebug() << "Setting LAN Discovery ON."; + enableLanDiscovery = true; + if (forceTCP) { + qDebug() << "Cannot use LAN discovery without UDP; enabling UDP."; + proxyType = ICoreSettings::ProxyType::ptNone; + } + if (proxyType != ICoreSettings::ProxyType::ptNone) { + qDebug() << "Cannot use LAN discovery with proxy; disabling proxy settings."; + proxyType = ICoreSettings::ProxyType::ptNone; + } + } else if (LANSetting.compare(QString("off"), Qt::CaseInsensitive) == 0) { + enableLanDiscovery = false; + qDebug() << "Setting LAN Discovery OFF."; + } else { + qCritical() << "Unable to parse LAN setting."; + return false; + } + } + + if (parser->isSet("P")) { + if (proxySettings[0].compare(QString("NONE"), Qt::CaseInsensitive) == 0) { + proxyType = ICoreSettings::ProxyType::ptNone; + proxyAddr = ""; + proxyPort = 0; + qDebug() << "Setting proxy type to NONE."; + return true; + } + // Since the first argument isn't 'none', verify format of remaining arguments + if (proxySettings.size() != 3) { + qCritical() << "Invalid number of proxy arguments."; + return false; + } + + if (proxySettings[0].compare(QString("SOCKS5"), Qt::CaseInsensitive) == 0) { + proxyType = ICoreSettings::ProxyType::ptSOCKS5; + forceTCP = true; + enableLanDiscovery = false; + qDebug() << "Setting proxy type to SOCKS5."; + } else if (proxySettings[0].compare(QString("HTTP"), Qt::CaseInsensitive) == 0) { + proxyType = ICoreSettings::ProxyType::ptHTTP; + forceTCP = true; + enableLanDiscovery = false; + qDebug() << "Setting proxy type to HTTP."; + } else { + qCritical() << "Unable to parse proxy type."; + return false; + } + + // Kriby: Sanity checking IPv4+IPv6/hostnames sure is a lot of work! + proxyAddr = proxySettings[1]; + qDebug() << QString("Setting proxy address to %1.").arg(proxySettings[1]); + + int portNumber = proxySettings[2].toInt(); + if (portNumber > 0 && portNumber < 65536) { + proxyPort = portNumber; + qDebug() << QString("Setting port number to %1.").arg(portNumber); + } else { + qCritical() << "Invalid port number range."; + } + } + return true; } void Settings::loadPersonal(QString profileName, const ToxEncrypt* passKey) diff --git a/src/persistence/settings.h b/src/persistence/settings.h index b08b9ea68..64ff10bfb 100644 --- a/src/persistence/settings.h +++ b/src/persistence/settings.h @@ -39,6 +39,7 @@ #include class Profile; +class QCommandLineParser; namespace Db { enum class syncType; @@ -170,7 +171,7 @@ public slots: void saveGlobal(); void sync(); void setAutoLogin(bool state); - void updateProfileData(Profile *profile); + void updateProfileData(Profile* profile, const QCommandLineParser* parser); signals: // General @@ -238,6 +239,8 @@ signals: void blackListChanged(QStringList& blist); public: + bool applyCommandLineOptions(const QCommandLineParser* parser); + bool getMakeToxPortable() const; void setMakeToxPortable(bool newValue);