diff --git a/3rdparty/plog b/3rdparty/plog index f4c22b0..914e799 160000 --- a/3rdparty/plog +++ b/3rdparty/plog @@ -1 +1 @@ -Subproject commit f4c22b03d5d3aa753cca8e716636ac4eb29b0917 +Subproject commit 914e799d2b08d790f5d04d1c46928586b3a41250 diff --git a/GPClient/CMakeLists.txt b/GPClient/CMakeLists.txt index 9c52cdd..5387aa4 100644 --- a/GPClient/CMakeLists.txt +++ b/GPClient/CMakeLists.txt @@ -33,6 +33,8 @@ add_executable(gpclient gpclient.ui normalloginwindow.ui settingsdialog.ui + vpn_dbus.cpp + vpn_json.cpp resources.qrc ${gpclient_GENERATED_SOURCES} ) @@ -52,7 +54,7 @@ add_3rdparty( add_3rdparty( plog GIT_REPOSITORY https://github.com/SergiusTheBest/plog.git - GIT_TAG 1.1.5 + GIT_TAG master CMAKE_ARGS -DPLOG_BUILD_SAMPLES=OFF -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} diff --git a/GPClient/gpclient.cpp b/GPClient/gpclient.cpp index 76cfd58..a2741cb 100644 --- a/GPClient/gpclient.cpp +++ b/GPClient/gpclient.cpp @@ -11,9 +11,10 @@ using namespace gpclient::helper; -GPClient::GPClient(QWidget *parent) +GPClient::GPClient(QWidget *parent, IVpn *vpn) : QMainWindow(parent) , ui(new Ui::GPClient) + , vpn(vpn) , settingsDialog(new SettingsDialog(this)) { ui->setupUi(this); @@ -25,14 +26,14 @@ GPClient::GPClient(QWidget *parent) setupSettings(); // Restore portal from the previous settings - ui->portalInput->setText(settings::get("portal", "").toString()); + this->portal(settings::get("portal", "").toString()); // DBus service setup - vpn = new com::yuezk::qt::GPService("com.yuezk.qt.GPService", "/", QDBusConnection::systemBus(), this); - connect(vpn, &com::yuezk::qt::GPService::connected, this, &GPClient::onVPNConnected); - connect(vpn, &com::yuezk::qt::GPService::disconnected, this, &GPClient::onVPNDisconnected); - connect(vpn, &com::yuezk::qt::GPService::error, this, &GPClient::onVPNError); - connect(vpn, &com::yuezk::qt::GPService::logAvailable, this, &GPClient::onVPNLogAvailable); + QObject *ov = dynamic_cast(vpn); + connect(ov, SIGNAL(connected()), this, SLOT(onVPNConnected())); + connect(ov, SIGNAL(disconnected()), this, SLOT(onVPNDisconnected())); + connect(ov, SIGNAL(error(const &QString)), this, SLOT(onVPNError(const QString&))); + connect(ov, SIGNAL(logAvailable(const &QString)), this, SLOT(onVPNLogAvailable(const QString&))); // Initiallize the context menu of system tray. initSystemTrayIcon(); @@ -373,7 +374,11 @@ void GPClient::onGatewaySuccess(const QString &authCookie) PLOGI << "Gateway login succeeded, got the cookie " << authCookie; isQuickConnect = false; - vpn->connect(currentGateway().address(), portalConfig.username(), authCookie, settings::get("extraArgs", "").toString()); + QList gatewayAddresses; + for (GPGateway &gw : allGateways()) { + gatewayAddresses.push_back(gw.address()); + } + vpn->connect(currentGateway().address(), gatewayAddresses, portalConfig.username(), authCookie, settings::get("extraArgs", "").toString()); ui->statusLabel->setText("Connecting..."); updateConnectionStatus(VpnStatus::pending); } @@ -410,6 +415,11 @@ QString GPClient::portal() const return input; } +void GPClient::portal(QString p) +{ + ui->portalInput->setText(p); +} + bool GPClient::connected() const { const QString statusText = ui->statusLabel->text(); diff --git a/GPClient/gpclient.h b/GPClient/gpclient.h index ae2eaf7..0578305 100644 --- a/GPClient/gpclient.h +++ b/GPClient/gpclient.h @@ -6,9 +6,9 @@ #include #include -#include "gpserviceinterface.h" #include "portalconfigresponse.h" #include "settingsdialog.h" +#include "vpn.h" QT_BEGIN_NAMESPACE namespace Ui { class GPClient; } @@ -19,12 +19,20 @@ class GPClient : public QMainWindow Q_OBJECT public: - GPClient(QWidget *parent = nullptr); + GPClient(QWidget *parent, IVpn *vpn); ~GPClient(); void activate(); void quit(); + QString portal() const; + void portal(QString); + + GPGateway currentGateway() const; + void setCurrentGateway(const GPGateway gateway); + + void doConnect(); + private slots: void onSettingsButtonClicked(); void onSettingsAccepted(); @@ -58,7 +66,7 @@ private: }; Ui::GPClient *ui; - com::yuezk::qt::GPService *vpn; + IVpn *vpn; QSystemTrayIcon *systemTrayIcon; QMenu *contextMenu; @@ -83,20 +91,15 @@ private: void populateGatewayMenu(); void updateConnectionStatus(const VpnStatus &status); - void doConnect(); void portalLogin(); void tryGatewayLogin(); void gatewayLogin(); - QString portal() const; bool connected() const; QList allGateways() const; void setAllGateways(QList gateways); - GPGateway currentGateway() const; - void setCurrentGateway(const GPGateway gateway); - void clearSettings(); }; #endif // GPCLIENT_H diff --git a/GPClient/main.cpp b/GPClient/main.cpp index 5344847..5e87ddc 100644 --- a/GPClient/main.cpp +++ b/GPClient/main.cpp @@ -3,24 +3,22 @@ #include #include #include +#include #include +#include #include "singleapplication.h" #include "gpclient.h" +#include "vpn_dbus.h" +#include "vpn_json.h" #include "enhancedwebview.h" #include "sigwatch.h" #include "version.h" int main(int argc, char *argv[]) { - const QDir path = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + "/GlobalProtect-openconnect"; - const QString logFile = path.path() + "/gpclient.log"; - if (!path.exists()) { - path.mkpath("."); - } - - static plog::ColorConsoleAppender consoleAppender; - plog::init(plog::debug, logFile.toUtf8()).addAppender(&consoleAppender); + plog::ColorConsoleAppender consoleAppender(plog::streamStdErr); + plog::init(plog::debug, &consoleAppender); PLOGI << "GlobalProtect started, version: " << VERSION; @@ -33,9 +31,35 @@ int main(int argc, char *argv[]) SingleApplication app(argc, argv); app.setQuitOnLastWindowClosed(false); - GPClient w; + QCommandLineParser parser; + parser.addHelpOption(); + parser.addVersionOption(); + parser.addPositionalArgument("server", "The URL of the VPN server. Optional."); + parser.addPositionalArgument("gateway", "The URL of the specific VPN gateway. Optional."); + parser.addOptions({ + {"json", "Write the result of the handshake with the GlobalConnect server to stdout as JSON and terminate. Useful for scripting."}, + {"now", "Do not show the dialog with the connect button; connect immediately instead."}, + }); + parser.process(app); + + const QStringList positional = parser.positionalArguments(); + + IVpn *vpn = parser.isSet("json") // yes it leaks, but this is cleared on exit anyway + ? static_cast(new VpnJson(nullptr)) // Print to stdout and exit + : static_cast(new VpnDbus(nullptr)); // Contact GPService daemon via dbus + GPClient w(nullptr, vpn); w.show(); + if (positional.size() > 0) { + w.portal(positional.at(0)); + } + if (positional.size() > 1) { + GPGateway gw; + gw.setName(positional.at(1)); + gw.setAddress(positional.at(1)); + w.setCurrentGateway(gw); + } + QObject::connect(&app, &SingleApplication::instanceStarted, &w, &GPClient::activate); UnixSignalWatcher sigwatch; @@ -45,5 +69,12 @@ int main(int argc, char *argv[]) sigwatch.watchForSignal(SIGHUP); QObject::connect(&sigwatch, &UnixSignalWatcher::unixSignal, &w, &GPClient::quit); + if (parser.isSet("now")) { + w.doConnect(); + } + if (parser.isSet("json")) { + QObject::connect(static_cast(vpn), &VpnJson::connected, &w, &GPClient::quit); + } + return app.exec(); } diff --git a/GPClient/vpn.h b/GPClient/vpn.h new file mode 100644 index 0000000..eb26f33 --- /dev/null +++ b/GPClient/vpn.h @@ -0,0 +1,24 @@ +#ifndef VPN_H +#define VPN_H +#include +#include + +class IVpn +{ +public: + virtual ~IVpn() = default; + + virtual void connect(const QString &preferredServer, const QList &servers, const QString &username, const QString &passwd, const QString &extraArgs) = 0; + virtual void disconnect() = 0; + virtual int status() = 0; + +// signals: // SIGNALS +// virtual void connected(); +// virtual void disconnected(); +// virtual void error(const QString &errorMessage); +// virtual void logAvailable(const QString &log); +}; + +Q_DECLARE_INTERFACE(IVpn, "IVpn") // define this out of namespace scope + +#endif diff --git a/GPClient/vpn_dbus.cpp b/GPClient/vpn_dbus.cpp new file mode 100644 index 0000000..2983cd1 --- /dev/null +++ b/GPClient/vpn_dbus.cpp @@ -0,0 +1,13 @@ +#include "vpn_dbus.h" + +void VpnDbus::connect(const QString &preferredServer, const QList &servers, const QString &username, const QString &passwd, const QString &extraArgs) { + inner->connect(preferredServer, username, passwd, extraArgs); +} + +void VpnDbus::disconnect() { + inner->disconnect(); +} + +int VpnDbus::status() { + return inner->status(); +} diff --git a/GPClient/vpn_dbus.h b/GPClient/vpn_dbus.h new file mode 100644 index 0000000..a66c86a --- /dev/null +++ b/GPClient/vpn_dbus.h @@ -0,0 +1,33 @@ +#ifndef VPN_DBUS_H +#define VPN_DBUS_H +#include "vpn.h" +#include "gpserviceinterface.h" + +class VpnDbus : public QObject, public IVpn +{ + Q_OBJECT + Q_INTERFACES(IVpn) + +private: + com::yuezk::qt::GPService *inner; + +public: + VpnDbus(QObject *parent) : QObject(parent) { + inner = new com::yuezk::qt::GPService("com.yuezk.qt.GPService", "/", QDBusConnection::systemBus(), this); + QObject::connect(inner, &com::yuezk::qt::GPService::connected, this, &VpnDbus::connected); + QObject::connect(inner, &com::yuezk::qt::GPService::disconnected, this, &VpnDbus::disconnected); + QObject::connect(inner, &com::yuezk::qt::GPService::error, this, &VpnDbus::error); + QObject::connect(inner, &com::yuezk::qt::GPService::logAvailable, this, &VpnDbus::logAvailable); + } + + void connect(const QString &preferredServer, const QList &servers, const QString &username, const QString &passwd, const QString &extraArgs); + void disconnect(); + int status(); + +signals: // SIGNALS + void connected(); + void disconnected(); + void error(const QString &errorMessage); + void logAvailable(const QString &log); +}; +#endif diff --git a/GPClient/vpn_json.cpp b/GPClient/vpn_json.cpp new file mode 100644 index 0000000..bfdcf7f --- /dev/null +++ b/GPClient/vpn_json.cpp @@ -0,0 +1,24 @@ +#include "vpn_json.h" +#include +#include +#include +#include + +void VpnJson::connect(const QString &preferredServer, const QList &servers, const QString &username, const QString &passwd, const QString &extraArgs) { + QJsonArray sl; + for (const QString &srv : servers) { + sl.push_back(QJsonValue(srv)); + } + QJsonObject j; + j["server"] = preferredServer; + j["availableServers"] = sl; + j["cookie"] = passwd; + QTextStream(stdout) << QJsonDocument(j).toJson(QJsonDocument::Compact) << "\n"; + emit connected(); +} + +void VpnJson::disconnect() { /* nop */ } + +int VpnJson::status() { + return 4; // disconnected +} diff --git a/GPClient/vpn_json.h b/GPClient/vpn_json.h new file mode 100644 index 0000000..f577bee --- /dev/null +++ b/GPClient/vpn_json.h @@ -0,0 +1,23 @@ +#ifndef VPN_JSON_H +#define VPN_JSON_H +#include "vpn.h" + +class VpnJson : public QObject, public IVpn +{ + Q_OBJECT + Q_INTERFACES(IVpn) + +public: + VpnJson(QObject *parent) : QObject(parent) {} + + void connect(const QString &preferredServer, const QList &servers, const QString &username, const QString &passwd, const QString &extraArgs); + void disconnect(); + int status(); + +signals: // SIGNALS + void connected(); + void disconnected(); + void error(const QString &errorMessage); + void logAvailable(const QString &log); +}; +#endif