diff --git a/GPClient/.qmake.stash b/GPClient/.qmake.stash new file mode 100644 index 0000000..d389915 --- /dev/null +++ b/GPClient/.qmake.stash @@ -0,0 +1,21 @@ +QMAKE_CXX.QT_COMPILER_STDCXX = 201402L +QMAKE_CXX.QMAKE_GCC_MAJOR_VERSION = 9 +QMAKE_CXX.QMAKE_GCC_MINOR_VERSION = 2 +QMAKE_CXX.QMAKE_GCC_PATCH_VERSION = 0 +QMAKE_CXX.COMPILER_MACROS = \ + QT_COMPILER_STDCXX \ + QMAKE_GCC_MAJOR_VERSION \ + QMAKE_GCC_MINOR_VERSION \ + QMAKE_GCC_PATCH_VERSION +QMAKE_CXX.INCDIRS = \ + /usr/include/c++/9.2.0 \ + /usr/include/c++/9.2.0/x86_64-pc-linux-gnu \ + /usr/include/c++/9.2.0/backward \ + /usr/lib/gcc/x86_64-pc-linux-gnu/9.2.0/include \ + /usr/local/include \ + /usr/lib/gcc/x86_64-pc-linux-gnu/9.2.0/include-fixed \ + /usr/include +QMAKE_CXX.LIBDIRS = \ + /usr/lib/gcc/x86_64-pc-linux-gnu/9.2.0 \ + /usr/lib \ + /lib diff --git a/GPClient/GPClient b/GPClient/GPClient new file mode 100755 index 0000000..fafb981 Binary files /dev/null and b/GPClient/GPClient differ diff --git a/GPClient/GPClient.pro b/GPClient/GPClient.pro new file mode 100644 index 0000000..7dbb124 --- /dev/null +++ b/GPClient/GPClient.pro @@ -0,0 +1,41 @@ +QT += core gui network websockets dbus webenginewidgets + +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets + +CONFIG += c++11 + +# The following define makes your compiler emit warnings if you use +# any Qt feature that has been marked deprecated (the exact warnings +# depend on your compiler). Please consult the documentation of the +# deprecated API in order to know how to port your code away from it. +DEFINES += QT_DEPRECATED_WARNINGS + +# You can also make your code fail to compile if it uses deprecated APIs. +# In order to do so, uncomment the following line. +# You can also select to disable deprecated APIs only up to a certain version of Qt. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += \ + cdpcommand.cpp \ + cdpcommandmanager.cpp \ + enhancedwebview.cpp \ + main.cpp \ + samlloginwindow.cpp \ + gpclient.cpp + +HEADERS += \ + cdpcommand.h \ + cdpcommandmanager.h \ + enhancedwebview.h \ + samlloginwindow.h \ + gpclient.h + +FORMS += \ + gpclient.ui + +DBUS_INTERFACES += ../GPService/gpservice.xml + +# Default rules for deployment. +qnx: target.path = /tmp/$${TARGET}/bin +else: unix:!android: target.path = /opt/$${TARGET}/bin +!isEmpty(target.path): INSTALLS += target diff --git a/GPClient/cdpcommand.cpp b/GPClient/cdpcommand.cpp new file mode 100644 index 0000000..10d3f48 --- /dev/null +++ b/GPClient/cdpcommand.cpp @@ -0,0 +1,30 @@ +#include "cdpcommand.h" + +#include +#include +#include + +CDPCommand::CDPCommand(QObject *parent) : QObject(parent) +{ +} + +CDPCommand::CDPCommand(int id, QString cmd, QVariantMap& params) : + QObject(nullptr), + id(id), + cmd(cmd), + params(¶ms) +{ +} + +QByteArray CDPCommand::toJson() +{ + QVariantMap payloadMap; + payloadMap["id"] = id; + payloadMap["method"] = cmd; + payloadMap["params"] = *params; + + QJsonObject payloadJsonObject = QJsonObject::fromVariantMap(payloadMap); + QJsonDocument payloadJson(payloadJsonObject); + + return payloadJson.toJson(); +} diff --git a/GPClient/cdpcommand.h b/GPClient/cdpcommand.h new file mode 100644 index 0000000..75cbdbe --- /dev/null +++ b/GPClient/cdpcommand.h @@ -0,0 +1,24 @@ +#ifndef CDPCOMMAND_H +#define CDPCOMMAND_H + +#include + +class CDPCommand : public QObject +{ + Q_OBJECT +public: + explicit CDPCommand(QObject *parent = nullptr); + CDPCommand(int id, QString cmd, QVariantMap& params); + + QByteArray toJson(); + +signals: + void finished(); + +private: + int id; + QString cmd; + QVariantMap *params; +}; + +#endif // CDPCOMMAND_H diff --git a/GPClient/cdpcommandmanager.cpp b/GPClient/cdpcommandmanager.cpp new file mode 100644 index 0000000..6b0f998 --- /dev/null +++ b/GPClient/cdpcommandmanager.cpp @@ -0,0 +1,85 @@ +#include "cdpcommandmanager.h" +#include + +CDPCommandManager::CDPCommandManager(QObject *parent) + : QObject(parent) + , networkManager(new QNetworkAccessManager) + , socket(new QWebSocket) +{ + // WebSocket setup + QObject::connect(socket, &QWebSocket::connected, this, &CDPCommandManager::ready); + QObject::connect(socket, &QWebSocket::textMessageReceived, this, &CDPCommandManager::onTextMessageReceived); + QObject::connect(socket, &QWebSocket::disconnected, this, &CDPCommandManager::onSocketDisconnected); + QObject::connect(socket, QOverload::of(&QWebSocket::error), this, &CDPCommandManager::onSocketError); +} + +CDPCommandManager::~CDPCommandManager() +{ + delete networkManager; + delete socket; +} + +void CDPCommandManager::initialize(QString endpoint) +{ + QNetworkReply *reply = networkManager->get(QNetworkRequest(endpoint)); + + QObject::connect( + reply, &QNetworkReply::finished, + [reply, this]() { + if (reply->error()) { + qDebug() << "CDP request error"; + return; + } + + QJsonDocument doc = QJsonDocument::fromJson(reply->readAll()); + QJsonArray pages = doc.array(); + QJsonObject page = pages.first().toObject(); + QString wsUrl = page.value("webSocketDebuggerUrl").toString(); + + socket->open(wsUrl); + } + ); +} + +CDPCommand *CDPCommandManager::sendCommand(QString cmd) +{ + QVariantMap emptyParams; + return sendCommend(cmd, emptyParams); +} + +CDPCommand *CDPCommandManager::sendCommend(QString cmd, QVariantMap ¶ms) +{ + int id = ++commandId; + CDPCommand *command = new CDPCommand(id, cmd, params); + socket->sendTextMessage(command->toJson()); + commandPool.insert(id, command); + + return command; +} + +void CDPCommandManager::onTextMessageReceived(QString message) +{ + QJsonDocument responseDoc = QJsonDocument::fromJson(message.toUtf8()); + QJsonObject response = responseDoc.object(); + + // Response for method + if (response.contains("id")) { + int id = response.value("id").toInt(); + if (commandPool.contains(id)) { + CDPCommand *command = commandPool.take(id); + command->finished(); + } + } else { // Response for event + emit eventReceived(response.value("method").toString(), response.value("params").toObject()); + } +} + +void CDPCommandManager::onSocketDisconnected() +{ + qDebug() << "WebSocket disconnected"; +} + +void CDPCommandManager::onSocketError(QAbstractSocket::SocketError error) +{ + qDebug() << "WebSocket error" << error; +} diff --git a/GPClient/cdpcommandmanager.h b/GPClient/cdpcommandmanager.h new file mode 100644 index 0000000..9aefedf --- /dev/null +++ b/GPClient/cdpcommandmanager.h @@ -0,0 +1,39 @@ +#ifndef CDPCOMMANDMANAGER_H +#define CDPCOMMANDMANAGER_H + +#include "cdpcommand.h" +#include +#include +#include +#include + +class CDPCommandManager : public QObject +{ + Q_OBJECT +public: + explicit CDPCommandManager(QObject *parent = nullptr); + ~CDPCommandManager(); + + void initialize(QString endpoint); + + CDPCommand *sendCommand(QString cmd); + CDPCommand *sendCommend(QString cmd, QVariantMap& params); + +signals: + void ready(); + void eventReceived(QString eventName, QJsonObject params); + +private: + QNetworkAccessManager *networkManager; + QWebSocket *socket; + + int commandId = 0; + QHash commandPool; + +private slots: + void onTextMessageReceived(QString message); + void onSocketDisconnected(); + void onSocketError(QAbstractSocket::SocketError error); +}; + +#endif // CDPCOMMANDMANAGER_H diff --git a/GPClient/enhancedwebview.cpp b/GPClient/enhancedwebview.cpp new file mode 100644 index 0000000..9387f24 --- /dev/null +++ b/GPClient/enhancedwebview.cpp @@ -0,0 +1,37 @@ +#include "enhancedwebview.h" +#include "cdpcommandmanager.h" + +#include +#include + +EnhancedWebView::EnhancedWebView(QWidget *parent) + : QWebEngineView(parent) + , cdp(new CDPCommandManager) +{ + QObject::connect(cdp, &CDPCommandManager::ready, this, &EnhancedWebView::onCDPReady); + QObject::connect(cdp, &CDPCommandManager::eventReceived, this, &EnhancedWebView::onEventReceived); +} + +EnhancedWebView::~EnhancedWebView() +{ + delete cdp; +} + +void EnhancedWebView::initialize() +{ + QString port = QProcessEnvironment::systemEnvironment().value("QTWEBENGINE_REMOTE_DEBUGGING"); + qDebug() << "port:" << port; + cdp->initialize("http://127.0.0.1:" + port + "/json"); +} + +void EnhancedWebView::onCDPReady() +{ + cdp->sendCommand("Network.enable"); +} + +void EnhancedWebView::onEventReceived(QString eventName, QJsonObject params) +{ + if (eventName == "Network.responseReceived") { + emit responseReceived(params); + } +} diff --git a/GPClient/enhancedwebview.h b/GPClient/enhancedwebview.h new file mode 100644 index 0000000..ccc0213 --- /dev/null +++ b/GPClient/enhancedwebview.h @@ -0,0 +1,28 @@ +#ifndef ENHANCEDWEBVIEW_H +#define ENHANCEDWEBVIEW_H + +#include "cdpcommandmanager.h" + +#include + +class EnhancedWebView : public QWebEngineView +{ + Q_OBJECT +public: + explicit EnhancedWebView(QWidget *parent = nullptr); + ~EnhancedWebView(); + + void initialize(); + +signals: + void responseReceived(QJsonObject params); + +private slots: + void onCDPReady(); + void onEventReceived(QString eventName, QJsonObject params); + +private: + CDPCommandManager *cdp; +}; + +#endif // ENHANCEDWEBVIEW_H diff --git a/GPClient/gpclient.cpp b/GPClient/gpclient.cpp new file mode 100644 index 0000000..0f6d891 --- /dev/null +++ b/GPClient/gpclient.cpp @@ -0,0 +1,155 @@ +#include "gpclient.h" +#include "ui_gpclient.h" +#include "samlloginwindow.h" + +GPClient::GPClient(QWidget *parent) + : QMainWindow(parent) + , ui(new Ui::GPClient) +{ + ui->setupUi(this); + + QObject::connect(this, &GPClient::connectFailed, [this]() { + ui->connectButton->setDisabled(false); + ui->connectButton->setText("Connect"); + }); + + // QNetworkAccessManager setup + networkManager = new QNetworkAccessManager(this); + + // Login window setup + loginWindow = new SAMLLoginWindow(this); + QObject::connect(loginWindow, &SAMLLoginWindow::success, this, &GPClient::onLoginSuccess); + QObject::connect(loginWindow, &SAMLLoginWindow::rejected, this, &GPClient::connectFailed); + + // DBus service setup + vpn = new com::yuezk::qt::GPService("com.yuezk.qt.GPService", "/", QDBusConnection::systemBus(), this); + QObject::connect(vpn, &com::yuezk::qt::GPService::connected, this, &GPClient::onVPNConnected); + QObject::connect(vpn, &com::yuezk::qt::GPService::disconnected, this, &GPClient::onVPNDisconnected); + QObject::connect(vpn, &com::yuezk::qt::GPService::logAvailable, this, &GPClient::onVPNLogAvailable); +} + +GPClient::~GPClient() +{ + delete ui; + delete networkManager; + delete reply; + delete loginWindow; + delete vpn; +} + +void GPClient::on_connectButton_clicked() +{ + if (ui->connectButton->text() == "Connect") { + QString portal = ui->portalInput->text(); + + ui->connectButton->setDisabled(true); + ui->connectButton->setText("Connecting..."); + samlLogin(portal); + } else { + ui->connectButton->setDisabled(true); + ui->connectButton->setText("Disconnecting..."); + + vpn->disconnect(); + } +} + +void GPClient::preloginResultFinished() +{ + if (reply->error()) { + qDebug() << "request error"; + emit connectFailed(); + return; + } + + QByteArray bytes = reply->readAll(); + qDebug("response is: %s", bytes.toStdString().c_str()); + + const QString tagMethod = "saml-auth-method"; + const QString tagRequest = "saml-request"; + QString samlMethod; + QString samlRequest; + + QXmlStreamReader xml(bytes); + while (!xml.atEnd()) { + xml.readNext(); + if (xml.tokenType() == xml.StartElement) { + if (xml.name() == tagMethod) { + samlMethod = xml.readElementText(); + } else if (xml.name() == tagRequest) { + samlRequest = QByteArray::fromBase64(QByteArray::fromStdString(xml.readElementText().toStdString())); + } + } + } + + if (samlMethod == nullptr || samlRequest == nullptr) { + qCritical("This does not appear to be a SAML prelogin response ( or tags missing)"); + emit connectFailed(); + return; + } + + if (samlMethod == "POST") { + // TODO + qInfo("TODO: SAML method is POST"); + emit connectFailed(); + } else if (samlMethod == "REDIRECT") { + qInfo() << "Request URL is: %s" << samlRequest; + + loginWindow->login(samlRequest); + loginWindow->exec(); + } + delete reply; +} + +void GPClient::onLoginSuccess(QJsonObject loginResult) +{ + qDebug() << "Login success:" << loginResult; + + QString fullpath = "/ssl-vpn/login.esp"; + QString shortpath = "gateway"; + QString user = loginResult.value("saml-username").toString(); + QString cookieName; + QString cookieValue; + QString cookies[]{"prelogin-cookie", "portal-userauthcookie"}; + + for (int i = 0; i < cookies->length(); i++) { + cookieValue = loginResult.value(cookies[i]).toString(); + if (cookieValue != nullptr) { + cookieName = cookies[i]; + break; + } + } + + QString host = QString("https://%1/%2:%3").arg(loginResult.value("server").toString(), shortpath, cookieName); + qDebug() << "Server:" << host << ", User:" << user << "Cookie:" << cookieValue; + qDebug() << "openconnect --protocol=gp -u" << user << "--passwd-on-stdin" << host; + + vpn->connect(host, user, cookieValue); +} + +void GPClient::onVPNConnected() +{ + qDebug() << "VPN connected"; + ui->connectButton->setDisabled(false); + ui->connectButton->setText("Disconnect"); +} + +void GPClient::onVPNDisconnected() +{ + qDebug() << "VPN disconnected"; + ui->connectButton->setDisabled(false); + ui->connectButton->setText("Connect"); +} + +void GPClient::onVPNLogAvailable(QString log) +{ + qDebug() << log; +} + +void GPClient::samlLogin(const QString portal) +{ + const QString preloginUrl = "https://" + portal + "/ssl-vpn/prelogin.esp"; + qDebug("%s", preloginUrl.toStdString().c_str()); + + reply = networkManager->post(QNetworkRequest(preloginUrl), (QByteArray) nullptr); + connect(reply, &QNetworkReply::finished, this, &GPClient::preloginResultFinished); +} diff --git a/GPClient/gpclient.h b/GPClient/gpclient.h new file mode 100644 index 0000000..454960d --- /dev/null +++ b/GPClient/gpclient.h @@ -0,0 +1,44 @@ +#ifndef GPCLIENT_H +#define GPCLIENT_H + +#include "gpservice_interface.h" +#include "samlloginwindow.h" +#include +#include +#include + +QT_BEGIN_NAMESPACE +namespace Ui { class GPClient; } +QT_END_NAMESPACE + +class GPClient : public QMainWindow +{ + Q_OBJECT + +public: + GPClient(QWidget *parent = nullptr); + ~GPClient(); + +signals: + void connectFailed(); + +private slots: + void on_connectButton_clicked(); + void preloginResultFinished(); + + void onLoginSuccess(QJsonObject loginResult); + + void onVPNConnected(); + void onVPNDisconnected(); + void onVPNLogAvailable(QString log); + +private: + Ui::GPClient *ui; + SAMLLoginWindow *loginWindow; + QNetworkAccessManager *networkManager; + QNetworkReply *reply; + com::yuezk::qt::GPService *vpn; + + void samlLogin(const QString portal); +}; +#endif // GPCLIENT_H diff --git a/GPClient/gpclient.ui b/GPClient/gpclient.ui new file mode 100644 index 0000000..7fe0221 --- /dev/null +++ b/GPClient/gpclient.ui @@ -0,0 +1,82 @@ + + + GPClient + + + + 0 + 0 + 258 + 316 + + + + GPClient + + + + + 0 + 0 + + + + Qt::LeftToRight + + + + 15 + + + 15 + + + 15 + + + 15 + + + + + + + TextLabel + + + + + + + + + 0 + + + + + vpn.microstrategy.com + + + + + + + + 0 + 0 + + + + Connect + + + + + + + + + + + diff --git a/GPClient/gpservice_interface.cpp b/GPClient/gpservice_interface.cpp new file mode 100644 index 0000000..12ed693 --- /dev/null +++ b/GPClient/gpservice_interface.cpp @@ -0,0 +1,25 @@ +/* + * This file was generated by qdbusxml2cpp version 0.8 + * Command line was: qdbusxml2cpp -i gpservice_interface.h -p :gpservice_interface.cpp ../GPService/gpservice.xml + * + * qdbusxml2cpp is Copyright (C) 2020 The Qt Company Ltd. + * + * This is an auto-generated file. + * This file may have been hand-edited. Look for HAND-EDIT comments + * before re-generating it. + */ + +#include "gpservice_interface.h" +/* + * Implementation of interface class ComYuezkQtGPServiceInterface + */ + +ComYuezkQtGPServiceInterface::ComYuezkQtGPServiceInterface(const QString &service, const QString &path, const QDBusConnection &connection, QObject *parent) + : QDBusAbstractInterface(service, path, staticInterfaceName(), connection, parent) +{ +} + +ComYuezkQtGPServiceInterface::~ComYuezkQtGPServiceInterface() +{ +} + diff --git a/GPClient/gpservice_interface.h b/GPClient/gpservice_interface.h new file mode 100644 index 0000000..6340f47 --- /dev/null +++ b/GPClient/gpservice_interface.h @@ -0,0 +1,71 @@ +/* + * This file was generated by qdbusxml2cpp version 0.8 + * Command line was: qdbusxml2cpp -p gpservice_interface.h: ../GPService/gpservice.xml + * + * qdbusxml2cpp is Copyright (C) 2020 The Qt Company Ltd. + * + * This is an auto-generated file. + * Do not edit! All changes made to it will be lost. + */ + +#ifndef GPSERVICE_INTERFACE_H +#define GPSERVICE_INTERFACE_H + +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Proxy class for interface com.yuezk.qt.GPService + */ +class ComYuezkQtGPServiceInterface: public QDBusAbstractInterface +{ + Q_OBJECT +public: + static inline const char *staticInterfaceName() + { return "com.yuezk.qt.GPService"; } + +public: + ComYuezkQtGPServiceInterface(const QString &service, const QString &path, const QDBusConnection &connection, QObject *parent = nullptr); + + ~ComYuezkQtGPServiceInterface(); + +public Q_SLOTS: // METHODS + inline QDBusPendingReply<> connect(const QString &server, const QString &username, const QString &passwd) + { + QList argumentList; + argumentList << QVariant::fromValue(server) << QVariant::fromValue(username) << QVariant::fromValue(passwd); + return asyncCallWithArgumentList(QStringLiteral("connect"), argumentList); + } + + inline QDBusPendingReply<> disconnect() + { + QList argumentList; + return asyncCallWithArgumentList(QStringLiteral("disconnect"), argumentList); + } + + inline QDBusPendingReply status() + { + QList argumentList; + return asyncCallWithArgumentList(QStringLiteral("status"), argumentList); + } + +Q_SIGNALS: // SIGNALS + void connected(); + void disconnected(); + void logAvailable(const QString &log); +}; + +namespace com { + namespace yuezk { + namespace qt { + typedef ::ComYuezkQtGPServiceInterface GPService; + } + } +} +#endif diff --git a/GPClient/main.cpp b/GPClient/main.cpp new file mode 100644 index 0000000..65d45bb --- /dev/null +++ b/GPClient/main.cpp @@ -0,0 +1,11 @@ +#include "gpclient.h" + +#include + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + GPClient w; + w.show(); + return a.exec(); +} diff --git a/GPClient/samlloginwindow.cpp b/GPClient/samlloginwindow.cpp new file mode 100644 index 0000000..189baa1 --- /dev/null +++ b/GPClient/samlloginwindow.cpp @@ -0,0 +1,59 @@ +#include "samlloginwindow.h" + +#include + +SAMLLoginWindow::SAMLLoginWindow(QWidget *parent) + : QDialog(parent) +{ + setWindowTitle("SAML Login"); + resize(610, 406); + QVBoxLayout *verticalLayout = new QVBoxLayout(this); + webView = new EnhancedWebView(this); + webView->setUrl(QUrl("about:blank")); + verticalLayout->addWidget(webView); + + webView->initialize(); + QObject::connect(webView, &EnhancedWebView::responseReceived, this, &SAMLLoginWindow::onResponseReceived); +} + +SAMLLoginWindow::~SAMLLoginWindow() +{ + delete webView; +} + +void SAMLLoginWindow::closeEvent(QCloseEvent *event) +{ + event->accept(); + reject(); +} + +void SAMLLoginWindow::login(QString url) +{ + webView->load(QUrl(url)); +} + +void SAMLLoginWindow::onResponseReceived(QJsonObject params) +{ + QString type = params.value("type").toString(); + // Skip non-document response + if (type != "Document") { + return; + } + + QJsonObject response = params.value("response").toObject(); + QJsonObject headers = response.value("headers").toObject(); + + foreach (const QString& key, headers.keys()) { + if (key.startsWith("saml-") || key == "prelogin-cookie" || key == "portal-userauthcookie") { + samlResult.insert(key, headers.value(key)); + } + } + + // Check the SAML result + if (samlResult.contains("saml-username") + && (samlResult.contains("prelogin-cookie") || samlResult.contains("portal-userauthcookie"))) { + samlResult.insert("server", QUrl(response.value("url").toString()).authority()); + emit success(samlResult); + accept(); + } +} diff --git a/GPClient/samlloginwindow.h b/GPClient/samlloginwindow.h new file mode 100644 index 0000000..5e6dad1 --- /dev/null +++ b/GPClient/samlloginwindow.h @@ -0,0 +1,33 @@ +#ifndef SAMLLOGINWINDOW_H +#define SAMLLOGINWINDOW_H + +#include "enhancedwebview.h" + +#include +#include +#include + +class SAMLLoginWindow : public QDialog +{ + Q_OBJECT + +public: + explicit SAMLLoginWindow(QWidget *parent = nullptr); + ~SAMLLoginWindow(); + + void login(QString url); + +signals: + void success(QJsonObject samlResult); + +private slots: + void onResponseReceived(QJsonObject params); + +private: + EnhancedWebView *webView; + QJsonObject samlResult; + + void closeEvent(QCloseEvent *event); +}; + +#endif // SAMLLOGINWINDOW_H diff --git a/GPService/.qmake.stash b/GPService/.qmake.stash new file mode 100644 index 0000000..d389915 --- /dev/null +++ b/GPService/.qmake.stash @@ -0,0 +1,21 @@ +QMAKE_CXX.QT_COMPILER_STDCXX = 201402L +QMAKE_CXX.QMAKE_GCC_MAJOR_VERSION = 9 +QMAKE_CXX.QMAKE_GCC_MINOR_VERSION = 2 +QMAKE_CXX.QMAKE_GCC_PATCH_VERSION = 0 +QMAKE_CXX.COMPILER_MACROS = \ + QT_COMPILER_STDCXX \ + QMAKE_GCC_MAJOR_VERSION \ + QMAKE_GCC_MINOR_VERSION \ + QMAKE_GCC_PATCH_VERSION +QMAKE_CXX.INCDIRS = \ + /usr/include/c++/9.2.0 \ + /usr/include/c++/9.2.0/x86_64-pc-linux-gnu \ + /usr/include/c++/9.2.0/backward \ + /usr/lib/gcc/x86_64-pc-linux-gnu/9.2.0/include \ + /usr/local/include \ + /usr/lib/gcc/x86_64-pc-linux-gnu/9.2.0/include-fixed \ + /usr/include +QMAKE_CXX.LIBDIRS = \ + /usr/lib/gcc/x86_64-pc-linux-gnu/9.2.0 \ + /usr/lib \ + /lib diff --git a/GPService/GPService b/GPService/GPService new file mode 100755 index 0000000..b387ae3 Binary files /dev/null and b/GPService/GPService differ diff --git a/GPService/GPService.pro b/GPService/GPService.pro new file mode 100644 index 0000000..eb0b3a3 --- /dev/null +++ b/GPService/GPService.pro @@ -0,0 +1,30 @@ +QT += dbus +QT -= gui + +CONFIG += c++11 console +CONFIG -= app_bundle + +# The following define makes your compiler emit warnings if you use +# any Qt feature that has been marked deprecated (the exact warnings +# depend on your compiler). Please consult the documentation of the +# deprecated API in order to know how to port your code away from it. +DEFINES += QT_DEPRECATED_WARNINGS + +# You can also make your code fail to compile if it uses deprecated APIs. +# In order to do so, uncomment the following line. +# You can also select to disable deprecated APIs only up to a certain version of Qt. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +HEADERS += \ + gpservice.h + +SOURCES += \ + gpservice.cpp \ + main.cpp + +DBUS_ADAPTORS += gpservice.xml + +# Default rules for deployment. +qnx: target.path = /tmp/$${TARGET}/bin +else: unix:!android: target.path = /opt/$${TARGET}/bin +!isEmpty(target.path): INSTALLS += target diff --git a/GPService/gpservice.cpp b/GPService/gpservice.cpp new file mode 100644 index 0000000..f0caf26 --- /dev/null +++ b/GPService/gpservice.cpp @@ -0,0 +1,235 @@ +#include "gpservice.h" +#include "gpservice_adaptor.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +struct { + uid_t tun_owner; + gid_t tun_group; +} tun_user; + +class SandboxProcess : public QProcess +{ +protected: + void setupChildProcess() override; +}; + +void SandboxProcess::setupChildProcess() +{ + /*if (initgroups (NM_OPENCONNECT_USER, tun_user.tun_group) || + setgid (tun_user.tun_group) || + setuid (tun_user.tun_owner)) { + qDebug() << "Failed to drop privileges when spawning openconnect"; + }*/ +} + +GPService::GPService(QObject *parent) + : QObject(parent) + , openconnect(new SandboxProcess) +{ + new GPServiceAdaptor(this); + QDBusConnection dbus = QDBusConnection::systemBus(); + dbus.registerObject("/", this); + dbus.registerService("com.yuezk.qt.GPService"); +} + +void GPService::connect(QString server, QString username, QString passwd) +{ + qDebug() << server << username << passwd; + + if (status() != QProcess::NotRunning) { + log("Openconnect has already started on PID " + QString::number(openconnect->processId()) + ", nothing changed."); + return; + } + + QString bin = findBinary(); + + if (bin == nullptr) { + log("Could not found openconnect binary, make sure openconnect is installed, exiting."); + return; + } + + char *tunName = "tun0"; // createPersistentTundev(); + // Failed to create device + if (tunName == nullptr) { + log("Could not create tun, exiting."); + return; + } + + qDebug() << tunName; + + // openconnect --protocol=gp -i vpn0 -s 'sudo -E /etc/vpnc/vpnc-script' -u "zyue@microstrategy.com" --passwd-on-stdin "https://vpn.microstrategy.com/gateway:prelogin-cookie" + QStringList args; + args << "--protocol=gp" + // << "-i" << tunName + // << "-s" << "sudo -E /etc/vpnc/vpnc-script" + // << "-U" << NM_OPENCONNECT_USER + << "-u" << username + << "--passwd-on-stdin" + << server; + + openconnect->start(bin, args); + openconnect->write(passwd.toUtf8()); + openconnect->closeWriteChannel(); + + QObject::connect(openconnect, &QProcess::started, [this]() { + log("Openconnect started successfully, PID=" + QString::number(openconnect->processId())); + }); + + QObject::connect(openconnect, &QProcess::errorOccurred, [tunName, this](QProcess::ProcessError error) { + log("Error occurred: Openconnect started failed"); + destroyPersistentTundev(tunName); + emit disconnected(); + }); + + QObject::connect(openconnect, &QProcess::readyReadStandardOutput, [this] () { + QString output = openconnect->readAllStandardOutput(); + + log(output); + if (output.startsWith("Connected as")) { + emit connected(); + } + }); + + QObject::connect(openconnect, &QProcess::readyReadStandardError, [this] () { + log(openconnect->readAllStandardError()); + }); + + QObject::connect(openconnect, QOverload::of(&QProcess::finished), [tunName, this](int exitCode, QProcess::ExitStatus exitStatus) { + log("Openconnect process exited with code " + QString::number(exitCode) + " and exit status " + QVariant::fromValue(exitStatus).toString()); + destroyPersistentTundev(tunName); + emit disconnected(); + }); +} + +void GPService::disconnect() +{ + if (openconnect->state() != QProcess::NotRunning) { + openconnect->terminate(); + } +} + +int GPService::status() +{ + return openconnect->state(); +} + +void GPService::log(QString msg) +{ + // 2020-02-12 15:33:45.120: log messsage + QString record = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz") + ": " + msg; + qDebug() << record; + emit logAvailable(record); +} + +QString GPService::findBinary() +{ + for (int i = 0; i < binaryPaths->length(); i++) { + if (QFileInfo::exists(binaryPaths[i])) { + return binaryPaths[i]; + } + } + + return nullptr; +} + +char *GPService::createPersistentTundev() +{ + struct passwd *pw; + struct ifreq ifr; + int fd; + int i; + + pw = getpwnam(NM_OPENCONNECT_USER); + if (!pw) + return nullptr; + + tun_user.tun_owner = pw->pw_uid; + tun_user.tun_group = pw->pw_gid; + + fd = open("/dev/net/tun", O_RDWR); + if (fd < 0) { + qDebug("Failed to open /dev/net/tun"); + return nullptr; + } + + memset(&ifr, 0, sizeof(ifr)); + ifr.ifr_flags = IFF_TUN | IFF_NO_PI; + + for (i = 0; i < 256; i++) { + sprintf(ifr.ifr_name, "gpvpn%d", i); + + int retcode = ioctl(fd, TUNSETIFF, (void *)&ifr); + + if (!retcode) { + break; + } + } + + if (i == 256) { + qDebug("Failed to create tun"); + return nullptr; + } + + if (ioctl(fd, TUNSETOWNER, tun_user.tun_owner) < 0) { + qDebug("TUNSETOWNER"); + return nullptr; + } + + if (ioctl(fd, TUNSETPERSIST, 1)) { + qDebug("TUNSETPERSIST"); + return nullptr; + } + close(fd); + qDebug("Created tundev %s\n", ifr.ifr_name); + return strdup(ifr.ifr_name); +} + +void GPService::destroyPersistentTundev(char *tun_name) +{ + struct ifreq ifr; + int fd; + + fd = open("/dev/net/tun", O_RDWR); + if (fd < 0) { + qDebug() << "Failed to open /dev/net/tun"; + return; + } + + memset(&ifr, 0, sizeof(ifr)); + ifr.ifr_flags = IFF_TUN | IFF_NO_PI; + strcpy(ifr.ifr_name, tun_name); + + if (ioctl(fd, TUNSETIFF, (void *)&ifr) < 0) { + qDebug() << "TUNSETIFF"; + return; + } + + if (ioctl(fd, TUNSETPERSIST, 0)) { + qDebug() << "TUNSETPERSIST"; + return; + } + + qDebug() << "Destroyed tundev %s\n" << tun_name; + close(fd); +} diff --git a/GPService/gpservice.h b/GPService/gpservice.h new file mode 100644 index 0000000..3029986 --- /dev/null +++ b/GPService/gpservice.h @@ -0,0 +1,44 @@ +#ifndef GLOBALPROTECTSERVICE_H +#define GLOBALPROTECTSERVICE_H + +#include +#include + +#define NM_OPENCONNECT_USER "nm-openconnect" + +static const QString binaryPaths[] { + "/usr/bin/openconnect", + "/usr/sbin/openconnect", + "/usr/local/bin/openconnect", + "/usr/local/sbin/openconnect", + "/opt/bin/openconnect", + "/opt/sbin/openconnect" +}; + +class GPService : public QObject +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "com.yuezk.qt.GPService") +public: + explicit GPService(QObject *parent = nullptr); + +signals: + void connected(); + void disconnected(); + void logAvailable(QString log); + +public slots: + void connect(QString server, QString username, QString passwd); + void disconnect(); + int status(); + +private: + QProcess *openconnect; + + void log(QString msg); + static QString findBinary(); + static char *createPersistentTundev(); + static void destroyPersistentTundev(char *tun_name); +}; + +#endif // GLOBALPROTECTSERVICE_H diff --git a/GPService/gpservice.xml b/GPService/gpservice.xml new file mode 100644 index 0000000..de8b11d --- /dev/null +++ b/GPService/gpservice.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/GPService/gpservice_adaptor.cpp b/GPService/gpservice_adaptor.cpp new file mode 100644 index 0000000..d23923a --- /dev/null +++ b/GPService/gpservice_adaptor.cpp @@ -0,0 +1,55 @@ +/* + * This file was generated by qdbusxml2cpp version 0.8 + * Command line was: qdbusxml2cpp -i gpservice_adaptor.h -a :gpservice_adaptor.cpp gpservice.xml + * + * qdbusxml2cpp is Copyright (C) 2020 The Qt Company Ltd. + * + * This is an auto-generated file. + * Do not edit! All changes made to it will be lost. + */ + +#include "gpservice_adaptor.h" +#include +#include +#include +#include +#include +#include +#include + +/* + * Implementation of adaptor class GPServiceAdaptor + */ + +GPServiceAdaptor::GPServiceAdaptor(QObject *parent) + : QDBusAbstractAdaptor(parent) +{ + // constructor + setAutoRelaySignals(true); +} + +GPServiceAdaptor::~GPServiceAdaptor() +{ + // destructor +} + +void GPServiceAdaptor::connect(const QString &server, const QString &username, const QString &passwd) +{ + // handle method call com.yuezk.qt.GPService.connect + QMetaObject::invokeMethod(parent(), "connect", Q_ARG(QString, server), Q_ARG(QString, username), Q_ARG(QString, passwd)); +} + +void GPServiceAdaptor::disconnect() +{ + // handle method call com.yuezk.qt.GPService.disconnect + QMetaObject::invokeMethod(parent(), "disconnect"); +} + +int GPServiceAdaptor::status() +{ + // handle method call com.yuezk.qt.GPService.status + int out0; + QMetaObject::invokeMethod(parent(), "status", Q_RETURN_ARG(int, out0)); + return out0; +} + diff --git a/GPService/gpservice_adaptor.h b/GPService/gpservice_adaptor.h new file mode 100644 index 0000000..c823e90 --- /dev/null +++ b/GPService/gpservice_adaptor.h @@ -0,0 +1,66 @@ +/* + * This file was generated by qdbusxml2cpp version 0.8 + * Command line was: qdbusxml2cpp -a gpservice_adaptor.h: gpservice.xml + * + * qdbusxml2cpp is Copyright (C) 2020 The Qt Company Ltd. + * + * This is an auto-generated file. + * This file may have been hand-edited. Look for HAND-EDIT comments + * before re-generating it. + */ + +#ifndef GPSERVICE_ADAPTOR_H +#define GPSERVICE_ADAPTOR_H + +#include +#include +QT_BEGIN_NAMESPACE +class QByteArray; +template class QList; +template class QMap; +class QString; +class QStringList; +class QVariant; +QT_END_NAMESPACE + +/* + * Adaptor class for interface com.yuezk.qt.GPService + */ +class GPServiceAdaptor: public QDBusAbstractAdaptor +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "com.yuezk.qt.GPService") + Q_CLASSINFO("D-Bus Introspection", "" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" + "") +public: + GPServiceAdaptor(QObject *parent); + virtual ~GPServiceAdaptor(); + +public: // PROPERTIES +public Q_SLOTS: // METHODS + void connect(const QString &server, const QString &username, const QString &passwd); + void disconnect(); + int status(); +Q_SIGNALS: // SIGNALS + void connected(); + void disconnected(); + void logAvailable(const QString &log); +}; + +#endif diff --git a/GPService/main.cpp b/GPService/main.cpp new file mode 100644 index 0000000..deaaa5b --- /dev/null +++ b/GPService/main.cpp @@ -0,0 +1,19 @@ +#include +#include + +#include "gpservice.h" + +int main(int argc, char *argv[]) +{ + QCoreApplication a(argc, argv); + + if (!QDBusConnection::systemBus().isConnected()) { + qWarning("Cannot connect to the D-Bus session bus.\n" + "Please check your system settings and try again.\n"); + return 1; + } + + new GPService; + + return a.exec(); +} diff --git a/GlobalProtect-openconnect.pro b/GlobalProtect-openconnect.pro new file mode 100644 index 0000000..00483f6 --- /dev/null +++ b/GlobalProtect-openconnect.pro @@ -0,0 +1,5 @@ +TEMPLATE = subdirs + +SUBDIRS += \ + GPClient \ + GPService