diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c7a4225..78d0262 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -31,6 +31,8 @@ jobs: - name: Build run: | ./scripts/install-ubuntu.sh + # assert no library missing + test $(ldd $(which gpclient) | grep 'not found' | wc -l) -eq 0 snapshot-archive-all: if: ${{ github.event_name != 'pull_request' && github.ref == 'refs/heads/develop' }} diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index d10a034..3a2c2d6 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -17,7 +17,7 @@ jobs: strategy: matrix: os: [ubuntu-18.04, ubuntu-20.04] - + runs-on: ${{ matrix.os }} steps: @@ -28,4 +28,6 @@ jobs: - name: Build run: | - ./scripts/install-ubuntu.sh \ No newline at end of file + ./scripts/install-ubuntu.sh + # assert no library missing + test $(ldd $(which gpclient) | grep 'not found' | wc -l) -eq 0 diff --git a/.gitmodules b/.gitmodules index 45a9506..706e889 100644 --- a/.gitmodules +++ b/.gitmodules @@ -5,3 +5,6 @@ [submodule "plog"] path = 3rdparty/plog url = https://github.com/SergiusTheBest/plog.git +[submodule "3rdparty/qtkeychain"] + path = 3rdparty/qtkeychain + url = git@github.com:frankosterfeld/qtkeychain.git diff --git a/3rdparty/qtkeychain b/3rdparty/qtkeychain new file mode 160000 index 0000000..f197cdb --- /dev/null +++ b/3rdparty/qtkeychain @@ -0,0 +1 @@ +Subproject commit f197cdb935b0cfd9881fdc6860874cb8379d1238 diff --git a/GPClient/CMakeLists.txt b/GPClient/CMakeLists.txt index 578e1fd..e31b75d 100644 --- a/GPClient/CMakeLists.txt +++ b/GPClient/CMakeLists.txt @@ -64,6 +64,17 @@ add_3rdparty( -DCMAKE_CXX_FLAGS_RELEASE=${CMAKE_CXX_FLAGS_RELEASE} ) +add_3rdparty( + qtkeychain + GIT_REPOSITORY https://github.com/frankosterfeld/qtkeychain.git + GIT_TAG master + CMAKE_ARGS + -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} + -DCMAKE_CXX_FLAGS_RELEASE=${CMAKE_CXX_FLAGS_RELEASE} + -DCMAKE_FIND_ROOT_PATH=${CMAKE_FIND_ROOT_PATH} + -DCMAKE_PREFIX_PATH=$ENV{CMAKE_PREFIX_PATH} +) + ExternalProject_Get_Property(SingleApplication-${PROJECT_NAME} SOURCE_DIR BINARY_DIR) set(SingleApplication_INCLUDE_DIR ${SOURCE_DIR}) set(SingleApplication_LIBRARY ${BINARY_DIR}/libSingleApplication.a) @@ -71,7 +82,16 @@ set(SingleApplication_LIBRARY ${BINARY_DIR}/libSingleApplication.a) ExternalProject_Get_Property(plog-${PROJECT_NAME} SOURCE_DIR) set(plog_INCLUDE_DIR "${SOURCE_DIR}/include") -add_dependencies(gpclient SingleApplication-${PROJECT_NAME} plog-${PROJECT_NAME}) +ExternalProject_Get_Property(qtkeychain-${PROJECT_NAME} SOURCE_DIR BINARY_DIR) +set(qtkeychain_INCLUDE_DIR "${SOURCE_DIR}") +set(qtkeychain_BINARY_DIR "${BINARY_DIR}") +set(qtkeychain_LIBRARY ${BINARY_DIR}/libqt5keychain.so) + +add_dependencies(gpclient + SingleApplication-${PROJECT_NAME} + plog-${PROJECT_NAME} + qtkeychain-${PROJECT_NAME} +) target_include_directories(gpclient PRIVATE ${CMAKE_BINARY_DIR} @@ -79,10 +99,13 @@ target_include_directories(gpclient PRIVATE ${CMAKE_CURRENT_BINARY_DIR} ${SingleApplication_INCLUDE_DIR} ${plog_INCLUDE_DIR} + ${qtkeychain_INCLUDE_DIR} + ${qtkeychain_BINARY_DIR} ) target_link_libraries(gpclient ${SingleApplication_LIBRARY} + ${qtkeychain_LIBRARY} Qt5::Widgets Qt5::Network Qt5::WebSockets diff --git a/GPClient/gpclient.cpp b/GPClient/gpclient.cpp index 23c77f5..a68d7c6 100644 --- a/GPClient/gpclient.cpp +++ b/GPClient/gpclient.cpp @@ -61,12 +61,14 @@ void GPClient::setupSettings() void GPClient::onSettingsButtonClicked() { settingsDialog->setClientos(settings::get("clientos", "Linux").toString()); + settingsDialog->setOsVersion(settings::get("os-version", QSysInfo::prettyProductName()).toString()); settingsDialog->show(); } void GPClient::onSettingsAccepted() { settings::save("clientos", settingsDialog->clientos()); + settings::save("os-version", settingsDialog->osVersion()); } void GPClient::on_connectButton_clicked() @@ -438,8 +440,14 @@ bool GPClient::connected() const QList GPClient::allGateways() const { - const QString gatewaysJson = settings::get(portal() + "_gateways").toString(); - return GPGateway::fromJson(gatewaysJson); + + QList gateways; + + for (auto g :settings::get_all("_gateways$") ){ + + gateways.append(GPGateway::fromJson(settings::get(g).toString())); + } + return gateways; } void GPClient::setAllGateways(QList gateways) @@ -467,6 +475,7 @@ void GPClient::setCurrentGateway(const GPGateway gateway) LOGI << "Updating the current gateway to " << gateway.name(); settings::save(portal() + "_selectedGateway", gateway.name()); + ui->portalInput->setText(gateway.address()); populateGatewayMenu(); } diff --git a/GPClient/gphelper.cpp b/GPClient/gphelper.cpp index 08a5152..f38d2a8 100644 --- a/GPClient/gphelper.cpp +++ b/GPClient/gphelper.cpp @@ -9,9 +9,12 @@ #include #include #include +#include #include "gphelper.h" +using namespace QKeychain; + QNetworkAccessManager* gpclient::helper::networkManager = new QNetworkAccessManager; QNetworkReply* gpclient::helper::createRequest(QString url, QByteArray params) @@ -115,6 +118,12 @@ QVariant gpclient::helper::settings::get(const QString &key, const QVariant &def return _settings->value(key, defaultValue); } +QStringList gpclient::helper::settings::get_all(const QString &key, const QVariant &defaultValue) +{ + QRegularExpression re(key); + return _settings->allKeys().filter(re); +} + void gpclient::helper::settings::save(const QString &key, const QVariant &value) { _settings->setValue(key, value); @@ -132,3 +141,38 @@ void gpclient::helper::settings::clear() QWebEngineProfile::defaultProfile()->cookieStore()->deleteAllCookies(); } + + +bool gpclient::helper::settings::secureSave(const QString &key, const QString &value) { + WritePasswordJob job( QLatin1String("gpclient") ); + job.setAutoDelete( false ); + job.setKey( key ); + job.setTextData( value ); + QEventLoop loop; + job.connect( &job, SIGNAL(finished(QKeychain::Job*)), &loop, SLOT(quit()) ); + job.start(); + loop.exec(); + if ( job.error() ) { + return false; + } + + return true; +} + +bool gpclient::helper::settings::secureGet(const QString &key, QString &value) { + ReadPasswordJob job( QLatin1String("gpclient") ); + job.setAutoDelete( false ); + job.setKey( key ); + QEventLoop loop; + job.connect( &job, SIGNAL(finished(QKeychain::Job*)), &loop, SLOT(quit()) ); + job.start(); + loop.exec(); + + const QString pw = job.textData(); + if ( job.error() ) { + return false; + } + + value = pw; + return true; +} \ No newline at end of file diff --git a/GPClient/gphelper.h b/GPClient/gphelper.h index fb74899..634f82e 100644 --- a/GPClient/gphelper.h +++ b/GPClient/gphelper.h @@ -34,8 +34,12 @@ namespace gpclient { static const QStringList reservedKeys {"extraArgs", "clientos"}; QVariant get(const QString &key, const QVariant &defaultValue = QVariant()); + QStringList get_all(const QString &key, const QVariant &defaultValue = QVariant()); void save(const QString &key, const QVariant &value); void clear(); + + bool secureSave(const QString &key, const QString &value); + bool secureGet(const QString &key, QString &value); } } } diff --git a/GPClient/loginparams.cpp b/GPClient/loginparams.cpp index 3126231..21ea259 100644 --- a/GPClient/loginparams.cpp +++ b/GPClient/loginparams.cpp @@ -1,6 +1,9 @@ #include #include "loginparams.h" +#include "gphelper.h" + +using namespace gpclient::helper; LoginParams::LoginParams(const QString clientos) { @@ -14,13 +17,18 @@ LoginParams::LoginParams(const QString clientos) params.addQueryItem("ok", "Login"); params.addQueryItem("direct", "yes"); params.addQueryItem("clientVer", "4100"); - params.addQueryItem("os-version", QUrl::toPercentEncoding(QSysInfo::prettyProductName())); // add the clientos parameter if not empty if (!clientos.isEmpty()) { params.addQueryItem("clientos", clientos); } + auto osVersion = settings::get("os-version", "").toString(); + if (osVersion.isEmpty()) { + osVersion = QSysInfo::prettyProductName(); + } + params.addQueryItem("os-version", QUrl::toPercentEncoding(osVersion)); + params.addQueryItem("portal-userauthcookie", ""); params.addQueryItem("portal-prelogonuserauthcookie", ""); params.addQueryItem("prelogin-cookie", ""); diff --git a/GPClient/settingsdialog.cpp b/GPClient/settingsdialog.cpp index e2e28d4..51e230f 100644 --- a/GPClient/settingsdialog.cpp +++ b/GPClient/settingsdialog.cpp @@ -32,3 +32,11 @@ QString SettingsDialog::clientos() { return ui->clientosInput->text(); } + +void SettingsDialog::setOsVersion(QString osVersion) { + ui->osVersionInput->setText(osVersion); +} + +QString SettingsDialog::osVersion() { + return ui->osVersionInput->text(); +} diff --git a/GPClient/settingsdialog.h b/GPClient/settingsdialog.h index 9e29e48..ab2a607 100644 --- a/GPClient/settingsdialog.h +++ b/GPClient/settingsdialog.h @@ -21,6 +21,9 @@ public: void setClientos(QString clientos); QString clientos(); + void setOsVersion(QString osVersion); + QString osVersion(); + private: Ui::SettingsDialog *ui; }; diff --git a/GPClient/settingsdialog.ui b/GPClient/settingsdialog.ui index 7a595f3..ba27742 100644 --- a/GPClient/settingsdialog.ui +++ b/GPClient/settingsdialog.ui @@ -7,7 +7,7 @@ 0 0 488 - 177 + 220 @@ -44,7 +44,7 @@ - Value of "clientos": + clientos: @@ -55,7 +55,7 @@ - + Qt::Horizontal @@ -65,6 +65,16 @@ + + + + + + + os-version: + + + diff --git a/GPClient/standardloginwindow.cpp b/GPClient/standardloginwindow.cpp index bdd9aa4..7964d1c 100644 --- a/GPClient/standardloginwindow.cpp +++ b/GPClient/standardloginwindow.cpp @@ -2,6 +2,9 @@ #include "standardloginwindow.h" #include "ui_standardloginwindow.h" +#include "gphelper.h" + +using namespace gpclient::helper; StandardLoginWindow::StandardLoginWindow(const QString &portalAddress, const QString &labelUsername, const QString &labelPassword, const QString &authMessage) : @@ -13,11 +16,24 @@ StandardLoginWindow::StandardLoginWindow(const QString &portalAddress, const QSt ui->password->setPlaceholderText(labelPassword); ui->authMessage->setText(authMessage); + autocomplete(); + setWindowTitle("GlobalProtect Login"); setFixedSize(width(), height()); setModal(true); } +void StandardLoginWindow::autocomplete() { + QString username, password; + settings::secureGet("username", username); + settings::secureGet("password", password); + + if (!username.isEmpty() && !password.isEmpty()) { + ui->username->setText(username); + ui->password->setText(password); + } +} + void StandardLoginWindow::setProcessing(bool isProcessing) { ui->username->setReadOnly(isProcessing); ui->password->setReadOnly(isProcessing); @@ -32,6 +48,9 @@ void StandardLoginWindow::on_loginButton_clicked() { return; } + settings::secureSave("username", username); + settings::secureSave("password", password); + emit performLogin(username, password); } diff --git a/GPClient/standardloginwindow.h b/GPClient/standardloginwindow.h index 92f5f5d..894e7e3 100644 --- a/GPClient/standardloginwindow.h +++ b/GPClient/standardloginwindow.h @@ -28,6 +28,7 @@ private: Ui::StandardLoginWindow *ui; void closeEvent(QCloseEvent *event); + void autocomplete(); }; #endif // STANDARDLOGINWINDOW_H diff --git a/GPService/gpservice.cpp b/GPService/gpservice.cpp index 754cee9..40d06b8 100644 --- a/GPService/gpservice.cpp +++ b/GPService/gpservice.cpp @@ -199,7 +199,9 @@ void GPService::onProcessStdout() QString output = openconnect->readAllStandardOutput(); log(output); - if (output.indexOf("Connected as") >= 0 || output.indexOf("Configured as") >= 0) { + if (output.indexOf("Connected as") >= 0 || + output.indexOf("Configured as") >= 0 || + output.indexOf("Configurado como") >= 0) { vpnStatus = GPService::VpnConnected; emit connected(); } diff --git a/README.md b/README.md index 50bfafb..5ae12b4 100644 --- a/README.md +++ b/README.md @@ -97,6 +97,14 @@ git clone https://github.com/yuezk/GlobalProtect-openconnect.git cd GlobalProtect-openconnect ``` +### MX Linux +The following instructions are for **MX-21.2.1_x64 KDE**. + +```sh +sudo apt install qttools5-dev libsecret-1-dev libqt5keychain1 +./scripts/install-debian.sh +``` + ### Ubuntu/Mint > **⚠️ REQUIRED for Ubuntu 18.04 ⚠️** @@ -138,6 +146,7 @@ Install the Qt5 dependencies and OpenConnect: - QtWebSockets - QtDBus - openconnect v8.x +- qtkeychain ...then build and install with: diff --git a/scripts/install-debian.sh b/scripts/install-debian.sh new file mode 100755 index 0000000..74ebe20 --- /dev/null +++ b/scripts/install-debian.sh @@ -0,0 +1,14 @@ +#!/bin/bash -e + +sudo apt-get update +sudo apt-get install -y \ + build-essential \ + qtbase5-dev \ + libqt5websockets5-dev \ + qtwebengine5-dev \ + qttools5-dev \ + libsecret-1-dev \ + openconnect \ + libqt5keychain1 + +./scripts/install.sh diff --git a/scripts/install-fedora.sh b/scripts/install-fedora.sh index 37bab58..ee71cec 100755 --- a/scripts/install-fedora.sh +++ b/scripts/install-fedora.sh @@ -4,6 +4,7 @@ sudo dnf install -y \ qt5-qtbase-devel \ qt5-qtwebengine-devel \ qt5-qtwebsockets-devel \ - openconnect + openconnect \ + qtkeychain -./scripts/install.sh \ No newline at end of file +./scripts/install.sh diff --git a/scripts/install-opensuse.sh b/scripts/install-opensuse.sh index 3bd78c5..c4651f3 100755 --- a/scripts/install-opensuse.sh +++ b/scripts/install-opensuse.sh @@ -4,6 +4,7 @@ sudo zypper install -y \ libqt5-qtbase-devel \ libqt5-qtwebsockets-devel \ libqt5-qtwebengine-devel \ - openconnect + openconnect \ + qtkeychain-qt5 -./scripts/install.sh \ No newline at end of file +./scripts/install.sh diff --git a/scripts/install-ubuntu.sh b/scripts/install-ubuntu.sh index c4ebe68..7f12c36 100755 --- a/scripts/install-ubuntu.sh +++ b/scripts/install-ubuntu.sh @@ -6,6 +6,8 @@ sudo apt-get install -y \ qtbase5-dev \ libqt5websockets5-dev \ qtwebengine5-dev \ - openconnect + qttools5-dev \ + openconnect \ + libqt5keychain1 ./scripts/install.sh