mirror of
https://github.com/yuezk/GlobalProtect-openconnect.git
synced 2025-05-20 07:26:58 -04:00
Compare commits
27 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
51c55376e8 | ||
|
26d5d5bcf0 | ||
|
b99053718a | ||
|
6e603c84b3 | ||
|
eeb60125e6 | ||
|
875c463bc2 | ||
|
8cc73df3d6 | ||
|
fd3ff7b0de | ||
|
72a83f12d0 | ||
|
ed7c8ca1a1 | ||
|
f71e29de5c | ||
|
a641453388 | ||
|
366b95ce1f | ||
|
136c870d1f | ||
|
0c411a542f | ||
|
1e70dd088f | ||
|
2dc7f97ff0 | ||
|
6542d677a9 | ||
|
222fe26cea | ||
|
ed413ee029 | ||
|
516d685c9e | ||
|
54e2371022 | ||
|
511cc5ebd7 | ||
|
30bec40338 | ||
|
0bb9353a11 | ||
|
80134f5a2b | ||
|
57e20fe478 |
47
.github/workflows/build.yaml
vendored
47
.github/workflows/build.yaml
vendored
@@ -14,6 +14,11 @@ on:
|
||||
- release/*
|
||||
tags:
|
||||
- v*.*.*
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
# Include arm64 if ref is a tag
|
||||
setup-matrix:
|
||||
@@ -62,6 +67,47 @@ jobs:
|
||||
path: |
|
||||
source/gp/.build/tarball/*.tar.gz
|
||||
|
||||
tarball-offline:
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- tarball
|
||||
steps:
|
||||
- uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
|
||||
- name: Prepare workspace
|
||||
run: rm -rf source-offline && mkdir source-offline
|
||||
|
||||
- name: Download tarball
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: artifact-source
|
||||
path: source-offline
|
||||
|
||||
- name: Create offline tarball
|
||||
run: |
|
||||
cd source-offline
|
||||
|
||||
offline_tarball=$(basename *.tar.gz .tar.gz).offline.tar.gz
|
||||
|
||||
# Extract the tarball
|
||||
tar -xzf *.tar.gz
|
||||
|
||||
cd */
|
||||
make tarball OFFLINE=1
|
||||
|
||||
# Rename the tarball to .offline.tar.gz
|
||||
mv -v .build/tarball/*.tar.gz ../$offline_tarball
|
||||
|
||||
- name: Upload offline tarball
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
path: source-offline/*.offline.tar.gz
|
||||
name: artifact-source-offline
|
||||
if-no-files-found: error
|
||||
|
||||
build-gp:
|
||||
needs:
|
||||
- setup-matrix
|
||||
@@ -162,6 +208,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- tarball
|
||||
- tarball-offline
|
||||
- build-gp
|
||||
- build-gpgui
|
||||
|
||||
|
25
.github/workflows/publish.yaml
vendored
25
.github/workflows/publish.yaml
vendored
@@ -52,38 +52,43 @@ jobs:
|
||||
version: 8
|
||||
- name: Prepare workspace
|
||||
run: rm -rf publish-ppa && mkdir publish-ppa
|
||||
- name: Download ${{ inputs.tag }} source code
|
||||
- name: Download ${{ inputs.tag }} offline source code
|
||||
uses: robinraju/release-downloader@v1.9
|
||||
with:
|
||||
token: ${{ secrets.GH_PAT }}
|
||||
tag: ${{ inputs.tag }}
|
||||
fileName: globalprotect-openconnect-*.tar.gz
|
||||
fileName: globalprotect-openconnect-*.offline.tar.gz
|
||||
tarBall: false
|
||||
zipBall: false
|
||||
out-file-path: publish-ppa
|
||||
- name: Make the offline tarball
|
||||
- name: Patch the source code
|
||||
run: |
|
||||
cd publish-ppa
|
||||
tar -xf globalprotect-openconnect-*.tar.gz
|
||||
cd globalprotect-openconnect-*/
|
||||
|
||||
make tarball OFFLINE=1
|
||||
# Rename the source tarball without the offline suffix
|
||||
mv *.tar.gz $(basename *.tar.gz .offline.tar.gz).tar.gz
|
||||
|
||||
# Extract the source tarball
|
||||
tar -xzf *.tar.gz
|
||||
|
||||
# Prepare the debian directory with custom files
|
||||
cd globalprotect-openconnect-*/
|
||||
|
||||
mkdir -p .build/debian
|
||||
sed 's/@RUST@/rust-all(>=1.70)/g' packaging/deb/control.in > .build/debian/control
|
||||
sed 's/@RUST@/rust-all(>=1.71)/g' packaging/deb/control.in > .build/debian/control
|
||||
sed 's/@OFFLINE@/1/g' packaging/deb/rules.in > .build/debian/rules
|
||||
cp packaging/deb/postrm .build/debian/postrm
|
||||
|
||||
- name: Publish to PPA
|
||||
uses: yuezk/publish-ppa-package@dev
|
||||
uses: yuezk/publish-ppa-package@gp_2.3.x
|
||||
with:
|
||||
repository: "yuezk/globalprotect-openconnect"
|
||||
gpg_private_key: ${{ secrets.PPA_GPG_PRIVATE_KEY }}
|
||||
gpg_passphrase: ${{ secrets.PPA_GPG_PASSPHRASE }}
|
||||
tarball: publish-ppa/globalprotect-openconnect-*/.build/tarball/*.tar.gz
|
||||
tarball: publish-ppa/globalprotect-openconnect-*.tar.gz
|
||||
debian_dir: publish-ppa/globalprotect-openconnect-*/.build/debian
|
||||
deb_email: "k3vinyue@gmail.com"
|
||||
deb_fullname: "Kevin Yue"
|
||||
extra_ppa: "liushuyu-011/rust-bpo-1.75"
|
||||
extra_ppa: "yuezk/globalprotect-openconnect liushuyu-011/rust-bpo-1.75"
|
||||
series: "bionic focal"
|
||||
revision: ${{ inputs.revision }}
|
||||
|
19
.github/workflows/release.yaml
vendored
19
.github/workflows/release.yaml
vendored
@@ -1,4 +1,4 @@
|
||||
name: Release Packages
|
||||
name: GH Release Packages
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
@@ -96,15 +96,16 @@ jobs:
|
||||
steps:
|
||||
- name: Prepare workspace
|
||||
run: rm -rf build-${{ matrix.package }} && mkdir -p build-${{ matrix.package }}
|
||||
|
||||
- name: Download ${{ inputs.tag }} source code
|
||||
uses: robinraju/release-downloader@v1.9
|
||||
with:
|
||||
token: ${{ secrets.GH_PAT }}
|
||||
tag: ${{ inputs.tag }}
|
||||
fileName: globalprotect-openconnect-*.tar.gz
|
||||
tarBall: false
|
||||
zipBall: false
|
||||
out-file-path: build-${{ matrix.package }}
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GH_PAT }}
|
||||
run: |
|
||||
gh -R yuezk/GlobalProtect-openconnect \
|
||||
release download ${{ inputs.tag }} \
|
||||
--pattern '*[^offline].tar.gz' \
|
||||
--dir build-${{ matrix.package }}
|
||||
|
||||
- name: Docker Login
|
||||
run: echo ${{ secrets.DOCKER_HUB_TOKEN }} | docker login -u ${{ secrets.DOCKER_HUB_USERNAME }} --password-stdin
|
||||
- name: Build ${{ matrix.package }} package in Docker
|
||||
|
2137
Cargo.lock
generated
2137
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
38
Cargo.toml
38
Cargo.toml
@@ -1,11 +1,17 @@
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
|
||||
members = ["crates/*", "apps/gpclient", "apps/gpservice", "apps/gpauth", "apps/gpgui-helper/src-tauri"]
|
||||
members = [
|
||||
"crates/*",
|
||||
"apps/gpclient",
|
||||
"apps/gpservice",
|
||||
"apps/gpauth",
|
||||
"apps/gpgui-helper/src-tauri",
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
rust-version = "1.70"
|
||||
version = "2.3.5"
|
||||
rust-version = "1.71.1"
|
||||
version = "2.3.11"
|
||||
authors = ["Kevin Yue <k3vinyue@gmail.com>"]
|
||||
homepage = "https://github.com/yuezk/GlobalProtect-openconnect"
|
||||
edition = "2021"
|
||||
@@ -13,43 +19,43 @@ license = "GPL-3.0"
|
||||
|
||||
[workspace.dependencies]
|
||||
anyhow = "1.0"
|
||||
base64 = "0.21"
|
||||
clap = { version = "4.4.2", features = ["derive"] }
|
||||
base64 = "0.22"
|
||||
clap = { version = "~4.4.2", features = ["derive"] }
|
||||
ctrlc = "3.4"
|
||||
directories = "5.0"
|
||||
dns-lookup = "2.0.4"
|
||||
env_logger = "0.10"
|
||||
env_logger = "0.11"
|
||||
is_executable = "1.0"
|
||||
log = "0.4"
|
||||
regex = "1"
|
||||
reqwest = { version = "0.11", features = ["native-tls-vendored", "json"] }
|
||||
openssl = "0.10"
|
||||
pem = "3"
|
||||
roxmltree = "0.18"
|
||||
roxmltree = "0.20"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
sysinfo = "0.29"
|
||||
sysinfo = "0.30"
|
||||
tempfile = "3.8"
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
tokio = { version = "1" }
|
||||
tokio-util = "0.7"
|
||||
url = "2.4"
|
||||
urlencoding = "2.1.3"
|
||||
axum = "0.7"
|
||||
futures = "0.3"
|
||||
futures-util = "0.3"
|
||||
tokio-tungstenite = "0.20.1"
|
||||
uzers = "0.11"
|
||||
tokio-tungstenite = "0.26.1"
|
||||
uzers = "0.12"
|
||||
whoami = "1"
|
||||
thiserror = "1"
|
||||
thiserror = "2"
|
||||
redact-engine = "0.1"
|
||||
compile-time = "0.2"
|
||||
serde_urlencoded = "0.7"
|
||||
md5="0.7"
|
||||
sha256="1"
|
||||
which="6"
|
||||
md5 = "0.7"
|
||||
sha256 = "1"
|
||||
which = "7"
|
||||
|
||||
# Tauri dependencies
|
||||
tauri = { version = "1.5" }
|
||||
tauri = { version = "1" }
|
||||
specta = "=2.0.0-rc.1"
|
||||
specta-macros = "=2.0.0-rc.1"
|
||||
rspc = { version = "1.0.0-rc.5", features = ["tauri"] }
|
||||
|
7
Makefile
7
Makefile
@@ -117,6 +117,10 @@ install:
|
||||
install -Dm755 .build/gpgui/gpgui_*/gpgui $(DESTDIR)/usr/bin/gpgui; \
|
||||
fi
|
||||
|
||||
# Install the disconnect hooks
|
||||
install -Dm755 packaging/files/usr/lib/NetworkManager/dispatcher.d/pre-down.d/gpclient.down $(DESTDIR)/usr/lib/NetworkManager/dispatcher.d/pre-down.d/gpclient.down
|
||||
install -Dm755 packaging/files/usr/lib/NetworkManager/dispatcher.d/gpclient-nm-hook $(DESTDIR)/usr/lib/NetworkManager/dispatcher.d/gpclient-nm-hook
|
||||
|
||||
install -Dm644 packaging/files/usr/share/applications/gpgui.desktop $(DESTDIR)/usr/share/applications/gpgui.desktop
|
||||
install -Dm644 packaging/files/usr/share/icons/hicolor/scalable/apps/gpgui.svg $(DESTDIR)/usr/share/icons/hicolor/scalable/apps/gpgui.svg
|
||||
install -Dm644 packaging/files/usr/share/icons/hicolor/32x32/apps/gpgui.png $(DESTDIR)/usr/share/icons/hicolor/32x32/apps/gpgui.png
|
||||
@@ -133,6 +137,9 @@ uninstall:
|
||||
rm -f $(DESTDIR)/usr/bin/gpgui-helper
|
||||
rm -f $(DESTDIR)/usr/bin/gpgui
|
||||
|
||||
rm -f $(DESTDIR)/usr/lib/NetworkManager/dispatcher.d/pre-down.d/gpclient.down
|
||||
rm -f $(DESTDIR)/usr/lib/NetworkManager/dispatcher.d/gpclient-nm-hook
|
||||
|
||||
rm -f $(DESTDIR)/usr/share/applications/gpgui.desktop
|
||||
rm -f $(DESTDIR)/usr/share/icons/hicolor/scalable/apps/gpgui.svg
|
||||
rm -f $(DESTDIR)/usr/share/icons/hicolor/32x32/apps/gpgui.png
|
||||
|
52
README.md
52
README.md
@@ -44,12 +44,20 @@ Options:
|
||||
See 'gpclient help <command>' for more information on a specific command.
|
||||
```
|
||||
|
||||
To use the default browser for authentication with the CLI version, you need to use the following command:
|
||||
To use the external browser for authentication with the CLI version, you need to use the following command:
|
||||
|
||||
```bash
|
||||
sudo -E gpclient connect --default-browser <portal>
|
||||
sudo -E gpclient connect --browser default <portal>
|
||||
```
|
||||
|
||||
Or you can try the following command if the above command does not work:
|
||||
|
||||
```bash
|
||||
gpauth <portal> --browser default 2>/dev/null | sudo gpclient connect <portal> --cookie-on-stdin
|
||||
```
|
||||
|
||||
You can specify the browser with the `--browser <browser>` option, e.g., `--browser firefox`, `--browser chrome`, etc.
|
||||
|
||||
### GUI
|
||||
|
||||
The GUI version is also available after you installed it. You can launch it from the application menu or run `gpclient launch-gui` in the terminal.
|
||||
@@ -77,17 +85,13 @@ sudo apt-get install globalprotect-openconnect
|
||||
|
||||
#### **Ubuntu 24.04 and later**
|
||||
|
||||
The `libwebkit2gtk-4.0-37` package was [removed](https://bugs.launchpad.net/ubuntu/+source/webkit2gtk/+bug/2061914) from its repo, before [the issue](https://github.com/yuezk/GlobalProtect-openconnect/issues/351) gets resolved, you need to install them manually:
|
||||
The `libwebkit2gtk-4.0-37` package was [removed](https://bugs.launchpad.net/ubuntu/+source/webkit2gtk/+bug/2061914) from its repo. You can use the [`deb-install.sh`](./scripts/deb-install.sh) script to install the package:
|
||||
|
||||
```bash
|
||||
wget http://launchpadlibrarian.net/704701349/libwebkit2gtk-4.0-37_2.43.3-1_amd64.deb
|
||||
wget http://launchpadlibrarian.net/704701345/libjavascriptcoregtk-4.0-18_2.43.3-1_amd64.deb
|
||||
|
||||
sudo dpkg --install *.deb
|
||||
curl -o- https://raw.githubusercontent.com/yuezk/GlobalProtect-openconnect/main/scripts/deb-install.sh \
|
||||
| bash -s -- 2.3.9
|
||||
```
|
||||
|
||||
And the latest package is not available in the PPA, you can follow the [Install from deb package](#install-from-deb-package) section to install the latest package.
|
||||
|
||||
#### **Ubuntu 18.04**
|
||||
|
||||
The latest package is not available in the PPA either, but you still needs to add the `ppa:yuezk/globalprotect-openconnect` repo beforehand to use the required `openconnect` package. Then you can follow the [Install from deb package](#install-from-deb-package) section to install the latest package.
|
||||
@@ -106,7 +110,7 @@ sudo apt install --fix-broken globalprotect-openconnect_*.deb
|
||||
|
||||
Install from AUR: [globalprotect-openconnect-git](https://aur.archlinux.org/packages/globalprotect-openconnect-git/)
|
||||
|
||||
```
|
||||
```bash
|
||||
yay -S globalprotect-openconnect-git
|
||||
```
|
||||
|
||||
@@ -124,7 +128,7 @@ sudo pacman -U globalprotect-openconnect-*.pkg.tar.zst
|
||||
|
||||
The package is available on [COPR](https://copr.fedorainfracloud.org/coprs/yuezk/globalprotect-openconnect/) for various RPM-based distributions. You can install it with the following commands:
|
||||
|
||||
```
|
||||
```bash
|
||||
sudo dnf copr enable yuezk/globalprotect-openconnect
|
||||
sudo dnf install globalprotect-openconnect
|
||||
```
|
||||
@@ -144,30 +148,16 @@ Download the latest RPM package from [releases](https://github.com/yuezk/GlobalP
|
||||
```bash
|
||||
sudo rpm -i globalprotect-openconnect-*.rpm
|
||||
```
|
||||
|
||||
### Gentoo
|
||||
|
||||
Install from the ```rios``` or ```slonko``` overlays. Example using rios:
|
||||
It is available via `guru` and `lamdness` overlays.
|
||||
|
||||
#### 1. Enable the overlay
|
||||
```bash
|
||||
sudo eselect repository enable guru
|
||||
sudo emerge -r guru sync
|
||||
sudo emerge -av net-vpn/globalprotect-openconnect
|
||||
```
|
||||
sudo eselect repository enable rios
|
||||
```
|
||||
|
||||
#### 2. Sync with the repository
|
||||
|
||||
- If you have eix installed, use it:
|
||||
```
|
||||
sudo eix-sync
|
||||
```
|
||||
- Otherwise, use:
|
||||
```
|
||||
sudo emerge --sync
|
||||
```
|
||||
|
||||
#### 3. Install
|
||||
|
||||
```sudo emerge globalprotect-openconnect```
|
||||
|
||||
|
||||
### Other distributions
|
||||
|
||||
|
@@ -1,5 +1,6 @@
|
||||
[package]
|
||||
name = "gpauth"
|
||||
authors.workspace = true
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
@@ -1,3 +1,5 @@
|
||||
use std::{env::temp_dir, fs, os::unix::fs::PermissionsExt};
|
||||
|
||||
use clap::Parser;
|
||||
use gpapi::{
|
||||
auth::{SamlAuthData, SamlAuthResult},
|
||||
@@ -11,36 +13,68 @@ use log::{info, LevelFilter};
|
||||
use serde_json::json;
|
||||
use tauri::{App, AppHandle, RunEvent};
|
||||
use tempfile::NamedTempFile;
|
||||
use tokio::{io::AsyncReadExt, net::TcpListener};
|
||||
|
||||
use crate::auth_window::{portal_prelogin, AuthWindow};
|
||||
|
||||
const VERSION: &str = concat!(env!("CARGO_PKG_VERSION"), " (", compile_time::date_str!(), ")");
|
||||
|
||||
#[derive(Parser, Clone)]
|
||||
#[command(version = VERSION)]
|
||||
#[command(
|
||||
version = VERSION,
|
||||
author,
|
||||
about = "The authentication component for the GlobalProtect VPN client, supports the SSO authentication method.",
|
||||
help_template = "\
|
||||
{before-help}{name} {version}
|
||||
{author}
|
||||
|
||||
{about}
|
||||
|
||||
{usage-heading} {usage}
|
||||
|
||||
{all-args}{after-help}
|
||||
|
||||
See 'gpauth -h' for more information.
|
||||
"
|
||||
)]
|
||||
struct Cli {
|
||||
#[arg(help = "The portal server to authenticate")]
|
||||
server: String,
|
||||
#[arg(long)]
|
||||
|
||||
#[arg(long, help = "Treating the server as a gateway")]
|
||||
gateway: bool,
|
||||
#[arg(long)]
|
||||
|
||||
#[arg(long, help = "The SAML authentication request")]
|
||||
saml_request: Option<String>,
|
||||
#[arg(long, default_value = GP_USER_AGENT)]
|
||||
|
||||
#[arg(long, default_value = GP_USER_AGENT, help = "The user agent to use")]
|
||||
user_agent: String,
|
||||
|
||||
#[arg(long, default_value = "Linux")]
|
||||
os: Os,
|
||||
|
||||
#[arg(long)]
|
||||
os_version: Option<String>,
|
||||
#[arg(long)]
|
||||
|
||||
#[arg(long, help = "The HiDPI mode, useful for high-resolution screens")]
|
||||
hidpi: bool,
|
||||
#[arg(long)]
|
||||
|
||||
#[arg(long, help = "Get around the OpenSSL `unsafe legacy renegotiation` error")]
|
||||
fix_openssl: bool,
|
||||
#[arg(long)]
|
||||
|
||||
#[arg(long, help = "Ignore TLS errors")]
|
||||
ignore_tls_errors: bool,
|
||||
#[arg(long)]
|
||||
|
||||
#[arg(long, help = "Clean the cache of the embedded browser")]
|
||||
clean: bool,
|
||||
#[arg(long)]
|
||||
|
||||
#[arg(long, help = "Use the default browser for authentication")]
|
||||
default_browser: bool,
|
||||
#[arg(long)]
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
help = "The browser to use for authentication, e.g., `default`, `firefox`, `chrome`, `chromium`, or the path to the browser executable"
|
||||
)]
|
||||
browser: Option<String>,
|
||||
}
|
||||
|
||||
@@ -74,6 +108,15 @@ impl Cli {
|
||||
|
||||
info!("Please continue the authentication process in the default browser");
|
||||
|
||||
let auth_result = match wait_auth_data().await {
|
||||
Ok(auth_data) => SamlAuthResult::Success(auth_data),
|
||||
Err(err) => SamlAuthResult::Failure(format!("{}", err)),
|
||||
};
|
||||
|
||||
info!("Authentication completed");
|
||||
|
||||
println!("{}", json!(auth_result));
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -181,3 +224,35 @@ pub async fn run() {
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
async fn wait_auth_data() -> anyhow::Result<SamlAuthData> {
|
||||
// Start a local server to receive the browser authentication data
|
||||
let listener = TcpListener::bind("127.0.0.1:0").await?;
|
||||
let port = listener.local_addr()?.port();
|
||||
let port_file = temp_dir().join("gpcallback.port");
|
||||
|
||||
// Write the port to a file
|
||||
fs::write(&port_file, port.to_string())?;
|
||||
fs::set_permissions(&port_file, fs::Permissions::from_mode(0o600))?;
|
||||
|
||||
// Remove the previous log file
|
||||
let callback_log = temp_dir().join("gpcallback.log");
|
||||
let _ = fs::remove_file(&callback_log);
|
||||
|
||||
info!("Listening authentication data on port {}", port);
|
||||
info!(
|
||||
"If it hangs, please check the logs at `{}` for more information",
|
||||
callback_log.display()
|
||||
);
|
||||
let (mut socket, _) = listener.accept().await?;
|
||||
|
||||
info!("Received the browser authentication data from the socket");
|
||||
let mut data = String::new();
|
||||
socket.read_to_string(&mut data).await?;
|
||||
|
||||
// Remove the port file
|
||||
fs::remove_file(&port_file)?;
|
||||
|
||||
let auth_data = SamlAuthData::from_gpcallback(&data)?;
|
||||
Ok(auth_data)
|
||||
}
|
||||
|
@@ -14,7 +14,7 @@ clap.workspace = true
|
||||
env_logger.workspace = true
|
||||
inquire = "0.6.2"
|
||||
log.workspace = true
|
||||
tokio.workspace = true
|
||||
tokio = { workspace = true, features = ["rt-multi-thread"] }
|
||||
sysinfo.workspace = true
|
||||
serde_json.workspace = true
|
||||
whoami.workspace = true
|
||||
|
@@ -1,3 +1,5 @@
|
||||
use std::{env::temp_dir, fs::File};
|
||||
|
||||
use clap::{Parser, Subcommand};
|
||||
use gpapi::utils::openssl;
|
||||
use log::{info, LevelFilter};
|
||||
@@ -5,7 +7,7 @@ use tempfile::NamedTempFile;
|
||||
|
||||
use crate::{
|
||||
connect::{ConnectArgs, ConnectHandler},
|
||||
disconnect::DisconnectHandler,
|
||||
disconnect::{DisconnectArgs, DisconnectHandler},
|
||||
launch_gui::{LaunchGuiArgs, LaunchGuiHandler},
|
||||
};
|
||||
|
||||
@@ -21,7 +23,7 @@ enum CliCommand {
|
||||
#[command(about = "Connect to a portal server")]
|
||||
Connect(Box<ConnectArgs>),
|
||||
#[command(about = "Disconnect from the server")]
|
||||
Disconnect,
|
||||
Disconnect(DisconnectArgs),
|
||||
#[command(about = "Launch the GUI")]
|
||||
LaunchGui(LaunchGuiArgs),
|
||||
}
|
||||
@@ -48,7 +50,7 @@ struct Cli {
|
||||
#[command(subcommand)]
|
||||
command: CliCommand,
|
||||
|
||||
#[arg(long, help = "Get around the OpenSSL `unsafe legacy renegotiation` error")]
|
||||
#[arg(long, help = "Uses extended compatibility mode for OpenSSL operations to support a broader range of systems and formats.")]
|
||||
fix_openssl: bool,
|
||||
#[arg(long, help = "Ignore the TLS errors")]
|
||||
ignore_tls_errors: bool,
|
||||
@@ -79,20 +81,35 @@ impl Cli {
|
||||
|
||||
match &self.command {
|
||||
CliCommand::Connect(args) => ConnectHandler::new(args, &shared_args).handle().await,
|
||||
CliCommand::Disconnect => DisconnectHandler::new().handle(),
|
||||
CliCommand::Disconnect(args) => DisconnectHandler::new(args).handle().await,
|
||||
CliCommand::LaunchGui(args) => LaunchGuiHandler::new(args).handle().await,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn init_logger() {
|
||||
env_logger::builder().filter_level(LevelFilter::Info).init();
|
||||
fn init_logger(command: &CliCommand) {
|
||||
let mut builder = env_logger::builder();
|
||||
builder.filter_level(LevelFilter::Info);
|
||||
|
||||
// Output the log messages to a file if the command is the auth callback
|
||||
if let CliCommand::LaunchGui(args) = command {
|
||||
let auth_data = args.auth_data.as_deref().unwrap_or_default();
|
||||
if !auth_data.is_empty() {
|
||||
if let Ok(log_file) = File::create(temp_dir().join("gpcallback.log")) {
|
||||
let target = Box::new(log_file);
|
||||
builder.target(env_logger::Target::Pipe(target));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
builder.init();
|
||||
}
|
||||
|
||||
pub(crate) async fn run() {
|
||||
let cli = Cli::parse();
|
||||
|
||||
init_logger();
|
||||
init_logger(&cli.command);
|
||||
|
||||
info!("gpclient started: {}", VERSION);
|
||||
|
||||
if let Err(err) = cli.run().await {
|
||||
|
@@ -1,8 +1,10 @@
|
||||
use std::{cell::RefCell, fs, sync::Arc};
|
||||
|
||||
use anyhow::bail;
|
||||
use clap::Args;
|
||||
use common::vpn_utils::find_csd_wrapper;
|
||||
use gpapi::{
|
||||
auth::SamlAuthResult,
|
||||
clap::args::Os,
|
||||
credential::{Credential, PasswordCredential},
|
||||
error::PortalError,
|
||||
@@ -19,9 +21,8 @@ use gpapi::{
|
||||
use inquire::{Password, PasswordDisplayMode, Select, Text};
|
||||
use log::info;
|
||||
use openconnect::Vpn;
|
||||
use tokio::{io::AsyncReadExt, net::TcpListener};
|
||||
|
||||
use crate::{cli::SharedArgs, GP_CLIENT_LOCK_FILE, GP_CLIENT_PORT_FILE};
|
||||
use crate::{cli::SharedArgs, GP_CLIENT_LOCK_FILE};
|
||||
|
||||
#[derive(Args)]
|
||||
pub(crate) struct ConnectArgs {
|
||||
@@ -37,6 +38,9 @@ pub(crate) struct ConnectArgs {
|
||||
#[arg(long, help = "Read the password from standard input")]
|
||||
passwd_on_stdin: bool,
|
||||
|
||||
#[arg(long, help = "Read the cookie from standard input")]
|
||||
cookie_on_stdin: bool,
|
||||
|
||||
#[arg(long, short, help = "The VPNC script to use")]
|
||||
script: Option<String>,
|
||||
|
||||
@@ -80,16 +84,16 @@ pub(crate) struct ConnectArgs {
|
||||
#[arg(long, default_value = GP_USER_AGENT, help = "The user agent to use")]
|
||||
user_agent: String,
|
||||
|
||||
#[arg(long, default_value = "Linux")]
|
||||
#[arg(long, value_enum, default_value_t = ConnectArgs::default_os())]
|
||||
os: Os,
|
||||
|
||||
#[arg(long)]
|
||||
#[arg(long, help = "If not specified, it will be computed based on the --os option")]
|
||||
os_version: Option<String>,
|
||||
|
||||
#[arg(long, help = "Disable DTLS and ESP")]
|
||||
no_dtls: bool,
|
||||
|
||||
#[arg(long, help = "The HiDPI mode, useful for high resolution screens")]
|
||||
#[arg(long, help = "The HiDPI mode, useful for high-resolution screens")]
|
||||
hidpi: bool,
|
||||
|
||||
#[arg(long, help = "Do not reuse the remembered authentication cookie")]
|
||||
@@ -100,15 +104,23 @@ pub(crate) struct ConnectArgs {
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
help = "Use the specified browser to authenticate, e.g., firefox, chromium, chrome, or the path to the browser"
|
||||
help = "Use the specified browser to authenticate, e.g., `default`, `firefox`, `chrome`, `chromium`, or the path to the browser executable"
|
||||
)]
|
||||
browser: Option<String>,
|
||||
}
|
||||
|
||||
impl ConnectArgs {
|
||||
fn default_os() -> Os {
|
||||
if cfg!(target_os = "macos") {
|
||||
Os::Mac
|
||||
} else {
|
||||
Os::Linux
|
||||
}
|
||||
}
|
||||
|
||||
fn os_version(&self) -> String {
|
||||
if let Some(os_version) = &self.os_version {
|
||||
return os_version.to_owned();
|
||||
if let Some(os_version) = self.os_version.as_deref() {
|
||||
return os_version.to_string();
|
||||
}
|
||||
|
||||
match self.os {
|
||||
@@ -147,6 +159,10 @@ impl<'a> ConnectHandler<'a> {
|
||||
}
|
||||
|
||||
pub(crate) async fn handle(&self) -> anyhow::Result<()> {
|
||||
if self.args.default_browser && self.args.browser.is_some() {
|
||||
bail!("Cannot use `--default-browser` and `--browser` options at the same time");
|
||||
}
|
||||
|
||||
self.latest_key_password.replace(self.args.key_password.clone());
|
||||
|
||||
loop {
|
||||
@@ -327,6 +343,10 @@ impl<'a> ConnectHandler<'a> {
|
||||
}
|
||||
|
||||
async fn obtain_credential(&self, prelogin: &Prelogin, server: &str) -> anyhow::Result<Credential> {
|
||||
if self.args.cookie_on_stdin {
|
||||
return read_cookie_from_stdin();
|
||||
}
|
||||
|
||||
let is_gateway = prelogin.is_gateway();
|
||||
|
||||
match prelogin {
|
||||
@@ -353,18 +373,9 @@ impl<'a> ConnectHandler<'a> {
|
||||
.launch()
|
||||
.await?;
|
||||
|
||||
if let Some(cred) = cred {
|
||||
return Ok(cred);
|
||||
}
|
||||
|
||||
if !use_default_browser {
|
||||
// This should never happen
|
||||
unreachable!("SAML authentication failed without using the default browser");
|
||||
}
|
||||
|
||||
info!("Waiting for the browser authentication to complete...");
|
||||
wait_credentials().await
|
||||
Ok(cred)
|
||||
}
|
||||
|
||||
Prelogin::Standard(prelogin) => {
|
||||
let prefix = if is_gateway { "Gateway" } else { "Portal" };
|
||||
println!("{} ({}: {})", prelogin.auth_message(), prefix, server);
|
||||
@@ -394,25 +405,17 @@ impl<'a> ConnectHandler<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
async fn wait_credentials() -> anyhow::Result<Credential> {
|
||||
// Start a local server to receive the browser authentication data
|
||||
let listener = TcpListener::bind("127.0.0.1:0").await?;
|
||||
let port = listener.local_addr()?.port();
|
||||
fn read_cookie_from_stdin() -> anyhow::Result<Credential> {
|
||||
info!("Reading cookie from standard input");
|
||||
|
||||
// Write the port to a file
|
||||
fs::write(GP_CLIENT_PORT_FILE, port.to_string())?;
|
||||
let mut cookie = String::new();
|
||||
std::io::stdin().read_line(&mut cookie)?;
|
||||
|
||||
info!("Listening authentication data on port {}", port);
|
||||
let (mut socket, _) = listener.accept().await?;
|
||||
let Ok(auth_result) = serde_json::from_str::<SamlAuthResult>(cookie.trim_end()) else {
|
||||
bail!("Failed to parse auth data")
|
||||
};
|
||||
|
||||
info!("Received the browser authentication data from the socket");
|
||||
let mut data = String::new();
|
||||
socket.read_to_string(&mut data).await?;
|
||||
|
||||
// Remove the port file
|
||||
fs::remove_file(GP_CLIENT_PORT_FILE)?;
|
||||
|
||||
Credential::from_gpcallback(&data)
|
||||
Credential::try_from(auth_result)
|
||||
}
|
||||
|
||||
fn write_pid_file() {
|
||||
|
@@ -1,31 +1,63 @@
|
||||
use crate::GP_CLIENT_LOCK_FILE;
|
||||
use clap::Args;
|
||||
use gpapi::utils::lock_file::gpservice_lock_info;
|
||||
use log::{info, warn};
|
||||
use std::fs;
|
||||
use sysinfo::{Pid, ProcessExt, Signal, System, SystemExt};
|
||||
use std::{fs, str::FromStr, thread, time::Duration};
|
||||
use sysinfo::{Pid, Signal, System};
|
||||
|
||||
pub(crate) struct DisconnectHandler;
|
||||
#[derive(Args)]
|
||||
pub struct DisconnectArgs {
|
||||
#[arg(
|
||||
long,
|
||||
required = false,
|
||||
help = "The time in seconds to wait for the VPN connection to disconnect"
|
||||
)]
|
||||
wait: Option<u64>,
|
||||
}
|
||||
|
||||
impl DisconnectHandler {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self
|
||||
pub struct DisconnectHandler<'a> {
|
||||
args: &'a DisconnectArgs,
|
||||
}
|
||||
|
||||
impl<'a> DisconnectHandler<'a> {
|
||||
pub fn new(args: &'a DisconnectArgs) -> Self {
|
||||
Self { args }
|
||||
}
|
||||
|
||||
pub(crate) fn handle(&self) -> anyhow::Result<()> {
|
||||
if fs::metadata(GP_CLIENT_LOCK_FILE).is_err() {
|
||||
warn!("PID file not found, maybe the client is not running");
|
||||
return Ok(());
|
||||
pub async fn handle(&self) -> anyhow::Result<()> {
|
||||
// Try to disconnect the CLI client
|
||||
if let Ok(c) = fs::read_to_string(GP_CLIENT_LOCK_FILE) {
|
||||
send_signal(c.trim(), Signal::Interrupt).unwrap_or_else(|err| {
|
||||
warn!("Failed to send signal to client: {}", err);
|
||||
});
|
||||
};
|
||||
|
||||
// Try to disconnect the GUI service
|
||||
if let Ok(c) = gpservice_lock_info().await {
|
||||
send_signal(&c.pid.to_string(), Signal::User1).unwrap_or_else(|err| {
|
||||
warn!("Failed to send signal to service: {}", err);
|
||||
});
|
||||
};
|
||||
|
||||
// sleep, to give the client and service time to disconnect
|
||||
if let Some(wait) = self.args.wait {
|
||||
thread::sleep(Duration::from_secs(wait));
|
||||
}
|
||||
|
||||
let pid = fs::read_to_string(GP_CLIENT_LOCK_FILE)?;
|
||||
let pid = pid.trim().parse::<usize>()?;
|
||||
let s = System::new_all();
|
||||
|
||||
if let Some(process) = s.process(Pid::from(pid)) {
|
||||
info!("Found process {}, killing...", pid);
|
||||
if process.kill_with(Signal::Interrupt).is_none() {
|
||||
warn!("Failed to kill process {}", pid);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn send_signal(pid: &str, signal: Signal) -> anyhow::Result<()> {
|
||||
let s = System::new_all();
|
||||
let pid = Pid::from_str(pid)?;
|
||||
|
||||
if let Some(process) = s.process(pid) {
|
||||
info!("Found process {}, sending signal...", pid);
|
||||
|
||||
if process.kill_with(signal).is_none() {
|
||||
warn!("Failed to kill process {}", pid);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@@ -9,15 +9,13 @@ use gpapi::{
|
||||
use log::info;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
|
||||
use crate::GP_CLIENT_PORT_FILE;
|
||||
|
||||
#[derive(Args)]
|
||||
pub(crate) struct LaunchGuiArgs {
|
||||
#[arg(
|
||||
required = false,
|
||||
help = "The authentication data, used for the default browser authentication"
|
||||
)]
|
||||
auth_data: Option<String>,
|
||||
pub auth_data: Option<String>,
|
||||
#[arg(long, help = "Launch the GUI minimized")]
|
||||
minimized: bool,
|
||||
}
|
||||
@@ -40,6 +38,7 @@ impl<'a> LaunchGuiHandler<'a> {
|
||||
|
||||
let auth_data = self.args.auth_data.as_deref().unwrap_or_default();
|
||||
if !auth_data.is_empty() {
|
||||
info!("Received auth callback data");
|
||||
// Process the authentication data, its format is `globalprotectcallback:<data>`
|
||||
return feed_auth_data(auth_data).await;
|
||||
}
|
||||
@@ -81,16 +80,26 @@ impl<'a> LaunchGuiHandler<'a> {
|
||||
}
|
||||
|
||||
async fn feed_auth_data(auth_data: &str) -> anyhow::Result<()> {
|
||||
let _ = tokio::join!(feed_auth_data_gui(auth_data), feed_auth_data_cli(auth_data));
|
||||
let (res_gui, res_cli) = tokio::join!(feed_auth_data_gui(auth_data), feed_auth_data_cli(auth_data));
|
||||
if let Err(err) = res_gui {
|
||||
info!("Failed to feed auth data to the GUI: {}", err);
|
||||
}
|
||||
|
||||
if let Err(err) = res_cli {
|
||||
info!("Failed to feed auth data to the CLI: {}", err);
|
||||
}
|
||||
|
||||
// Cleanup the temporary file
|
||||
let html_file = temp_dir().join("gpauth.html");
|
||||
let _ = std::fs::remove_file(html_file);
|
||||
if let Err(err) = std::fs::remove_file(&html_file) {
|
||||
info!("Failed to remove {}: {}", html_file.display(), err);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn feed_auth_data_gui(auth_data: &str) -> anyhow::Result<()> {
|
||||
info!("Feeding auth data to the GUI");
|
||||
let service_endpoint = http_endpoint().await?;
|
||||
|
||||
reqwest::Client::default()
|
||||
@@ -104,7 +113,10 @@ async fn feed_auth_data_gui(auth_data: &str) -> anyhow::Result<()> {
|
||||
}
|
||||
|
||||
async fn feed_auth_data_cli(auth_data: &str) -> anyhow::Result<()> {
|
||||
let port = tokio::fs::read_to_string(GP_CLIENT_PORT_FILE).await?;
|
||||
info!("Feeding auth data to the CLI");
|
||||
|
||||
let port_file = temp_dir().join("gpcallback.port");
|
||||
let port = tokio::fs::read_to_string(port_file).await?;
|
||||
let mut stream = tokio::net::TcpStream::connect(format!("127.0.0.1:{}", port.trim())).await?;
|
||||
|
||||
stream.write_all(auth_data.as_bytes()).await?;
|
||||
@@ -124,7 +136,7 @@ async fn try_active_gui() -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_log_file() -> anyhow::Result<PathBuf> {
|
||||
fn get_log_file() -> anyhow::Result<PathBuf> {
|
||||
let dirs = ProjectDirs::from("com.yuezk", "GlobalProtect-openconnect", "gpclient")
|
||||
.ok_or_else(|| anyhow::anyhow!("Failed to get project dirs"))?;
|
||||
|
||||
|
@@ -4,7 +4,6 @@ mod disconnect;
|
||||
mod launch_gui;
|
||||
|
||||
pub(crate) const GP_CLIENT_LOCK_FILE: &str = "/var/run/gpclient.lock";
|
||||
pub(crate) const GP_CLIENT_PORT_FILE: &str = "/var/run/gpclient.port";
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
|
1
apps/gpgui-helper/.gitignore
vendored
1
apps/gpgui-helper/.gitignore
vendored
@@ -8,7 +8,6 @@ pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
|
99
apps/gpgui-helper/dist/assets/icon-674efcbe.svg
vendored
Normal file
99
apps/gpgui-helper/dist/assets/icon-674efcbe.svg
vendored
Normal file
@@ -0,0 +1,99 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.1"
|
||||
id="Layer_1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 96 96"
|
||||
style="enable-background:new 0 0 96 96;"
|
||||
xml:space="preserve"
|
||||
sodipodi:docname="com.yuezk.qt.gpclient.svg"
|
||||
inkscape:version="0.92.4 5da689c313, 2019-01-14"><metadata
|
||||
id="metadata14"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs12" /><sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1006"
|
||||
id="namedview10"
|
||||
showgrid="false"
|
||||
inkscape:zoom="6.9532168"
|
||||
inkscape:cx="7.9545315"
|
||||
inkscape:cy="59.062386"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="g8499" />
|
||||
<style
|
||||
type="text/css"
|
||||
id="style2">
|
||||
.st0{fill:#2980B9;}
|
||||
.st1{fill:#3498DB;}
|
||||
.st2{fill:#2ECC71;}
|
||||
.st3{fill:#27AE60;}
|
||||
</style>
|
||||
|
||||
<g
|
||||
id="g8499"
|
||||
transform="matrix(1.3407388,0,0,1.3407388,-16.409202,-16.355463)"><g
|
||||
id="XMLID_1_">
|
||||
<circle
|
||||
r="32.5"
|
||||
cy="48"
|
||||
cx="48"
|
||||
class="st0"
|
||||
id="XMLID_3_"
|
||||
style="fill:#2980b9" />
|
||||
<path
|
||||
d="m 48,15.5 v 65 C 65.9,80.5 80.5,65.7 80.5,48 80.5,30 65.9,15.5 48,15.5 Z"
|
||||
class="st1"
|
||||
id="XMLID_4_"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#3498db" />
|
||||
<path
|
||||
d="m 48,15.5 v 0.6 l 1.2,-0.3 c 0.3,-0.3 0.4,-0.3 0.6,-0.3 h -1.1 z m 7.3,0.9 c -0.1,0 0.4,0.9 1.1,1.8 0.8,1.5 1.1,2.1 1.3,2.1 0.3,-0.3 1.9,-1.2 3,-2.1 -1.7,-0.9 -3.5,-1.5 -5.4,-1.8 z m 10.3,6.2 c -0.1,0 -0.4,0 -0.9,0.6 l -0.8,0.9 0.6,0.6 c 0.3,0.6 0.8,0.9 1,1.2 0.5,0.6 0.6,0.6 0.1,1.5 -0.2,0.6 -0.3,0.9 -0.3,0.9 0.1,0.3 0.3,0.3 1.4,0.3 h 1.6 c 0.1,0 0.3,-0.6 0.4,-1.2 l 0.1,-0.9 -1.1,-0.9 c -1,-0.9 -1,-0.9 -1.4,-1.8 -0.3,-0.6 -0.6,-1.2 -0.7,-1.2 z m -3,2.4 c -0.2,0 -1.3,2.1 -1.3,2.4 0,0 0.3,0.6 0.7,0.9 0.4,0.3 0.7,0.6 0.7,0.6 0.1,0 1.2,-1.2 1.4,-1.5 C 64.2,27.1 64,26.8 63.5,26.2 63.1,25.5 62.7,25 62.6,25 Z m 9.5,1.1 0.2,0.3 c 0,0.3 -0.7,0.9 -1.4,1.5 -1.2,0.9 -1.4,1.2 -2,1.2 -0.6,0 -0.9,0.3 -1.8,0.9 -0.6,0.6 -1.2,0.9 -1.2,1.2 0,0 0.2,0.3 0.6,0.9 0.7,0.6 0.7,0.9 0.2,1.8 l -0.4,0.3 h -1.1 c -0.6,0 -1.5,0 -1.8,-0.3 -0.9,0 -0.8,0 -0.1,2.1 1,3 1.1,3.2 1.3,3.2 0.1,0 1.3,-1.2 2.8,-2.4 1.5,-1.2 2.7,-2.4 2.8,-2.4 l 0.6,0.3 c 0.4,0.3 0.5,0 1.3,-0.6 l 0.8,-0.6 0.8,0.6 c 1.9,1.2 2.2,1.5 2.3,2.4 0.2,1.5 0.3,1.8 0.5,1.8 0.1,0 1.3,-1.5 1.6,-1.8 0.1,-0.3 -0.1,-0.6 -1.1,-2.1 -0.7,-0.9 -1.1,-1.8 -1.1,-2.1 0,0 0.1,0 0.3,-0.3 0.2,0 0.4,0.3 1,0.9 -1.6,-2.3 -3.2,-4.7 -5.1,-6.8 z m 2.8,10.7 c -0.2,0 -0.9,0.9 -0.8,1.2 l 0.5,0.3 H 75 c 0.2,0 0.3,0 0.2,-0.3 C 75.1,37.4 75,36.8 74.9,36.8 Z M 72.3,38 h -2.4 l -2.4,0.3 -4.5,3.5 -4.4,3.8 v 3.5 c 0,2.1 0,3.8 0.1,3.8 0.1,0 0.7,0.9 1.5,1.5 0.8,0.9 1.5,1.5 1.8,1.8 0.4,0.3 0.5,0.3 4,0.6 l 3.4,0.3 1.6,0.9 c 0.8,0.6 1.5,1.2 1.6,1.2 0.1,0 -0.3,0.3 -0.6,0.6 l -0.6,0.6 1,1.2 c 0.5,0.6 1.3,1.5 1.7,1.8 l 0.6,0.9 v 1.7 0.9 c 3.7,-5 5.9,-11.5 6.1,-18.3 0.1,-2.7 -0.3,-5.3 -0.8,-8 l -0.6,-0.3 c -0.1,0 -0.5,0.3 -1,0.6 -0.5,0.3 -1,0.9 -1.1,0.9 -0.1,0 -0.8,-0.3 -1.8,-0.6 l -1.8,-0.6 v -0.9 c 0,-0.6 0,-0.9 -0.6,-1.5 z M 48,63.7 V 64 h 0.2 z"
|
||||
class="st2"
|
||||
id="XMLID_13_"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#2ecc71" />
|
||||
<path
|
||||
d="m 48,15.5 c -3.1,0 -6.2,0.5 -9,1.3 0.3,0.4 0.3,0.4 0.6,0.9 1.5,2.5 1.7,2.8 2.1,2.9 0.3,0 0.9,0.1 1.6,0.1 h 1.2 l 0.9,-2 0.8,-1.9 1.8,-0.6 z m -16.9,4.7 c -2.8,1.7 -5.4,3.9 -7.6,6.4 -3.8,4.3 -6.3,9.6 -7.4,15.4 0.5,0 0.9,-0.1 1.8,-0.1 2.8,0.1 2.5,0 3.4,1.4 0.5,0.8 0.6,0.8 1.4,0.8 1,0.1 0.9,0 0.5,-1.6 -0.2,-0.6 -0.3,-1.2 -0.3,-1.4 0,-0.2 0.5,-0.7 1.7,-1.6 1.9,-1.5 1.8,-1.3 1.5,-2.9 -0.1,-0.3 0.1,-0.6 0.6,-1.2 0.7,-0.7 0.7,-0.6 1.4,-0.6 h 0.7 l 0.1,-1.2 c 0.1,-0.7 0.1,-1.3 0.2,-1.3 0,0 1.9,-1.1 4.1,-2.3 2.2,-1.2 4.1,-2.2 4.2,-2.3 0.2,-0.2 -0.3,-0.8 -2.7,-3.8 -1.5,-1.9 -2.8,-3.6 -2.9,-3.7 z m -5.8,23 c -0.1,0 -0.1,0.3 -0.1,0.6 0,0.6 0,0.7 0.6,1 0.8,0.4 0.9,0.5 0.8,0.2 -0.1,-0.4 -1.2,-1.9 -1.3,-1.8 z m -3.4,2.1 -0.5,1.8 c 0.1,0.1 0.9,0.3 1.8,0.5 1,0.2 1.6,0.4 1.8,0.3 l 0.5,-1.3 z m -3.8,1 -1.1,0.6 c -0.6,0.3 -1.2,0.6 -1.4,0.6 h -0.1 c 0,1.4 0.1,2.8 0.3,4.2 l 0.6,0.4 1,-0.1 h 1 l 0.6,1.4 c 0.3,0.7 0.7,1.4 0.8,1.5 0.1,0.1 1,0.1 1.8,0.1 h 1.5 L 23,56.2 c 0,1.2 0,1.3 -0.6,2.2 -0.4,0.5 -0.6,1.2 -0.6,1.4 0,0.2 0.7,2.1 1.6,4.3 l 1.5,4 1.6,0.8 c 1.2,0.6 1.5,0.8 1.5,1 0,0.1 -0.4,2.1 -0.6,3.1 3,2.5 6.4,4.5 10.2,5.8 3.5,-3.6 6.8,-7.1 7.3,-7.6 l 0.7,-0.7 0.2,-1.9 c 0.2,-1.1 0.4,-2.1 0.4,-2.2 0,-0.1 0.5,-0.6 1,-1.2 0.5,-0.5 0.8,-1 0.8,-1.1 v -0.2 c -0.1,-0.1 -1.4,-1.1 -3,-2.2 l -3.1,-2.1 -1.1,-0.1 c -0.8,0 -1.2,0 -1.3,-0.2 C 39.4,59.2 39.2,58.5 39.1,57.7 39,56.9 38.9,56.2 38.8,56.1 38.8,56 38,56 37.1,56 36.2,56 35.4,55.9 35.3,55.8 35.2,55.7 35.2,55.1 35.1,54.3 35,53.6 34.9,53 34.8,52.9 34.7,52.8 33.7,52.7 32.5,52.6 30.5,52.5 30.1,52.5 29.1,52 l -1.2,-0.6 -1.6,0.7 -1.7,0.9 -1.8,-0.1 c -2,0 -1.9,0.2 -2.1,-1.6 C 20.6,50.7 20.6,50.1 20.5,50.1 20.4,50 20,50 19.6,49.9 L 18.9,49.7 19,49.2 c 0,-0.3 0,-1 0.1,-1.4 L 19.2,47 18.7,46.5 Z m 9.1,1.1 C 27.1,47.5 27.1,47.8 27,48 l -0.1,0.5 2.9,1.2 c 2.9,1.1 3.4,1.2 3.9,0.7 0.2,-0.2 0.1,-0.2 -0.3,-0.4 -0.3,-0.1 -1.7,-0.9 -3.2,-1.6 -1.7,-0.7 -2.9,-1.1 -3,-1 z"
|
||||
class="st3"
|
||||
id="XMLID_20_"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#27ae60" />
|
||||
</g><g
|
||||
transform="matrix(1.458069,0,0,1.458069,-22.631538,-19.615144)"
|
||||
id="g7664"><path
|
||||
inkscape:connector-curvature="0"
|
||||
id="XMLID_6_"
|
||||
class="st3"
|
||||
d="m 38.8,56.1 c 0,1.2 1,2.2 2.2,2.2 h 15.2 c 1.2,0 2.2,-1 2.2,-2.2 V 45.3 c 0,-1.2 -1,-2.2 -2.2,-2.2 H 40.9 c -1.2,0 -2.2,1 -2.2,2.2 v 10.8 z"
|
||||
style="fill:#f1aa27;fill-opacity:1" /><path
|
||||
style="fill:#e6e6e6"
|
||||
inkscape:connector-curvature="0"
|
||||
id="XMLID_7_"
|
||||
class="st4"
|
||||
d="m 55.5,43.1 h -3.3 v -3.7 c 0,-2.1 -1.7,-3.8 -3.8,-3.8 -2.1,0 -3.8,1.7 -3.8,3.8 v 3.8 h -3.1 v -3.8 c 0,-3.9 3.2,-7 7,-7 3.9,0 7,3.2 7,7 z" /><path
|
||||
style="fill:#e6e6e6;fill-opacity:1"
|
||||
inkscape:connector-curvature="0"
|
||||
id="XMLID_8_"
|
||||
class="st5"
|
||||
d="m 50.35,48.2 c 0,-1 -0.8,-1.8 -1.8,-1.8 -1,0 -1.8,0.8 -1.8,1.8 0,0.7 0.4,1.3 1,1.6 l -1,5.2 h 3.6 l -1,-5.2 c 0.6,-0.3 1,-0.9 1,-1.6 z" /></g></g></svg>
|
After Width: | Height: | Size: 6.7 KiB |
1
apps/gpgui-helper/dist/assets/index-11e7064a.css
vendored
Normal file
1
apps/gpgui-helper/dist/assets/index-11e7064a.css
vendored
Normal file
@@ -0,0 +1 @@
|
||||
html,body,#root{height:100%;margin:0;padding:0;-webkit-user-select:none;user-select:none;cursor:default}
|
188
apps/gpgui-helper/dist/assets/main-c159dd55.js
vendored
Normal file
188
apps/gpgui-helper/dist/assets/main-c159dd55.js
vendored
Normal file
File diff suppressed because one or more lines are too long
21
apps/gpgui-helper/dist/index.html
vendored
Normal file
21
apps/gpgui-helper/dist/index.html
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>GlobalProtect</title>
|
||||
<script type="module" crossorigin src="/assets/main-c159dd55.js"></script>
|
||||
<link rel="stylesheet" href="/assets/index-11e7064a.css">
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
/* workaround to webview font size auto scaling */
|
||||
var htmlFontSize = getComputedStyle(document.documentElement).fontSize;
|
||||
var ratio = parseInt(htmlFontSize, 10) / 16;
|
||||
document.documentElement.style.fontSize = 16 / ratio + "px";
|
||||
</script>
|
||||
<div id="root" data-tauri-drag-region></div>
|
||||
|
||||
</body>
|
||||
</html>
|
6
apps/gpgui-helper/dist/tauri.svg
vendored
Normal file
6
apps/gpgui-helper/dist/tauri.svg
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg width="206" height="231" viewBox="0 0 206 231" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M143.143 84C143.143 96.1503 133.293 106 121.143 106C108.992 106 99.1426 96.1503 99.1426 84C99.1426 71.8497 108.992 62 121.143 62C133.293 62 143.143 71.8497 143.143 84Z" fill="#FFC131"/>
|
||||
<ellipse cx="84.1426" cy="147" rx="22" ry="22" transform="rotate(180 84.1426 147)" fill="#24C8DB"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M166.738 154.548C157.86 160.286 148.023 164.269 137.757 166.341C139.858 160.282 141 153.774 141 147C141 144.543 140.85 142.121 140.558 139.743C144.975 138.204 149.215 136.139 153.183 133.575C162.73 127.404 170.292 118.608 174.961 108.244C179.63 97.8797 181.207 86.3876 179.502 75.1487C177.798 63.9098 172.884 53.4021 165.352 44.8883C157.82 36.3744 147.99 30.2165 137.042 27.1546C126.095 24.0926 114.496 24.2568 103.64 27.6274C92.7839 30.998 83.1319 37.4317 75.8437 46.1553C74.9102 47.2727 74.0206 48.4216 73.176 49.5993C61.9292 50.8488 51.0363 54.0318 40.9629 58.9556C44.2417 48.4586 49.5653 38.6591 56.679 30.1442C67.0505 17.7298 80.7861 8.57426 96.2354 3.77762C111.685 -1.01901 128.19 -1.25267 143.769 3.10474C159.348 7.46215 173.337 16.2252 184.056 28.3411C194.775 40.457 201.767 55.4101 204.193 71.404C206.619 87.3978 204.374 103.752 197.73 118.501C191.086 133.25 180.324 145.767 166.738 154.548ZM41.9631 74.275L62.5557 76.8042C63.0459 72.813 63.9401 68.9018 65.2138 65.1274C57.0465 67.0016 49.2088 70.087 41.9631 74.275Z" fill="#FFC131"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M38.4045 76.4519C47.3493 70.6709 57.2677 66.6712 67.6171 64.6132C65.2774 70.9669 64 77.8343 64 85.0001C64 87.1434 64.1143 89.26 64.3371 91.3442C60.0093 92.8732 55.8533 94.9092 51.9599 97.4256C42.4128 103.596 34.8505 112.392 30.1816 122.756C25.5126 133.12 23.9357 144.612 25.6403 155.851C27.3449 167.09 32.2584 177.598 39.7906 186.112C47.3227 194.626 57.153 200.784 68.1003 203.846C79.0476 206.907 90.6462 206.743 101.502 203.373C112.359 200.002 122.011 193.568 129.299 184.845C130.237 183.722 131.131 182.567 131.979 181.383C143.235 180.114 154.132 176.91 164.205 171.962C160.929 182.49 155.596 192.319 148.464 200.856C138.092 213.27 124.357 222.426 108.907 227.222C93.458 232.019 76.9524 232.253 61.3736 227.895C45.7948 223.538 31.8055 214.775 21.0867 202.659C10.3679 190.543 3.37557 175.59 0.949823 159.596C-1.47592 143.602 0.768139 127.248 7.41237 112.499C14.0566 97.7497 24.8183 85.2327 38.4045 76.4519ZM163.062 156.711L163.062 156.711C162.954 156.773 162.846 156.835 162.738 156.897C162.846 156.835 162.954 156.773 163.062 156.711Z" fill="#24C8DB"/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.5 KiB |
1
apps/gpgui-helper/dist/vite.svg
vendored
Normal file
1
apps/gpgui-helper/dist/vite.svg
vendored
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
After Width: | Height: | Size: 1.5 KiB |
@@ -9,7 +9,7 @@ gpapi = { path = "../../crates/gpapi" }
|
||||
openconnect = { path = "../../crates/openconnect" }
|
||||
clap.workspace = true
|
||||
anyhow.workspace = true
|
||||
tokio.workspace = true
|
||||
tokio = { workspace = true, features = ["rt-multi-thread"] }
|
||||
tokio-util.workspace = true
|
||||
axum = { workspace = true, features = ["ws"] }
|
||||
futures.workspace = true
|
||||
|
@@ -30,7 +30,8 @@ struct Cli {
|
||||
|
||||
impl Cli {
|
||||
async fn run(&mut self, redaction: Arc<Redaction>) -> anyhow::Result<()> {
|
||||
let lock_file = Arc::new(LockFile::new(GP_SERVICE_LOCK_FILE));
|
||||
let pid = std::process::id();
|
||||
let lock_file = Arc::new(LockFile::new(GP_SERVICE_LOCK_FILE, pid));
|
||||
|
||||
if lock_file.check_health().await {
|
||||
bail!("Another instance of the service is already running");
|
||||
@@ -48,9 +49,17 @@ impl Cli {
|
||||
|
||||
let (shutdown_tx, mut shutdown_rx) = mpsc::channel::<()>(4);
|
||||
let shutdown_tx_clone = shutdown_tx.clone();
|
||||
let vpn_task_token = vpn_task.cancel_token();
|
||||
let vpn_task_cancel_token = vpn_task.cancel_token();
|
||||
let server_token = ws_server.cancel_token();
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
let vpn_ctx = vpn_task.context();
|
||||
let ws_ctx = ws_server.context();
|
||||
|
||||
tokio::spawn(async move { signals::handle_signals(vpn_ctx, ws_ctx).await });
|
||||
}
|
||||
|
||||
let vpn_task_handle = tokio::spawn(async move { vpn_task.start(server_token).await });
|
||||
let ws_server_handle = tokio::spawn(async move { ws_server.start(shutdown_tx_clone).await });
|
||||
|
||||
@@ -74,15 +83,15 @@ impl Cli {
|
||||
}
|
||||
|
||||
tokio::select! {
|
||||
_ = shutdown_signal() => {
|
||||
info!("Shutdown signal received");
|
||||
}
|
||||
_ = shutdown_rx.recv() => {
|
||||
info!("Shutdown request received, shutting down");
|
||||
}
|
||||
_ = shutdown_signal() => {
|
||||
info!("Shutdown signal received");
|
||||
}
|
||||
_ = shutdown_rx.recv() => {
|
||||
info!("Shutdown request received, shutting down");
|
||||
}
|
||||
}
|
||||
|
||||
vpn_task_token.cancel();
|
||||
vpn_task_cancel_token.cancel();
|
||||
let _ = tokio::join!(vpn_task_handle, ws_server_handle);
|
||||
|
||||
lock_file.unlock()?;
|
||||
@@ -125,6 +134,54 @@ fn init_logger() -> Arc<Redaction> {
|
||||
redaction
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
mod signals {
|
||||
use std::sync::Arc;
|
||||
|
||||
use log::{info, warn};
|
||||
|
||||
use crate::vpn_task::VpnTaskContext;
|
||||
use crate::ws_server::WsServerContext;
|
||||
|
||||
const DISCONNECTED_PID_FILE: &str = "/tmp/gpservice_disconnected.pid";
|
||||
|
||||
pub(crate) async fn handle_signals(vpn_ctx: Arc<VpnTaskContext>, ws_ctx: Arc<WsServerContext>) {
|
||||
use gpapi::service::event::WsEvent;
|
||||
use tokio::signal::unix::{signal, Signal, SignalKind};
|
||||
|
||||
let (mut user_sig1, mut user_sig2) = match || -> anyhow::Result<(Signal, Signal)> {
|
||||
let user_sig1 = signal(SignalKind::user_defined1())?;
|
||||
let user_sig2 = signal(SignalKind::user_defined2())?;
|
||||
Ok((user_sig1, user_sig2))
|
||||
}() {
|
||||
Ok(signals) => signals,
|
||||
Err(err) => {
|
||||
warn!("Failed to create signal: {}", err);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
_ = user_sig1.recv() => {
|
||||
info!("Received SIGUSR1 signal");
|
||||
if vpn_ctx.disconnect().await {
|
||||
// Write the PID to a dedicated file to indicate that the VPN task is disconnected via SIGUSR1
|
||||
let pid = std::process::id();
|
||||
if let Err(err) = tokio::fs::write(DISCONNECTED_PID_FILE, pid.to_string()).await {
|
||||
warn!("Failed to write PID to file: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ = user_sig2.recv() => {
|
||||
info!("Received SIGUSR2 signal");
|
||||
ws_ctx.send_event(WsEvent::ResumeConnection).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn launch_gui(envs: Option<HashMap<String, String>>, api_key: Vec<u8>, mut minimized: bool) {
|
||||
loop {
|
||||
let gui_launcher = GuiLauncher::new(env!("CARGO_PKG_VERSION"), &api_key)
|
||||
|
@@ -43,7 +43,7 @@ pub(crate) async fn auth_data(State(ctx): State<Arc<WsServerContext>>, body: Str
|
||||
ctx.send_event(WsEvent::AuthData(body)).await;
|
||||
}
|
||||
|
||||
pub async fn update_gui(State(ctx): State<Arc<WsServerContext>>, body: Bytes) -> Result<(), StatusCode> {
|
||||
pub(crate) async fn update_gui(State(ctx): State<Arc<WsServerContext>>, body: Bytes) -> Result<(), StatusCode> {
|
||||
let payload = match ctx.decrypt::<UpdateGuiRequest>(body.to_vec()) {
|
||||
Ok(payload) => payload,
|
||||
Err(err) => {
|
||||
|
@@ -87,7 +87,7 @@ impl VpnTaskContext {
|
||||
});
|
||||
}
|
||||
|
||||
pub async fn disconnect(&self) {
|
||||
pub async fn disconnect(&self) -> bool {
|
||||
if let Some(disconnect_rx) = self.disconnect_rx.write().await.take() {
|
||||
info!("Disconnecting VPN...");
|
||||
if let Some(vpn) = self.vpn_handle.read().await.as_ref() {
|
||||
@@ -98,9 +98,13 @@ impl VpnTaskContext {
|
||||
// Wait for the VPN to be disconnected
|
||||
disconnect_rx.await.ok();
|
||||
info!("VPN disconnected");
|
||||
|
||||
true
|
||||
} else {
|
||||
info!("VPN is not connected, skip disconnect");
|
||||
self.vpn_state_tx.send(VpnState::Disconnected).ok();
|
||||
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -143,6 +147,10 @@ impl VpnTask {
|
||||
server_cancel_token.cancel();
|
||||
}
|
||||
|
||||
pub fn context(&self) -> Arc<VpnTaskContext> {
|
||||
return Arc::clone(&self.ctx);
|
||||
}
|
||||
|
||||
async fn recv(&mut self) {
|
||||
while let Some(req) = self.ws_req_rx.recv().await {
|
||||
tokio::spawn(process_ws_req(req, self.ctx.clone()));
|
||||
|
@@ -113,6 +113,10 @@ impl WsServer {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn context(&self) -> Arc<WsServerContext> {
|
||||
Arc::clone(&self.ctx)
|
||||
}
|
||||
|
||||
pub fn cancel_token(&self) -> CancellationToken {
|
||||
self.cancel_token.clone()
|
||||
}
|
||||
@@ -124,7 +128,7 @@ impl WsServer {
|
||||
warn!("Failed to start WS server: {}", err);
|
||||
let _ = shutdown_tx.send(()).await;
|
||||
return;
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
tokio::select! {
|
||||
@@ -149,7 +153,7 @@ impl WsServer {
|
||||
|
||||
info!("WS server listening on port: {}", port);
|
||||
|
||||
self.lock_file.lock(port.to_string())?;
|
||||
self.lock_file.lock(&port.to_string())?;
|
||||
|
||||
Ok(listener)
|
||||
}
|
||||
|
30
changelog.md
30
changelog.md
@@ -1,5 +1,35 @@
|
||||
# Changelog
|
||||
|
||||
## 2.3.11 - 2025-01-21
|
||||
|
||||
- Update minimal Rust version to 1.71.1, so that the PPA can be built on Ubuntu 18.04.
|
||||
|
||||
## 2.3.10 - 2025-01-20
|
||||
|
||||
- Disconnect the VPN when sleep (fix [#166](https://github.com/yuezk/GlobalProtect-openconnect/issues/166), [#267](https://github.com/yuezk/GlobalProtect-openconnect/issues/267))
|
||||
|
||||
## 2.3.9 - 2024-11-02
|
||||
|
||||
- Enhance the OpenSSL compatibility mode (fix [#437](https://github.com/yuezk/GlobalProtect-openconnect/issues/437))
|
||||
|
||||
## 2.3.8 - 2024-10-31
|
||||
|
||||
- GUI: support configure the external browser to use for authentication (fix [#423](https://github.com/yuezk/GlobalProtect-openconnect/issues/423))
|
||||
- GUI: add option to remember the credential (fix [#420](https://github.com/yuezk/GlobalProtect-openconnect/issues/420))
|
||||
- GUI: fix the credential not saved issue (fix [#420](https://github.com/yuezk/GlobalProtect-openconnect/issues/420))
|
||||
- CLI: fix the default browser detection issue (fix [#416](https://github.com/yuezk/GlobalProtect-openconnect/issues/416))
|
||||
|
||||
## 2.3.7 - 2024-08-16
|
||||
|
||||
- Fix the Rust type inference regression [issue in 1.80](https://github.com/rust-lang/rust/issues/125319).
|
||||
|
||||
## 2.3.6 - 2024-08-15
|
||||
|
||||
- CLI: enhance the `gpauth` command to support external browser authentication
|
||||
- CLI: add the `--cookie-on-stdin` option to support read the cookie from stdin
|
||||
- CLI: support usage: `gpauth <portal> --browser <browser> 2>/dev/null | sudo gpclient connect <portal> --cookie-on-stdin`
|
||||
- CLI: fix the `--browser <browser>` option not working
|
||||
|
||||
## 2.3.5 - 2024-08-14
|
||||
|
||||
- Support configure `no-dtls` option
|
||||
|
@@ -17,7 +17,7 @@ serde.workspace = true
|
||||
specta.workspace = true
|
||||
specta-macros.workspace = true
|
||||
urlencoding.workspace = true
|
||||
tokio.workspace = true
|
||||
tokio = { workspace = true, features = ["process", "signal", "macros"] }
|
||||
serde_json.workspace = true
|
||||
whoami.workspace = true
|
||||
tempfile.workspace = true
|
||||
@@ -32,11 +32,15 @@ md5.workspace = true
|
||||
sha256.workspace = true
|
||||
which.workspace = true
|
||||
|
||||
# Pin the version of home because the latest version requires Rust 1.81
|
||||
home = "=0.5.9"
|
||||
|
||||
tauri = { workspace = true, optional = true }
|
||||
clap = { workspace = true, optional = true }
|
||||
open = { version = "5", optional = true }
|
||||
webbrowser = { version = "1", optional = true }
|
||||
|
||||
[features]
|
||||
tauri = ["dep:tauri"]
|
||||
clap = ["dep:clap"]
|
||||
browser-auth = ["dep:open"]
|
||||
browser-auth = ["dep:open", "dep:webbrowser"]
|
||||
|
@@ -85,7 +85,6 @@ impl SamlAuthData {
|
||||
return Ok(auth_data);
|
||||
}
|
||||
|
||||
info!("Parsing SAML auth data...");
|
||||
let auth_data = decode_to_string(auth_data).map_err(|e| {
|
||||
warn!("Failed to decode SAML auth data: {}", e);
|
||||
AuthDataParseError::Invalid
|
||||
|
@@ -1,9 +1,10 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use anyhow::bail;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use specta::Type;
|
||||
|
||||
use crate::auth::SamlAuthData;
|
||||
use crate::auth::{SamlAuthData, SamlAuthResult};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Type, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
@@ -110,11 +111,11 @@ impl AuthCookieCredential {
|
||||
pub struct CachedCredential {
|
||||
username: String,
|
||||
password: Option<String>,
|
||||
auth_cookie: AuthCookieCredential,
|
||||
auth_cookie: Option<AuthCookieCredential>,
|
||||
}
|
||||
|
||||
impl CachedCredential {
|
||||
pub fn new(username: String, password: Option<String>, auth_cookie: AuthCookieCredential) -> Self {
|
||||
pub fn new(username: String, password: Option<String>, auth_cookie: Option<AuthCookieCredential>) -> Self {
|
||||
Self {
|
||||
username,
|
||||
password,
|
||||
@@ -130,12 +131,12 @@ impl CachedCredential {
|
||||
self.password.as_deref()
|
||||
}
|
||||
|
||||
pub fn auth_cookie(&self) -> &AuthCookieCredential {
|
||||
&self.auth_cookie
|
||||
pub fn auth_cookie(&self) -> Option<&AuthCookieCredential> {
|
||||
self.auth_cookie.as_ref()
|
||||
}
|
||||
|
||||
pub fn set_auth_cookie(&mut self, auth_cookie: AuthCookieCredential) {
|
||||
self.auth_cookie = auth_cookie;
|
||||
self.auth_cookie = Some(auth_cookie);
|
||||
}
|
||||
|
||||
pub fn set_username(&mut self, username: String) {
|
||||
@@ -149,11 +150,7 @@ impl CachedCredential {
|
||||
|
||||
impl From<PasswordCredential> for CachedCredential {
|
||||
fn from(value: PasswordCredential) -> Self {
|
||||
Self::new(
|
||||
value.username().to_owned(),
|
||||
Some(value.password().to_owned()),
|
||||
AuthCookieCredential::new("", "", ""),
|
||||
)
|
||||
Self::new(value.username().to_owned(), Some(value.password().to_owned()), None)
|
||||
}
|
||||
}
|
||||
#[derive(Debug, Serialize, Deserialize, Type, Clone)]
|
||||
@@ -200,8 +197,8 @@ impl Credential {
|
||||
Credential::Cached(cred) => (
|
||||
cred.password(),
|
||||
None,
|
||||
Some(cred.auth_cookie.user_auth_cookie()),
|
||||
Some(cred.auth_cookie.prelogon_user_auth_cookie()),
|
||||
cred.auth_cookie.as_ref().map(|c| c.user_auth_cookie()),
|
||||
cred.auth_cookie.as_ref().map(|c| c.prelogon_user_auth_cookie()),
|
||||
None,
|
||||
),
|
||||
};
|
||||
@@ -230,6 +227,17 @@ impl From<SamlAuthData> for Credential {
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<SamlAuthResult> for Credential {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(value: SamlAuthResult) -> anyhow::Result<Self> {
|
||||
match value {
|
||||
SamlAuthResult::Success(auth_data) => Ok(Self::from(auth_data)),
|
||||
SamlAuthResult::Failure(err) => bail!(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PasswordCredential> for Credential {
|
||||
fn from(value: PasswordCredential) -> Self {
|
||||
Self::Password(value)
|
||||
|
@@ -29,7 +29,7 @@ pub async fn gateway_login(gateway: &str, cred: &Credential, gp_params: &GpParam
|
||||
params.extend(extra_params);
|
||||
params.insert("server", &gateway);
|
||||
|
||||
info!("Gateway login, user_agent: {}", gp_params.user_agent());
|
||||
info!("Perform gateway login, user_agent: {}", gp_params.user_agent());
|
||||
|
||||
let res = client
|
||||
.post(&login_url)
|
||||
|
@@ -109,7 +109,7 @@ pub async fn retrieve_config(portal: &str, cred: &Credential, gp_params: &GpPara
|
||||
params.insert("server", &server);
|
||||
params.insert("host", &server);
|
||||
|
||||
info!("Portal config, user_agent: {}", gp_params.user_agent());
|
||||
info!("Retrieve the portal config, user_agent: {}", gp_params.user_agent());
|
||||
|
||||
let res = client
|
||||
.post(&url)
|
||||
@@ -153,7 +153,9 @@ pub async fn retrieve_config(portal: &str, cred: &Credential, gp_params: &GpPara
|
||||
});
|
||||
|
||||
let user_auth_cookie = root.descendant_text("portal-userauthcookie").unwrap_or_default();
|
||||
let prelogon_user_auth_cookie = root.descendant_text("portal-prelogonuserauthcookie").unwrap_or_default();
|
||||
let prelogon_user_auth_cookie = root
|
||||
.descendant_text("portal-prelogonuserauthcookie")
|
||||
.unwrap_or_default();
|
||||
let config_digest = root.descendant_text("config-digest");
|
||||
|
||||
if gateways.is_empty() {
|
||||
|
@@ -116,6 +116,8 @@ pub async fn prelogin(portal: &str, gp_params: &GpParams) -> anyhow::Result<Prel
|
||||
|
||||
let client = Client::try_from(gp_params)?;
|
||||
|
||||
info!("Perform prelogin, user_agent: {}", gp_params.user_agent());
|
||||
|
||||
let res = client
|
||||
.post(&prelogin_url)
|
||||
.form(¶ms)
|
||||
|
@@ -96,7 +96,7 @@ impl<'a> SamlAuthLauncher<'a> {
|
||||
}
|
||||
|
||||
/// Launch the authenticator binary as the current user or SUDO_USER if available.
|
||||
pub async fn launch(self) -> anyhow::Result<Option<Credential>> {
|
||||
pub async fn launch(self) -> anyhow::Result<Credential> {
|
||||
let mut auth_cmd = Command::new(GP_AUTH_BINARY);
|
||||
auth_cmd.arg(self.server);
|
||||
|
||||
@@ -152,17 +152,10 @@ impl<'a> SamlAuthLauncher<'a> {
|
||||
.wait_with_output()
|
||||
.await?;
|
||||
|
||||
if self.default_browser {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let Ok(auth_result) = serde_json::from_slice::<SamlAuthResult>(&output.stdout) else {
|
||||
bail!("Failed to parse auth data")
|
||||
};
|
||||
|
||||
match auth_result {
|
||||
SamlAuthResult::Success(auth_data) => Ok(Some(Credential::from(auth_data))),
|
||||
SamlAuthResult::Failure(msg) => bail!(msg),
|
||||
}
|
||||
Credential::try_from(auth_result)
|
||||
}
|
||||
}
|
||||
|
@@ -17,9 +17,14 @@ impl BrowserAuthenticator<'_> {
|
||||
}
|
||||
|
||||
pub fn new_with_browser<'a>(auth_request: &'a str, browser: &'a str) -> BrowserAuthenticator<'a> {
|
||||
let browser = browser.trim();
|
||||
BrowserAuthenticator {
|
||||
auth_request,
|
||||
browser: Some(browser),
|
||||
browser: if browser.is_empty() || browser == "default" {
|
||||
None
|
||||
} else {
|
||||
Some(browser)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,7 +57,7 @@ impl BrowserAuthenticator<'_> {
|
||||
open::with_detached(path.as_ref(), app)?;
|
||||
} else {
|
||||
info!("Launching the default browser...");
|
||||
open::that_detached(path.as_ref())?;
|
||||
webbrowser::open(path.as_ref())?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@@ -9,4 +9,5 @@ pub enum WsEvent {
|
||||
ActiveGui,
|
||||
/// External authentication data
|
||||
AuthData(String),
|
||||
ResumeConnection,
|
||||
}
|
||||
|
@@ -1,10 +1,9 @@
|
||||
use tokio::fs;
|
||||
|
||||
use crate::GP_SERVICE_LOCK_FILE;
|
||||
use super::lock_file::gpservice_lock_info;
|
||||
|
||||
async fn read_port() -> anyhow::Result<String> {
|
||||
let port = fs::read_to_string(GP_SERVICE_LOCK_FILE).await?;
|
||||
Ok(port.trim().to_string())
|
||||
let lock_info = gpservice_lock_info().await?;
|
||||
|
||||
Ok(lock_info.port.to_string())
|
||||
}
|
||||
|
||||
pub async fn http_endpoint() -> anyhow::Result<String> {
|
||||
|
@@ -1,19 +1,24 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use thiserror::Error;
|
||||
use tokio::fs;
|
||||
|
||||
pub struct LockFile {
|
||||
path: PathBuf,
|
||||
pid: u32,
|
||||
}
|
||||
|
||||
impl LockFile {
|
||||
pub fn new<P: Into<PathBuf>>(path: P) -> Self {
|
||||
Self { path: path.into() }
|
||||
pub fn new<P: Into<PathBuf>>(path: P, pid: u32) -> Self {
|
||||
Self { path: path.into(), pid }
|
||||
}
|
||||
|
||||
pub fn exists(&self) -> bool {
|
||||
self.path.exists()
|
||||
}
|
||||
|
||||
pub fn lock(&self, content: impl AsRef<[u8]>) -> anyhow::Result<()> {
|
||||
pub fn lock(&self, content: &str) -> anyhow::Result<()> {
|
||||
let content = format!("{}:{}", self.pid, content);
|
||||
std::fs::write(&self.path, content)?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -37,3 +42,87 @@ impl LockFile {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum LockFileError {
|
||||
#[error("Failed to read lock file: {0}")]
|
||||
IoError(#[from] std::io::Error),
|
||||
|
||||
#[error("Invalid lock file format: expected 'pid:port'")]
|
||||
InvalidFormat,
|
||||
|
||||
#[error("Invalid PID value: {0}")]
|
||||
InvalidPid(std::num::ParseIntError),
|
||||
|
||||
#[error("Invalid port value: {0}")]
|
||||
InvalidPort(std::num::ParseIntError),
|
||||
}
|
||||
|
||||
pub struct LockInfo {
|
||||
pub pid: u32,
|
||||
pub port: u32,
|
||||
}
|
||||
|
||||
impl LockInfo {
|
||||
async fn from_file(path: impl AsRef<std::path::Path>) -> Result<Self, LockFileError> {
|
||||
let content = fs::read_to_string(path).await?;
|
||||
Self::parse(&content)
|
||||
}
|
||||
|
||||
fn parse(content: &str) -> Result<Self, LockFileError> {
|
||||
let mut parts = content.trim().split(':');
|
||||
|
||||
let pid = parts
|
||||
.next()
|
||||
.ok_or(LockFileError::InvalidFormat)?
|
||||
.parse()
|
||||
.map_err(LockFileError::InvalidPid)?;
|
||||
|
||||
let port = parts
|
||||
.next()
|
||||
.ok_or(LockFileError::InvalidFormat)?
|
||||
.parse()
|
||||
.map_err(LockFileError::InvalidPort)?;
|
||||
|
||||
// Ensure there are no extra parts after pid:port
|
||||
if parts.next().is_some() {
|
||||
return Err(LockFileError::InvalidFormat);
|
||||
}
|
||||
|
||||
Ok(Self { pid, port })
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn gpservice_lock_info() -> Result<LockInfo, LockFileError> {
|
||||
LockInfo::from_file(crate::GP_SERVICE_LOCK_FILE).await
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_parse_valid_input() {
|
||||
let info = LockInfo::parse("1234:8080").unwrap();
|
||||
assert_eq!(info.pid, 1234);
|
||||
assert_eq!(info.port, 8080);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_invalid_format() {
|
||||
assert!(matches!(
|
||||
LockInfo::parse("123:456:789"),
|
||||
Err(LockFileError::InvalidFormat)
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_invalid_numbers() {
|
||||
assert!(matches!(LockInfo::parse("abc:8080"), Err(LockFileError::InvalidPid(_))));
|
||||
|
||||
assert!(matches!(
|
||||
LockInfo::parse("1234:abc"),
|
||||
Err(LockFileError::InvalidPort(_))
|
||||
));
|
||||
}
|
||||
}
|
||||
|
@@ -10,12 +10,24 @@ pub fn openssl_conf() -> String {
|
||||
|
||||
[openssl_init]
|
||||
ssl_conf = ssl_sect
|
||||
providers = provider_sect
|
||||
|
||||
[ssl_sect]
|
||||
system_default = system_default_sect
|
||||
|
||||
[system_default_sect]
|
||||
Options = {}",
|
||||
Options = {}
|
||||
|
||||
[provider_sect]
|
||||
default = default_sect
|
||||
legacy = legacy_sect
|
||||
|
||||
[default_sect]
|
||||
activate = 1
|
||||
|
||||
[legacy_sect]
|
||||
activate = 1
|
||||
",
|
||||
option
|
||||
)
|
||||
}
|
||||
|
@@ -42,6 +42,8 @@
|
||||
|
||||
overrideMain = {...}: {
|
||||
postPatch = ''
|
||||
substituteInPlace crates/common/src/vpn_utils.rs \
|
||||
--replace-fail /etc/vpnc/vpnc-script ${pkgs.vpnc-scripts}/bin/vpnc-script
|
||||
substituteInPlace crates/gpapi/src/lib.rs \
|
||||
--replace-fail /usr/bin/gpclient $out/bin/gpclient \
|
||||
--replace-fail /usr/bin/gpservice $out/bin/gpservice \
|
||||
|
@@ -10,6 +10,10 @@ install:
|
||||
install -Dm755 artifacts/usr/bin/gpgui $(DESTDIR)/usr/bin/gpgui; \
|
||||
fi
|
||||
|
||||
# Install the disconnect hooks
|
||||
install -Dm755 artifacts/usr/lib/NetworkManager/dispatcher.d/pre-down.d/gpclient.down $(DESTDIR)/usr/lib/NetworkManager/dispatcher.d/pre-down.d/gpclient.down
|
||||
install -Dm755 artifacts/usr/lib/NetworkManager/dispatcher.d/gpclient-nm-hook $(DESTDIR)/usr/lib/NetworkManager/dispatcher.d/gpclient-nm-hook
|
||||
|
||||
install -Dm644 artifacts/usr/share/applications/gpgui.desktop $(DESTDIR)/usr/share/applications/gpgui.desktop
|
||||
install -Dm644 artifacts/usr/share/icons/hicolor/scalable/apps/gpgui.svg $(DESTDIR)/usr/share/icons/hicolor/scalable/apps/gpgui.svg
|
||||
install -Dm644 artifacts/usr/share/icons/hicolor/32x32/apps/gpgui.png $(DESTDIR)/usr/share/icons/hicolor/32x32/apps/gpgui.png
|
||||
@@ -26,6 +30,9 @@ uninstall:
|
||||
rm -f $(DESTDIR)/usr/bin/gpgui-helper
|
||||
rm -f $(DESTDIR)/usr/bin/gpgui
|
||||
|
||||
rm -f $(DESTDIR)/usr/lib/NetworkManager/dispatcher.d/pre-down.d/gpclient.down
|
||||
rm -f $(DESTDIR)/usr/lib/NetworkManager/dispatcher.d/gpclient-nm-hook
|
||||
|
||||
rm -f $(DESTDIR)/usr/share/applications/gpgui.desktop
|
||||
rm -f $(DESTDIR)/usr/share/icons/hicolor/scalable/apps/gpgui.svg
|
||||
rm -f $(DESTDIR)/usr/share/icons/hicolor/32x32/apps/gpgui.png
|
||||
|
26
packaging/files/usr/lib/NetworkManager/dispatcher.d/gpclient-nm-hook
Executable file
26
packaging/files/usr/lib/NetworkManager/dispatcher.d/gpclient-nm-hook
Executable file
@@ -0,0 +1,26 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Resume the VPN connection if the network comes back up
|
||||
|
||||
set -e
|
||||
|
||||
PIDFILE=/tmp/gpservice_disconnected.pid
|
||||
|
||||
resume_vpn() {
|
||||
if [ -f $PIDFILE ]; then
|
||||
PID=$(cat $PIDFILE)
|
||||
|
||||
# Always remove the PID file
|
||||
rm $PIDFILE
|
||||
|
||||
# Ensure the PID is a gpservice process
|
||||
if ps -p $PID -o comm= | grep -q gpservice; then
|
||||
# Send a USR2 signal to the gpclient process to resume the VPN connection
|
||||
kill -USR2 $PID
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
if [ "$2" = "up" ]; then
|
||||
resume_vpn
|
||||
fi
|
@@ -0,0 +1,6 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
# Disconnect the VPN connection before the network goes down
|
||||
/usr/bin/gpclient disconnect --wait 3
|
@@ -55,6 +55,13 @@ make build OFFLINE=@OFFLINE@ BUILD_FE=0
|
||||
%{_datadir}/icons/hicolor/scalable/apps/gpgui.svg
|
||||
%{_datadir}/polkit-1/actions/com.yuezk.gpgui.policy
|
||||
|
||||
%dir /usr/lib/NetworkManager
|
||||
%dir /usr/lib/NetworkManager/dispatcher.d
|
||||
%dir /usr/lib/NetworkManager/dispatcher.d/pre-down.d
|
||||
|
||||
/usr/lib/NetworkManager/dispatcher.d/pre-down.d/gpclient.down
|
||||
/usr/lib/NetworkManager/dispatcher.d/gpclient-nm-hook
|
||||
|
||||
%dir %{_datadir}/icons/hicolor
|
||||
%dir %{_datadir}/icons/hicolor/32x32
|
||||
%dir %{_datadir}/icons/hicolor/32x32/apps
|
||||
|
2
rust-toolchain.toml
Normal file
2
rust-toolchain.toml
Normal file
@@ -0,0 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "1.71.1"
|
59
scripts/deb-install.sh
Executable file
59
scripts/deb-install.sh
Executable file
@@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
# Usage: ./deb-install.sh <version>
|
||||
|
||||
usage() {
|
||||
echo "Usage: $0 <version>"
|
||||
echo "Example: $0 2.3.9"
|
||||
exit 1
|
||||
}
|
||||
|
||||
if [ $# -ne 1 ]; then
|
||||
usage
|
||||
fi
|
||||
|
||||
VERSION=$1
|
||||
|
||||
# Check the architecture, only support x86_64 and aarch64/arm64
|
||||
ARCH=$(uname -m)
|
||||
|
||||
# Normalize the architecture name
|
||||
if [ "$ARCH" == "x86_64" ]; then
|
||||
ARCH="amd64"
|
||||
elif [ "$ARCH" == "aarch64" ] || [ "$ARCH" == "arm64" ]; then
|
||||
ARCH="arm64"
|
||||
else
|
||||
echo "Unsupported architecture: $ARCH"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
LIB_JAVASCRIPT_x86="http://launchpadlibrarian.net/704701345/libjavascriptcoregtk-4.0-18_2.43.3-1_amd64.deb"
|
||||
LIB_WEBKIT_x86="http://launchpadlibrarian.net/704701349/libwebkit2gtk-4.0-37_2.43.3-1_amd64.deb"
|
||||
|
||||
LIB_JAVASCRIPT_arm="http://launchpadlibrarian.net/704735771/libjavascriptcoregtk-4.0-18_2.43.3-1_arm64.deb"
|
||||
LIB_WEBKIT_arm="http://launchpadlibrarian.net/704735777/libwebkit2gtk-4.0-37_2.43.3-1_arm64.deb"
|
||||
|
||||
DEB_URL="https://github.com/yuezk/GlobalProtect-openconnect/releases/download/v${VERSION}/globalprotect-openconnect_${VERSION}-1_${ARCH}.deb"
|
||||
|
||||
# Install the dependencies
|
||||
if [ "$ARCH" == "amd64" ]; then
|
||||
wget -O /tmp/libjavascriptcoregtk.deb $LIB_JAVASCRIPT_x86
|
||||
wget -O /tmp/libwebkit2gtk.deb $LIB_WEBKIT_x86
|
||||
else
|
||||
wget -O /tmp/libjavascriptcoregtk.deb $LIB_JAVASCRIPT_arm
|
||||
wget -O /tmp/libwebkit2gtk.deb $LIB_WEBKIT_arm
|
||||
fi
|
||||
|
||||
sudo dpkg -i /tmp/libjavascriptcoregtk.deb /tmp/libwebkit2gtk.deb
|
||||
|
||||
# Install the package
|
||||
wget -O /tmp/globalprotect-openconnect.deb $DEB_URL
|
||||
sudo apt install --fix-broken -y /tmp/globalprotect-openconnect.deb
|
||||
|
||||
# Clean up
|
||||
rm /tmp/libjavascriptcoregtk.deb /tmp/libwebkit2gtk.deb /tmp/globalprotect-openconnect.deb
|
||||
|
||||
echo ""
|
||||
echo "GlobalProtect OpenConnect VPN client has been installed successfully."
|
@@ -40,7 +40,7 @@ release_tag() {
|
||||
gh -R "$REPO" release create $TAG \
|
||||
--title "$TAG" \
|
||||
--notes "$RELEASE_NOTES" \
|
||||
"$PROJECT_DIR"/.build/artifacts/artifact-source/* \
|
||||
"$PROJECT_DIR"/.build/artifacts/artifact-source*/* \
|
||||
"$PROJECT_DIR"/.build/artifacts/artifact-gpgui-*/*
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user