1
0
mirror of https://github.com/qTox/qTox.git synced 2024-03-22 14:00:36 +08:00

feat(proxy): provide commandline tools for proxy settings

This commit is contained in:
jenli669 2019-08-16 16:55:29 +02:00 committed by Anthony Bilinski
parent 2ea5030958
commit 31fec7488f
No known key found for this signature in database
GPG Key ID: 2AA8E0DA1B31FB3C
7 changed files with 278 additions and 17 deletions

View File

@ -58,6 +58,96 @@ static QList<QByteArray>* logBuffer =
QMutex* logBufferMutex = new QMutex(); QMutex* logBufferMutex = new QMutex();
#endif #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() void cleanup()
{ {
// force save early even though destruction saves, because Windows OS will // force save early even though destruction saves, because Windows OS will
@ -215,6 +305,21 @@ int main(int argc, char* argv[])
QCommandLineOption(QStringList() << "l" QCommandLineOption(QStringList() << "l"
<< "login", << "login",
QObject::tr("Starts new instance and opens the login screen."))); QObject::tr("Starts new instance and opens the login screen.")));
parser.addOption(QCommandLineOption(QStringList() << "I"
<< "IPv6",
QObject::tr("Sets IPv6 <on>/<off>"), QObject::tr("on/off")));
parser.addOption(QCommandLineOption(QStringList() << "U"
<< "UDP",
QObject::tr("Sets UDP <on>/<off>"), QObject::tr("on/off")));
parser.addOption(
QCommandLineOption(QStringList() << "L"
<< "LAN",
QObject::tr("Sets LAN discovery <on>/<off>. 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); parser.process(*a);
uint32_t profileId = settings.getCurrentProfileId(); 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 // TODO(sudden6): remove once we get rid of Nexus
Nexus& nexus = Nexus::getInstance(); Nexus& nexus = Nexus::getInstance();
// TODO(kriby): Consider moving application initializing variables into a globalSettings object // 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) // Further: generate view instances separately (loginScreen, mainGUI, audio)
Profile* profile = nullptr; Profile* profile = nullptr;
if (autoLogin && Profile::exists(profileName) && !Profile::isEncrypted(profileName)) { if (autoLogin && Profile::exists(profileName) && !Profile::isEncrypted(profileName)) {
profile = Profile::loadProfile(profileName); profile = Profile::loadProfile(profileName, &parser);
if (!profile) { if (!profile) {
QMessageBox::information(nullptr, QObject::tr("Error"), QMessageBox::information(nullptr, QObject::tr("Error"),
QObject::tr("Failed to load profile automatically.")); QObject::tr("Failed to load profile automatically."));
} }
} }
if (profile) { if (profile) {
settings.updateProfileData(profile);
nexus.bootstrapWithProfile(profile); nexus.bootstrapWithProfile(profile);
} else { } else {
nexus.setParser(&parser);
int returnval = nexus.showLogin(profileName); int returnval = nexus.showLogin(profileName);
if (returnval != 0) { if (returnval != 0) {
return returnval; return returnval;

View File

@ -30,6 +30,7 @@
#include "widget/gui.h" #include "widget/gui.h"
#include "widget/loginscreen.h" #include "widget/loginscreen.h"
#include <QApplication> #include <QApplication>
#include <QCommandLineParser>
#include <QDebug> #include <QDebug>
#include <QDesktopWidget> #include <QDesktopWidget>
#include <QThread> #include <QThread>
@ -183,14 +184,10 @@ void Nexus::bootstrapWithProfile(Profile* p)
void Nexus::setSettings(Settings* settings) void Nexus::setSettings(Settings* settings)
{ {
if (this->settings) { if (this->settings) {
QObject::disconnect(this, &Nexus::currentProfileChanged, this->settings,
&Settings::updateProfileData);
QObject::disconnect(this, &Nexus::saveGlobal, this->settings, &Settings::saveGlobal); QObject::disconnect(this, &Nexus::saveGlobal, this->settings, &Settings::saveGlobal);
} }
this->settings = settings; this->settings = settings;
if (this->settings) { if (this->settings) {
QObject::connect(this, &Nexus::currentProfileChanged, this->settings,
&Settings::updateProfileData);
QObject::connect(this, &Nexus::saveGlobal, this->settings, &Settings::saveGlobal); 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) 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) 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. * Changes the loaded profile and notifies listeners.
@ -319,6 +318,11 @@ void Nexus::setProfile(Profile* p)
emit currentProfileChanged(p); emit currentProfileChanged(p);
} }
void Nexus::setParser(QCommandLineParser* parser)
{
this->parser = parser;
}
/** /**
* @brief Get desktop GUI widget. * @brief Get desktop GUI widget.
* @return nullptr if not started, desktop widget otherwise. * @return nullptr if not started, desktop widget otherwise.
@ -389,7 +393,7 @@ void Nexus::updateWindowsArg(QWindow* closedWindow)
QAction* action = windowActions->addAction(windowList[i]->title()); QAction* action = windowActions->addAction(windowList[i]->title());
action->setCheckable(true); action->setCheckable(true);
action->setChecked(windowList[i] == activeWindow); action->setChecked(windowList[i] == activeWindow);
connect(action, &QAction::triggered, [=] { onOpenWindow(windowList[i]);}); connect(action, &QAction::triggered, [=] { onOpenWindow(windowList[i]); });
windowMenu->addAction(action); windowMenu->addAction(action);
dockMenu->insertAction(dockLast, action); dockMenu->insertAction(dockLast, action);
} }

View File

@ -30,6 +30,7 @@ class Profile;
class Settings; class Settings;
class LoginScreen; class LoginScreen;
class Core; class Core;
class QCommandLineParser;
#ifdef Q_OS_MAC #ifdef Q_OS_MAC
class QMenuBar; class QMenuBar;
@ -47,6 +48,7 @@ public:
void start(); void start();
void showMainGUI(); void showMainGUI();
void setSettings(Settings* settings); void setSettings(Settings* settings);
void setParser(QCommandLineParser* parser);
static Nexus& getInstance(); static Nexus& getInstance();
static void destroyInstance(); static void destroyInstance();
static Core* getCore(); static Core* getCore();
@ -89,7 +91,7 @@ public slots:
void onCreateNewProfile(const QString& name, const QString& pass); void onCreateNewProfile(const QString& name, const QString& pass);
void onLoadProfile(const QString& name, const QString& pass); void onLoadProfile(const QString& name, const QString& pass);
int showLogin(const QString& profileName = QString()); int showLogin(const QString& profileName = QString());
void bootstrapWithProfile(Profile *p); void bootstrapWithProfile(Profile* p);
private: private:
explicit Nexus(QObject* parent = nullptr); explicit Nexus(QObject* parent = nullptr);
@ -102,6 +104,7 @@ private:
Settings* settings; Settings* settings;
Widget* widget; Widget* widget;
std::unique_ptr<IAudioControl> audioControl; std::unique_ptr<IAudioControl> audioControl;
QCommandLineParser* parser = nullptr;
}; };
#endif // NEXUS_H #endif // NEXUS_H

View File

@ -123,7 +123,7 @@ Profile::Profile(QString name, const QString& password, bool isNewProfile,
* *
* @example If the profile is already in use return nullptr. * @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()) { if (ProfileLocker::hasLock()) {
qCritical() << "Tried to load profile " << name << ", but another profile is already locked!"; 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; Profile* p = nullptr;
qint64 fileSize = 0; qint64 fileSize = 0;
QString path = Settings::getInstance().getSettingsDirPath() + name + ".tox"; Settings& s = Settings::getInstance();
QString path = s.getSettingsDirPath() + name + ".tox";
QFile saveFile(path); QFile saveFile(path);
qDebug() << "Loading tox save " << path; qDebug() << "Loading tox save " << path;
@ -186,6 +187,8 @@ Profile* Profile::loadProfile(QString name, const QString& password)
saveFile.close(); saveFile.close();
p = new Profile(name, password, false, data, std::move(tmpKey)); p = new Profile(name, password, false, data, std::move(tmpKey));
s.updateProfileData(p, parser);
return p; return p;
// cleanup in case of error // cleanup in case of error
@ -203,7 +206,7 @@ fail:
* *
* @note If the profile is already in use return nullptr. * @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<ToxEncrypt> tmpKey; std::unique_ptr<ToxEncrypt> tmpKey;
if (!password.isEmpty()) { if (!password.isEmpty()) {

View File

@ -34,13 +34,16 @@
#include <QVector> #include <QVector>
#include <memory> #include <memory>
class Settings;
class QCommandLineParser;
class Profile : public QObject class Profile : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
static Profile* loadProfile(QString name, const QString& password = QString()); static Profile* loadProfile(QString name, const QCommandLineParser* parser, const QString& password = QString());
static Profile* createProfile(QString name, QString password); static Profile* createProfile(QString name, const QCommandLineParser* parser, QString password);
~Profile(); ~Profile();
Core* getCore(); Core* getCore();

View File

@ -45,6 +45,7 @@
#include <QStandardPaths> #include <QStandardPaths>
#include <QStyleFactory> #include <QStyleFactory>
#include <QThread> #include <QThread>
#include <QtCore/QCommandLineParser>
/** /**
* @var QHash<QString, QByteArray> Settings::widgetSettings * @var QHash<QString, QByteArray> Settings::widgetSettings
@ -279,7 +280,7 @@ bool Settings::isToxPortable()
return result; return result;
} }
void Settings::updateProfileData(Profile *profile) void Settings::updateProfileData(Profile* profile, const QCommandLineParser* parser)
{ {
QMutexLocker locker{&bigLock}; QMutexLocker locker{&bigLock};
@ -290,6 +291,141 @@ void Settings::updateProfileData(Profile *profile)
setCurrentProfile(profile->getName()); setCurrentProfile(profile->getName());
saveGlobal(); saveGlobal();
loadPersonal(profile->getName(), profile->getPasskey()); 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) void Settings::loadPersonal(QString profileName, const ToxEncrypt* passKey)

View File

@ -39,6 +39,7 @@
#include <QPixmap> #include <QPixmap>
class Profile; class Profile;
class QCommandLineParser;
namespace Db { namespace Db {
enum class syncType; enum class syncType;
@ -170,7 +171,7 @@ public slots:
void saveGlobal(); void saveGlobal();
void sync(); void sync();
void setAutoLogin(bool state); void setAutoLogin(bool state);
void updateProfileData(Profile *profile); void updateProfileData(Profile* profile, const QCommandLineParser* parser);
signals: signals:
// General // General
@ -238,6 +239,8 @@ signals:
void blackListChanged(QStringList& blist); void blackListChanged(QStringList& blist);
public: public:
bool applyCommandLineOptions(const QCommandLineParser* parser);
bool getMakeToxPortable() const; bool getMakeToxPortable() const;
void setMakeToxPortable(bool newValue); void setMakeToxPortable(bool newValue);