Compare commits

..

1 Commits

Author SHA1 Message Date
Kevin Yue
3ca72d3cbd fix: disconnect VPN when sleep 2025-01-20 11:59:40 +08:00
29 changed files with 77 additions and 364 deletions

View File

@@ -44,8 +44,7 @@ jobs:
with: with:
version: 9 version: 9
- name: Prepare workspace - name: Prepare workspace
run: rm -rf source && mkdir -p source/artifacts run: rm -rf source && mkdir source
- name: Checkout GlobalProtect-openconnect - name: Checkout GlobalProtect-openconnect
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
@@ -53,7 +52,6 @@ jobs:
repository: yuezk/GlobalProtect-openconnect repository: yuezk/GlobalProtect-openconnect
ref: ${{ github.ref }} ref: ${{ github.ref }}
path: source/gp path: source/gp
- name: Create tarball - name: Create tarball
run: | run: |
cd source/gp cd source/gp
@@ -62,69 +60,13 @@ jobs:
touch SNAPSHOT touch SNAPSHOT
fi fi
make tarball make tarball
mv -v .build/tarball/*.tar.gz ../artifacts/
- name: Generate RPM spec file
env:
RELEASE_TAG: ${{ github.ref == 'refs/heads/dev' && 'snapshot' || github.ref_name }}
run: |
cd source/gp
make init-rpm \
REVISION='1%{?dist}' \
RPM_SOURCE=https://github.com/yuezk/GlobalProtect-openconnect/releases/download/${RELEASE_TAG}/%{name}-%{version}.tar.gz
mv -v .build/rpm/*.spec ../artifacts/
- name: Upload tarball - name: Upload tarball
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: artifact-source name: artifact-source
if-no-files-found: error if-no-files-found: error
path: | path: |
source/artifacts/* source/gp/.build/tarball/*.tar.gz
tarball-offline:
if: ${{ github.ref == 'refs/heads/dev' || startsWith(github.ref, 'refs/tags/') }}
runs-on: ubuntu-latest
needs:
- tarball
steps:
- uses: pnpm/action-setup@v4
with:
version: 9
- name: Prepare workspace
run: rm -rf source-offline && mkdir source-offline
- name: Download tarball
uses: actions/download-artifact@v4
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@v4
with:
path: source-offline/*.offline.tar.gz
name: artifact-source-offline
if-no-files-found: error
build-gp: build-gp:
needs: needs:
@@ -226,7 +168,6 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: needs:
- tarball - tarball
- tarball-offline
- build-gp - build-gp
- build-gpgui - build-gpgui

View File

@@ -52,27 +52,23 @@ jobs:
version: 9 version: 9
- name: Prepare workspace - name: Prepare workspace
run: rm -rf publish-ppa && mkdir publish-ppa run: rm -rf publish-ppa && mkdir publish-ppa
- name: Download ${{ inputs.tag }} offline source code - name: Download ${{ inputs.tag }} source code
env: uses: robinraju/release-downloader@v1.9
GH_TOKEN: ${{ secrets.GH_PAT }} with:
run: | token: ${{ secrets.GH_PAT }}
gh -R yuezk/GlobalProtect-openconnect \ tag: ${{ inputs.tag }}
release download ${{ inputs.tag }} \ fileName: globalprotect-openconnect-*.tar.gz
--pattern '*.offline.tar.gz' \ tarBall: false
--dir publish-ppa zipBall: false
- name: Patch the source code out-file-path: publish-ppa
- name: Make the offline tarball
run: | run: |
cd publish-ppa cd publish-ppa
tar -xf globalprotect-openconnect-*.tar.gz
# Rename the source tarball without the offline suffix
mv -v *.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-*/ cd globalprotect-openconnect-*/
make tarball OFFLINE=1
# Prepare the debian directory with custom files # Prepare the debian directory with custom files
mkdir -p .build/debian mkdir -p .build/debian
@@ -82,6 +78,7 @@ jobs:
cp -v packaging/deb/postrm .build/debian/postrm cp -v packaging/deb/postrm .build/debian/postrm
sed -i "s/@RUST@/cargo-1.80/g" .build/debian/control sed -i "s/@RUST@/cargo-1.80/g" .build/debian/control
sed -i "s/@OFFLINE@/1/g" .build/debian/rules sed -i "s/@OFFLINE@/1/g" .build/debian/rules
sed -i "s/@BUILD_GUI@/1/g" .build/debian/rules sed -i "s/@BUILD_GUI@/1/g" .build/debian/rules
sed -i "s/@RUST_VERSION@/1.80/g" .build/debian/rules sed -i "s/@RUST_VERSION@/1.80/g" .build/debian/rules
@@ -92,7 +89,7 @@ jobs:
repository: "yuezk/globalprotect-openconnect" repository: "yuezk/globalprotect-openconnect"
gpg_private_key: ${{ secrets.PPA_GPG_PRIVATE_KEY }} gpg_private_key: ${{ secrets.PPA_GPG_PRIVATE_KEY }}
gpg_passphrase: ${{ secrets.PPA_GPG_PASSPHRASE }} gpg_passphrase: ${{ secrets.PPA_GPG_PASSPHRASE }}
tarball: publish-ppa/globalprotect-openconnect-*.tar.gz tarball: publish-ppa/globalprotect-openconnect-*/.build/tarball/*.tar.gz
debian_dir: publish-ppa/globalprotect-openconnect-*/.build/debian debian_dir: publish-ppa/globalprotect-openconnect-*/.build/debian
deb_email: "k3vinyue@gmail.com" deb_email: "k3vinyue@gmail.com"
deb_fullname: "Kevin Yue" deb_fullname: "Kevin Yue"

View File

@@ -96,16 +96,15 @@ jobs:
steps: steps:
- name: Prepare workspace - name: Prepare workspace
run: rm -rf build-${{ matrix.package }} && mkdir -p build-${{ matrix.package }} run: rm -rf build-${{ matrix.package }} && mkdir -p build-${{ matrix.package }}
- name: Download ${{ inputs.tag }} source code - name: Download ${{ inputs.tag }} source code
env: uses: robinraju/release-downloader@v1.9
GH_TOKEN: ${{ secrets.GH_PAT }} with:
run: | token: ${{ secrets.GH_PAT }}
gh -R yuezk/GlobalProtect-openconnect \ tag: ${{ inputs.tag }}
release download ${{ inputs.tag }} \ fileName: globalprotect-openconnect-*.tar.gz
--pattern '*[^offline].tar.gz' \ tarBall: false
--dir build-${{ matrix.package }} zipBall: false
out-file-path: build-${{ matrix.package }}
- name: Docker Login - name: Docker Login
run: echo ${{ secrets.DOCKER_HUB_TOKEN }} | docker login -u ${{ secrets.DOCKER_HUB_USERNAME }} --password-stdin run: echo ${{ secrets.DOCKER_HUB_TOKEN }} | docker login -u ${{ secrets.DOCKER_HUB_USERNAME }} --password-stdin
- name: Build ${{ matrix.package }} package in Docker - name: Build ${{ matrix.package }} package in Docker

20
Cargo.lock generated
View File

@@ -176,7 +176,7 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
[[package]] [[package]]
name = "auth" name = "auth"
version = "2.4.3" version = "2.4.1"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"block2", "block2",
@@ -188,7 +188,6 @@ dependencies = [
"objc2-web-kit", "objc2-web-kit",
"open", "open",
"regex", "regex",
"serde_json",
"tauri", "tauri",
"tiny_http", "tiny_http",
"tokio", "tokio",
@@ -196,10 +195,7 @@ dependencies = [
"uuid", "uuid",
"webbrowser", "webbrowser",
"webkit2gtk", "webkit2gtk",
"webview2-com",
"which", "which",
"windows 0.58.0",
"windows-core 0.58.0",
] ]
[[package]] [[package]]
@@ -646,7 +642,7 @@ dependencies = [
[[package]] [[package]]
name = "common" name = "common"
version = "2.4.3" version = "2.4.1"
dependencies = [ dependencies = [
"is_executable", "is_executable",
] ]
@@ -1594,7 +1590,7 @@ dependencies = [
[[package]] [[package]]
name = "gpapi" name = "gpapi"
version = "2.4.3" version = "2.4.1"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"base64 0.22.1", "base64 0.22.1",
@@ -1630,7 +1626,7 @@ dependencies = [
[[package]] [[package]]
name = "gpauth" name = "gpauth"
version = "2.4.3" version = "2.4.1"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"auth", "auth",
@@ -1649,7 +1645,7 @@ dependencies = [
[[package]] [[package]]
name = "gpclient" name = "gpclient"
version = "2.4.3" version = "2.4.1"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"clap", "clap",
@@ -1671,7 +1667,7 @@ dependencies = [
[[package]] [[package]]
name = "gpgui-helper" name = "gpgui-helper"
version = "2.4.3" version = "2.4.1"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"clap", "clap",
@@ -1689,7 +1685,7 @@ dependencies = [
[[package]] [[package]]
name = "gpservice" name = "gpservice"
version = "2.4.3" version = "2.4.1"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"axum", "axum",
@@ -2955,7 +2951,7 @@ dependencies = [
[[package]] [[package]]
name = "openconnect" name = "openconnect"
version = "2.4.3" version = "2.4.1"
dependencies = [ dependencies = [
"cc", "cc",
"common", "common",

View File

@@ -11,7 +11,7 @@ members = [
[workspace.package] [workspace.package]
rust-version = "1.80" rust-version = "1.80"
version = "2.4.3" version = "2.4.1"
authors = ["Kevin Yue <k3vinyue@gmail.com>"] authors = ["Kevin Yue <k3vinyue@gmail.com>"]
homepage = "https://github.com/yuezk/GlobalProtect-openconnect" homepage = "https://github.com/yuezk/GlobalProtect-openconnect"
edition = "2021" edition = "2021"

View File

@@ -8,8 +8,6 @@ RUST_VERSION = 1.80
VERSION = $(shell $(CARGO) metadata --no-deps --format-version 1 | jq -r '.packages[0].version') VERSION = $(shell $(CARGO) metadata --no-deps --format-version 1 | jq -r '.packages[0].version')
REVISION ?= 1 REVISION ?= 1
RPM_SOURCE ?= %{name}.tar.gz
PPA_REVISION ?= 1 PPA_REVISION ?= 1
PKG_NAME = globalprotect-openconnect PKG_NAME = globalprotect-openconnect
PKG = $(PKG_NAME)-$(VERSION) PKG = $(PKG_NAME)-$(VERSION)
@@ -236,7 +234,6 @@ init-rpm: clean-rpm
sed -i "s/@VERSION@/$(VERSION)/g" .build/rpm/globalprotect-openconnect.spec sed -i "s/@VERSION@/$(VERSION)/g" .build/rpm/globalprotect-openconnect.spec
sed -i "s/@REVISION@/$(REVISION)/g" .build/rpm/globalprotect-openconnect.spec sed -i "s/@REVISION@/$(REVISION)/g" .build/rpm/globalprotect-openconnect.spec
sed -i "s|@SOURCE@|$(RPM_SOURCE)|g" .build/rpm/globalprotect-openconnect.spec
sed -i "s/@OFFLINE@/$(OFFLINE)/g" .build/rpm/globalprotect-openconnect.spec sed -i "s/@OFFLINE@/$(OFFLINE)/g" .build/rpm/globalprotect-openconnect.spec
sed -i "s/@DATE@/$(shell LC_ALL=en.US date "+%a %b %d %Y")/g" .build/rpm/globalprotect-openconnect.spec sed -i "s/@DATE@/$(shell LC_ALL=en.US date "+%a %b %d %Y")/g" .build/rpm/globalprotect-openconnect.spec

View File

@@ -70,7 +70,7 @@ The GUI version is also available after you installed it. You can launch it from
### Debian/Ubuntu based distributions ### Debian/Ubuntu based distributions
#### Install from PPA #### Install from PPA (Ubuntu > 18.04)
``` ```
sudo add-apt-repository ppa:yuezk/globalprotect-openconnect sudo add-apt-repository ppa:yuezk/globalprotect-openconnect
@@ -81,6 +81,10 @@ sudo apt-get install globalprotect-openconnect
> >
> For Linux Mint, you might need to import the GPG key with: `sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 7937C393082992E5D6E4A60453FC26B43838D761` if you encountered an error `gpg: keyserver receive failed: General error`. > For Linux Mint, you might need to import the GPG key with: `sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 7937C393082992E5D6E4A60453FC26B43838D761` if you encountered an error `gpg: keyserver receive failed: General error`.
#### **Ubuntu 18.04**
The latest package is not available in the PPA, 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.
#### Install from deb package #### Install from deb package
Download the latest deb package from [releases](https://github.com/yuezk/GlobalProtect-openconnect/releases) page. Then install it with `apt`: Download the latest deb package from [releases](https://github.com/yuezk/GlobalProtect-openconnect/releases) page. Then install it with `apt`:

View File

@@ -22,7 +22,7 @@ serde_json.workspace = true
whoami.workspace = true whoami.workspace = true
tempfile.workspace = true tempfile.workspace = true
reqwest.workspace = true reqwest.workspace = true
directories.workspace = true directories = "5.0"
compile-time.workspace = true compile-time.workspace = true
[features] [features]

View File

@@ -1,21 +1,17 @@
use std::{env::temp_dir, fs::File, str::FromStr}; use std::{env::temp_dir, fs::File};
use anyhow::bail;
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use gpapi::{ use gpapi::{
clap::{handle_error, Args, InfoLevelVerbosity}, clap::{handle_error, Args, InfoLevelVerbosity},
utils::openssl, utils::openssl,
}; };
use log::info; use log::info;
use sysinfo::{Pid, System};
use tempfile::NamedTempFile; use tempfile::NamedTempFile;
use tokio::fs;
use crate::{ use crate::{
connect::{ConnectArgs, ConnectHandler}, connect::{ConnectArgs, ConnectHandler},
disconnect::{DisconnectArgs, DisconnectHandler}, disconnect::{DisconnectArgs, DisconnectHandler},
launch_gui::{LaunchGuiArgs, LaunchGuiHandler}, launch_gui::{LaunchGuiArgs, LaunchGuiHandler},
GP_CLIENT_LOCK_FILE,
}; };
const VERSION: &str = concat!(env!("CARGO_PKG_VERSION"), " (", compile_time::date_str!(), ")"); const VERSION: &str = concat!(env!("CARGO_PKG_VERSION"), " (", compile_time::date_str!(), ")");
@@ -81,25 +77,6 @@ impl Args for Cli {
} }
impl Cli { impl Cli {
async fn is_running(&self) -> bool {
let Ok(c) = fs::read_to_string(GP_CLIENT_LOCK_FILE).await else {
return false;
};
let Ok(pid) = Pid::from_str(c.trim()) else {
return false;
};
let s = System::new_all();
let Some(p) = s.process(pid) else {
return false;
};
p.exe()
.map(|exe| exe.to_string_lossy().contains("gpclient"))
.unwrap_or(false)
}
fn fix_openssl(&self) -> anyhow::Result<Option<NamedTempFile>> { fn fix_openssl(&self) -> anyhow::Result<Option<NamedTempFile>> {
if self.fix_openssl { if self.fix_openssl {
let file = openssl::fix_openssl_env()?; let file = openssl::fix_openssl_env()?;
@@ -110,11 +87,6 @@ impl Cli {
} }
async fn run(&self) -> anyhow::Result<()> { async fn run(&self) -> anyhow::Result<()> {
// check if an instance is running
if self.is_running().await {
bail!("Another instance of the client is already running");
}
// The temp file will be dropped automatically when the file handle is dropped // The temp file will be dropped automatically when the file handle is dropped
// So, declare it here to ensure it's not dropped // So, declare it here to ensure it's not dropped
let _file = self.fix_openssl()?; let _file = self.fix_openssl()?;

View File

@@ -87,8 +87,8 @@ pub(crate) struct ConnectArgs {
#[arg(long, value_enum, default_value_t = ConnectArgs::default_os())] #[arg(long, value_enum, default_value_t = ConnectArgs::default_os())]
os: Os, os: Os,
#[arg(long, help = "If not specified, it will be computed based on the --os option")] #[arg(long, default_value_t = ConnectArgs::default_os_version())]
os_version: Option<String>, os_version: String,
#[arg(long, help = "Disable DTLS and ESP")] #[arg(long, help = "Disable DTLS and ESP")]
no_dtls: bool, no_dtls: bool,
@@ -121,12 +121,8 @@ impl ConnectArgs {
} }
} }
fn os_version(&self) -> String { fn default_os_version() -> String {
if let Some(os_version) = self.os_version.as_deref() { match ConnectArgs::default_os() {
return os_version.to_string();
}
match self.os {
Os::Linux => format!("Linux {}", whoami::distro()), Os::Linux => format!("Linux {}", whoami::distro()),
Os::Windows => String::from("Microsoft Windows 11 Pro , 64-bit"), Os::Windows => String::from("Microsoft Windows 11 Pro , 64-bit"),
Os::Mac => String::from("Apple Mac OS X 13.4.0"), Os::Mac => String::from("Apple Mac OS X 13.4.0"),
@@ -153,7 +149,7 @@ impl<'a> ConnectHandler<'a> {
GpParams::builder() GpParams::builder()
.user_agent(&self.args.user_agent) .user_agent(&self.args.user_agent)
.client_os(ClientOs::from(&self.args.os)) .client_os(ClientOs::from(&self.args.os))
.os_version(self.args.os_version()) .os_version(self.args.os_version.clone())
.ignore_tls_errors(self.shared_args.ignore_tls_errors) .ignore_tls_errors(self.shared_args.ignore_tls_errors)
.certificate(self.args.certificate.clone()) .certificate(self.args.certificate.clone())
.sslkey(self.args.sslkey.clone()) .sslkey(self.args.sslkey.clone())
@@ -363,7 +359,7 @@ impl<'a> ConnectHandler<'a> {
None None
}; };
let os_version = self.args.os_version(); let os_version = self.args.os_version.clone();
let verbose = self.shared_args.verbose.to_verbose_arg(); let verbose = self.shared_args.verbose.to_verbose_arg();
let auth_launcher = SamlAuthLauncher::new(&self.args.server) let auth_launcher = SamlAuthLauncher::new(&self.args.server)
.gateway(is_gateway) .gateway(is_gateway)

View File

@@ -177,12 +177,11 @@ mod signals {
tokio::select! { tokio::select! {
_ = user_sig1.recv() => { _ = user_sig1.recv() => {
info!("Received SIGUSR1 signal"); info!("Received SIGUSR1 signal");
if vpn_ctx.disconnect().await { vpn_ctx.disconnect().await;
// Write the PID to a dedicated file to indicate that the VPN task is disconnected via SIGUSR1 // Write the PID to a dedicated file to indicate that the VPN task is disconnected via SIGUSR1
let pid = std::process::id(); let pid = std::process::id();
if let Err(err) = tokio::fs::write(DISCONNECTED_PID_FILE, pid.to_string()).await { if let Err(err) = tokio::fs::write(DISCONNECTED_PID_FILE, pid.to_string()).await {
warn!("Failed to write PID to file: {}", err); warn!("Failed to write PID to file: {}", err);
}
} }
} }
_ = user_sig2.recv() => { _ = user_sig2.recv() => {

View File

@@ -90,7 +90,7 @@ impl VpnTaskContext {
}); });
} }
pub async fn disconnect(&self) -> bool { pub async fn disconnect(&self) {
if let Some(disconnect_rx) = self.disconnect_rx.write().await.take() { if let Some(disconnect_rx) = self.disconnect_rx.write().await.take() {
info!("Disconnecting VPN..."); info!("Disconnecting VPN...");
if let Some(vpn) = self.vpn_handle.read().await.as_ref() { if let Some(vpn) = self.vpn_handle.read().await.as_ref() {
@@ -101,13 +101,9 @@ impl VpnTaskContext {
// Wait for the VPN to be disconnected // Wait for the VPN to be disconnected
disconnect_rx.await.ok(); disconnect_rx.await.ok();
info!("VPN disconnected"); info!("VPN disconnected");
true
} else { } else {
info!("VPN is not connected, skip disconnect"); info!("VPN is not connected, skip disconnect");
self.vpn_state_tx.send(VpnState::Disconnected).ok(); self.vpn_state_tx.send(VpnState::Disconnected).ok();
false
} }
} }
} }

View File

@@ -1,13 +1,5 @@
# Changelog # Changelog
## 2.4.3 - 2025-01-21
- Do not use static default value for `--os-version` option.
## 2.4.2 - 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.4.1 - 2025-01-09 ## 2.4.1 - 2025-01-09
- Fix the network issue with OpenSSL < 3.0.4 - Fix the network issue with OpenSSL < 3.0.4

View File

@@ -28,21 +28,15 @@ regex = { workspace = true, optional = true }
tokio-util = { workspace = true, optional = true } tokio-util = { workspace = true, optional = true }
html-escape = { version = "0.2.13", optional = true } html-escape = { version = "0.2.13", optional = true }
[target.'cfg(not(any(target_os="macos", target_os="windows")))'.dependencies] [target.'cfg(not(target_os = "macos"))'.dependencies]
webkit2gtk = { version = "2", optional = true } webkit2gtk = { version = "2", optional = true }
[target.'cfg(target_os="macos")'.dependencies] [target.'cfg(target_os = "macos")'.dependencies]
block2 = { version = "0.5", optional = true } block2 = { version = "0.5", optional = true }
objc2 = { version = "0.5", optional = true } objc2 = { version = "0.5", optional = true }
objc2-foundation = { version = "0.2", optional = true } objc2-foundation = { version = "0.2", optional = true }
objc2-web-kit = { version = "0.2", optional = true } objc2-web-kit = { version = "0.2", optional = true }
[target.'cfg(target_os="windows")'.dependencies]
webview2-com = { version = "0.34", optional = true }
windows-core = { version = "0.58", optional = true }
windows = { version = "0.58", optional = true }
serde_json = { workspace = true, optional = true }
[features] [features]
browser-auth = [ browser-auth = [
"dep:webbrowser", "dep:webbrowser",
@@ -62,8 +56,4 @@ webview-auth = [
"dep:objc2", "dep:objc2",
"dep:objc2-foundation", "dep:objc2-foundation",
"dep:objc2-web-kit", "dep:objc2-web-kit",
"dep:webview2-com",
"dep:windows-core",
"dep:windows",
"dep:serde_json",
] ]

View File

@@ -1,4 +1,4 @@
use std::{env::temp_dir, fs}; use std::{env::temp_dir, fs, os::unix::fs::PermissionsExt};
use gpapi::{auth::SamlAuthData, GP_CALLBACK_PORT_FILENAME}; use gpapi::{auth::SamlAuthData, GP_CALLBACK_PORT_FILENAME};
use log::info; use log::info;
@@ -96,11 +96,7 @@ async fn wait_auth_data() -> anyhow::Result<SamlAuthData> {
// Write the port to a file // Write the port to a file
fs::write(&port_file, port.to_string())?; fs::write(&port_file, port.to_string())?;
#[cfg(unix)] fs::set_permissions(&port_file, fs::Permissions::from_mode(0o600))?;
{
use os::unix::fs::PermissionsExt;
fs::set_permissions(&port_file, fs::Permissions::from_mode(0o600))?;
}
// Remove the previous log file // Remove the previous log file
let callback_log = temp_dir().join("gpcallback.log"); let callback_log = temp_dir().join("gpcallback.log");

View File

@@ -1,9 +1,8 @@
mod auth_messenger; mod auth_messenger;
mod webview_auth; mod webview_auth;
#[cfg_attr(not(any(target_os = "macos", target_os = "windows")), path = "webview/unix.rs")] #[cfg_attr(not(target_os = "macos"), path = "webview/unix.rs")]
#[cfg_attr(target_os = "macos", path = "webview/macos.rs")] #[cfg_attr(target_os = "macos", path = "webview/macos.rs")]
#[cfg_attr(windows, path = "webview/windows.rs")]
mod platform_impl; mod platform_impl;
pub use webview_auth::WebviewAuthenticator; pub use webview_auth::WebviewAuthenticator;

View File

@@ -15,7 +15,7 @@ pub(crate) enum AuthDataLocation {
#[derive(Debug)] #[derive(Debug)]
pub(crate) enum AuthError { pub(crate) enum AuthError {
/// Failed to load page due to TLS error /// Failed to load page due to TLS error
#[cfg(not(any(target_os = "macos", target_os = "windows")))] #[cfg(not(target_os = "macos"))]
TlsError, TlsError,
/// 1. Found auth data in headers/body but it's invalid /// 1. Found auth data in headers/body but it's invalid
/// 2. Loaded an empty page, failed to load page. etc. /// 2. Loaded an empty page, failed to load page. etc.

View File

@@ -115,7 +115,7 @@ impl<'a> WebviewAuthenticator<'a> {
match auth_messenger.subscribe().await? { match auth_messenger.subscribe().await? {
AuthEvent::Close => bail!("Authentication cancelled"), AuthEvent::Close => bail!("Authentication cancelled"),
AuthEvent::RaiseWindow => self.raise_window(&auth_window), AuthEvent::RaiseWindow => self.raise_window(&auth_window),
#[cfg(not(any(target_os = "macos", target_os = "windows")))] #[cfg(not(target_os = "macos"))]
AuthEvent::Error(AuthError::TlsError) => bail!(gpapi::error::PortalError::TlsError), AuthEvent::Error(AuthError::TlsError) => bail!(gpapi::error::PortalError::TlsError),
AuthEvent::Error(AuthError::NotFound(location)) => { AuthEvent::Error(AuthError::NotFound(location)) => {
info!( info!(
@@ -261,10 +261,10 @@ impl<'a> WebviewAuthenticator<'a> {
info!("Raising auth window..."); info!("Raising auth window...");
#[cfg(any(target_os = "macos", target_os = "windows"))] #[cfg(target_os = "macos")]
let result = auth_window.show(); let result = auth_window.show();
#[cfg(not(any(target_os = "macos", target_os = "windows")))] #[cfg(not(target_os = "macos"))]
let result = { let result = {
use gpapi::utils::window::WindowExt; use gpapi::utils::window::WindowExt;
auth_window.raise() auth_window.raise()

View File

@@ -1,142 +0,0 @@
use log::warn;
use tauri::webview::PlatformWebview;
use webview2_com::{
pwstr_from_str, take_pwstr, ExecuteScriptCompletedHandler,
Microsoft::Web::WebView2::Win32::{
ICoreWebView2WebResourceResponseView, ICoreWebView2_14, ICoreWebView2_2,
COREWEBVIEW2_SERVER_CERTIFICATE_ERROR_ACTION_ALWAYS_ALLOW,
},
ServerCertificateErrorDetectedEventHandler, WebResourceResponseReceivedEventHandler,
};
use windows_core::{Interface, PWSTR};
use super::{
auth_messenger::AuthError,
webview_auth::{GetHeader, PlatformWebviewExt},
};
impl PlatformWebviewExt for PlatformWebview {
fn ignore_tls_errors(&self) -> anyhow::Result<()> {
unsafe {
let wv = self.controller().CoreWebView2()?.cast::<ICoreWebView2_14>()?;
let handler = ServerCertificateErrorDetectedEventHandler::create(Box::new(|_, e| {
if let Some(e) = e {
let _ = e.SetAction(COREWEBVIEW2_SERVER_CERTIFICATE_ERROR_ACTION_ALWAYS_ALLOW);
}
Ok(())
}));
wv.add_ServerCertificateErrorDetected(&handler, &mut Default::default())?;
}
Ok(())
}
fn load_url(&self, url: &str) -> anyhow::Result<()> {
let url = pwstr_from_str(url);
unsafe { self.controller().CoreWebView2()?.Navigate(url)? }
Ok(())
}
fn load_html(&self, html: &str) -> anyhow::Result<()> {
let html = pwstr_from_str(html);
unsafe { self.controller().CoreWebView2()?.NavigateToString(html)? }
Ok(())
}
fn get_html(&self, callback: Box<dyn Fn(anyhow::Result<String>) + 'static>) {
unsafe {
match self.controller().CoreWebView2() {
Ok(wv) => {
let js = "document.documentElement.outerHTML";
let js = pwstr_from_str(js);
let handler = ExecuteScriptCompletedHandler::create(Box::new(move |err, html| {
if let Err(err) = err {
callback(Err(anyhow::anyhow!(err)));
return Ok(());
}
// The returned HTML is JSON.stringify'd string, so we need to parse it
let res = match serde_json::from_str(&html) {
Ok(Some(html)) => Ok(html),
Ok(None) => Err(anyhow::anyhow!("No HTML returned")),
Err(err) => Err(anyhow::anyhow!(err)),
};
callback(res);
Ok(())
}));
if let Err(err) = wv.ExecuteScript(js, &handler) {
warn!("Failed to execute script: {}", err);
}
}
Err(err) => callback(Err(anyhow::anyhow!(err))),
}
}
}
}
impl GetHeader for ICoreWebView2WebResourceResponseView {
fn get_header(&self, key: &str) -> Option<String> {
unsafe {
let headers = self.Headers().ok()?;
let key = pwstr_from_str(key);
let mut contains = Default::default();
headers.Contains(key, &mut contains).ok()?;
if contains.as_bool() {
let mut value = PWSTR::null();
headers.GetHeader(key, &mut value).ok()?;
let value = take_pwstr(value);
Some(value)
} else {
None
}
}
}
}
pub trait PlatformWebviewOnResponse {
fn on_response(
&self,
callback: Box<dyn Fn(anyhow::Result<ICoreWebView2WebResourceResponseView, AuthError>) + 'static>,
);
}
impl PlatformWebviewOnResponse for PlatformWebview {
fn on_response(
&self,
callback: Box<dyn Fn(anyhow::Result<ICoreWebView2WebResourceResponseView, AuthError>) + 'static>,
) {
unsafe {
let _ = self
.controller()
.CoreWebView2()
.and_then(|wv| wv.cast::<ICoreWebView2_2>())
.map(|wv| {
let handler = WebResourceResponseReceivedEventHandler::create(Box::new(move |_, e| {
let Some(e) = e else {
return Ok(());
};
match e.Response() {
Ok(res) => callback(Ok(res)),
Err(err) => warn!("Failed to get response: {}", err),
}
Ok(())
}));
let _ = wv.add_WebResourceResponseReceived(&handler, &mut Default::default());
});
}
}
}

View File

@@ -27,7 +27,7 @@ chacha20poly1305 = { version = "0.10", features = ["std"] }
redact-engine.workspace = true redact-engine.workspace = true
url.workspace = true url.workspace = true
regex.workspace = true regex.workspace = true
uzers.workspace = true
serde_urlencoded.workspace = true serde_urlencoded.workspace = true
md5.workspace = true md5.workspace = true
sha256.workspace = true sha256.workspace = true
@@ -39,9 +39,6 @@ clap-verbosity-flag = { workspace = true, optional = true }
env_logger = { workspace = true, optional = true } env_logger = { workspace = true, optional = true }
log-reload = { version = "0.1", optional = true } log-reload = { version = "0.1", optional = true }
[target.'cfg(target_family="unix")'.dependencies]
uzers.workspace = true
[features] [features]
tauri = ["dep:tauri"] tauri = ["dep:tauri"]
clap = ["dep:clap", "dep:clap-verbosity-flag"] clap = ["dep:clap", "dep:clap-verbosity-flag"]

View File

@@ -104,7 +104,7 @@ impl SamlAuthData {
} }
let auth_data = decode_to_string(auth_data).map_err(|e| { let auth_data = decode_to_string(auth_data).map_err(|e| {
warn!("Failed to decode SAML auth data: {}", auth_data); warn!("Failed to decode SAML auth data: {}", e);
AuthDataParseError::Invalid(anyhow::anyhow!(e)) AuthDataParseError::Invalid(anyhow::anyhow!(e))
})?; })?;
let auth_data = Self::from_html(&auth_data)?; let auth_data = Self::from_html(&auth_data)?;

View File

@@ -1,7 +1,5 @@
mod login; mod login;
mod parse_gateways; mod parse_gateways;
#[cfg(unix)]
pub mod hip; pub mod hip;
pub use login::*; pub use login::*;

View File

@@ -4,10 +4,7 @@ pub mod error;
pub mod gateway; pub mod gateway;
pub mod gp_params; pub mod gp_params;
pub mod portal; pub mod portal;
#[cfg(unix)]
pub mod process; pub mod process;
pub mod service; pub mod service;
pub mod utils; pub mod utils;

View File

@@ -1,11 +1,8 @@
pub(crate) mod command_traits; pub(crate) mod command_traits;
pub(crate) mod gui_helper_launcher; pub(crate) mod gui_helper_launcher;
pub mod auth_launcher; pub mod auth_launcher;
pub mod gui_launcher; pub mod gui_launcher;
pub mod hip_launcher; pub mod hip_launcher;
pub mod service_launcher; pub mod service_launcher;
#[cfg(unix)]
pub mod users; pub mod users;

View File

@@ -9,7 +9,7 @@ pub mod lock_file;
pub mod openssl; pub mod openssl;
pub mod redact; pub mod redact;
pub mod request; pub mod request;
#[cfg(all(feature = "tauri", not(any(target_os = "macos", target_os = "windows"))))] #[cfg(feature = "tauri")]
pub mod window; pub mod window;
mod shutdown_signal; mod shutdown_signal;

View File

@@ -6,21 +6,15 @@ pub async fn shutdown_signal() {
}; };
#[cfg(unix)] #[cfg(unix)]
{ let terminate = async {
let terminate = async { signal::unix::signal(signal::unix::SignalKind::terminate())
signal::unix::signal(signal::unix::SignalKind::terminate()) .expect("failed to install signal handler")
.expect("failed to install signal handler") .recv()
.recv() .await;
.await; };
};
tokio::select! {
_ = ctrl_c => {},
_ = terminate => {},
}
}
#[cfg(not(unix))] tokio::select! {
{ _ = ctrl_c => {},
ctrl_c.await; _ = terminate => {},
} }
} }

View File

@@ -6,7 +6,7 @@ Group: Productivity/Networking/PPP
License: GPL-3.0 License: GPL-3.0
URL: https://github.com/yuezk/GlobalProtect-openconnect URL: https://github.com/yuezk/GlobalProtect-openconnect
Source: @SOURCE@ Source: %{name}.tar.gz
BuildRequires: make BuildRequires: make
BuildRequires: rust BuildRequires: rust

View File

@@ -1,2 +0,0 @@
[toolchain]
channel = "1.80.1"

View File

@@ -28,7 +28,7 @@ release_snapshot() {
echo "Uploading new assets..." echo "Uploading new assets..."
gh -R "$REPO" release upload "$TAG" \ gh -R "$REPO" release upload "$TAG" \
"$PROJECT_DIR"/.build/artifacts/artifact-source*/* \ "$PROJECT_DIR"/.build/artifacts/artifact-source/* \
"$PROJECT_DIR"/.build/artifacts/artifact-gpgui-*/* "$PROJECT_DIR"/.build/artifacts/artifact-gpgui-*/*
} }
@@ -40,7 +40,7 @@ release_tag() {
gh -R "$REPO" release create $TAG \ gh -R "$REPO" release create $TAG \
--title "$TAG" \ --title "$TAG" \
--notes "$RELEASE_NOTES" \ --notes "$RELEASE_NOTES" \
"$PROJECT_DIR"/.build/artifacts/artifact-source*/* \ "$PROJECT_DIR"/.build/artifacts/artifact-source/* \
"$PROJECT_DIR"/.build/artifacts/artifact-gpgui-*/* "$PROJECT_DIR"/.build/artifacts/artifact-gpgui-*/*
} }