mirror of
https://github.com/yuezk/GlobalProtect-openconnect.git
synced 2025-04-02 18:31:50 -04:00
Enhancements and Bug Fixes: Align Pre-login Behavior, TLS Error Ignorance, GUI Auto-Launch, and Documentation Improvements (#291)
This commit is contained in:
parent
03f8c98cb5
commit
8bc4049a0f
138
.github/workflows/build.yaml
vendored
138
.github/workflows/build.yaml
vendored
@ -6,8 +6,8 @@ on:
|
|||||||
- "*.md"
|
- "*.md"
|
||||||
- .vscode
|
- .vscode
|
||||||
- .devcontainer
|
- .devcontainer
|
||||||
# branches:
|
branches:
|
||||||
# - main
|
- main
|
||||||
# tags:
|
# tags:
|
||||||
# - v*.*.*
|
# - v*.*.*
|
||||||
jobs:
|
jobs:
|
||||||
@ -114,137 +114,3 @@ jobs:
|
|||||||
name: artifact-${{ matrix.arch }}-tauri
|
name: artifact-${{ matrix.arch }}-tauri
|
||||||
path: |
|
path: |
|
||||||
gpgui/.tmp/artifact
|
gpgui/.tmp/artifact
|
||||||
|
|
||||||
package-rpm:
|
|
||||||
needs: [setup-matrix, build-tauri]
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
arch: ${{ fromJson(needs.setup-matrix.outputs.matrix) }}
|
|
||||||
steps:
|
|
||||||
- name: Checkout gpgui repo
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
token: ${{ secrets.GH_PAT }}
|
|
||||||
repository: yuezk/gpgui
|
|
||||||
path: gpgui
|
|
||||||
|
|
||||||
- name: Download artifact-${{ matrix.arch }}
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: artifact-${{ matrix.arch }}-tauri
|
|
||||||
path: gpgui/.tmp/artifact
|
|
||||||
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v3
|
|
||||||
with:
|
|
||||||
platforms: ${{ matrix.arch }}
|
|
||||||
|
|
||||||
- name: Login to Docker Hub
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKER_HUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Create RPM package
|
|
||||||
run: |
|
|
||||||
docker run \
|
|
||||||
--rm \
|
|
||||||
-v $(pwd):/${{ github.workspace }} \
|
|
||||||
-w ${{ github.workspace }} \
|
|
||||||
--platform linux/${{ matrix.arch }} \
|
|
||||||
yuezk/gpdev:rpm-builder \
|
|
||||||
"./gpgui/scripts/build-rpm.sh"
|
|
||||||
|
|
||||||
- name: Upload rpm artifacts
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: artifact-${{ matrix.arch }}-rpm
|
|
||||||
path: |
|
|
||||||
gpgui/.tmp/artifact/*.rpm
|
|
||||||
|
|
||||||
package-pkgbuild:
|
|
||||||
needs: [setup-matrix, build-tauri]
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
arch: ${{ fromJson(needs.setup-matrix.outputs.matrix) }}
|
|
||||||
steps:
|
|
||||||
- name: Checkout gpgui repo
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
token: ${{ secrets.GH_PAT }}
|
|
||||||
repository: yuezk/gpgui
|
|
||||||
path: gpgui
|
|
||||||
|
|
||||||
- name: Download artifact-${{ matrix.arch }}
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: artifact-${{ matrix.arch }}-tauri
|
|
||||||
path: gpgui/.tmp/artifact
|
|
||||||
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v3
|
|
||||||
with:
|
|
||||||
platforms: ${{ matrix.arch }}
|
|
||||||
|
|
||||||
- name: Login to Docker Hub
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKER_HUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Generate PKGBUILD
|
|
||||||
run: |
|
|
||||||
./gpgui/scripts/generate-pkgbuild.sh
|
|
||||||
|
|
||||||
- name: Build PKGBUILD package
|
|
||||||
run: |
|
|
||||||
# Generate PKGBUILD to .tmp/pkgbuild
|
|
||||||
./gpgui/scripts/generate-pkgbuild.sh
|
|
||||||
|
|
||||||
# Build package
|
|
||||||
docker run \
|
|
||||||
--rm \
|
|
||||||
-v $(pwd)/gpgui/.tmp/pkgbuild:/pkgbuild \
|
|
||||||
--platform linux/${{ matrix.arch }} \
|
|
||||||
yuezk/gpdev:pkgbuild
|
|
||||||
|
|
||||||
- name: Upload pkgbuild artifacts
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: artifact-${{ matrix.arch }}-pkgbuild
|
|
||||||
path: |
|
|
||||||
gpgui/.tmp/pkgbuild/*.pkg.tar.zst
|
|
||||||
|
|
||||||
gh-release:
|
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs:
|
|
||||||
- package-rpm
|
|
||||||
- package-pkgbuild
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Download artifact
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
path: artifact
|
|
||||||
pattern: artifact-*
|
|
||||||
merge-multiple: true
|
|
||||||
|
|
||||||
- name: Generate checksum
|
|
||||||
uses: jmgilman/actions-generate-checksum@v1
|
|
||||||
with:
|
|
||||||
output: checksums.txt
|
|
||||||
patterns: |
|
|
||||||
artifact/*
|
|
||||||
|
|
||||||
- name: Create GH release
|
|
||||||
uses: softprops/action-gh-release@v1
|
|
||||||
with:
|
|
||||||
token: ${{ secrets.GH_PAT }}
|
|
||||||
prerelease: contains(github.ref, 'latest')
|
|
||||||
fail_on_unmatched_files: true
|
|
||||||
files: |
|
|
||||||
checksums.txt
|
|
||||||
artifact/*
|
|
||||||
|
15
Cargo.lock
generated
15
Cargo.lock
generated
@ -1428,6 +1428,7 @@ dependencies = [
|
|||||||
"anyhow",
|
"anyhow",
|
||||||
"base64 0.21.5",
|
"base64 0.21.5",
|
||||||
"chacha20poly1305",
|
"chacha20poly1305",
|
||||||
|
"clap",
|
||||||
"dotenvy_macro",
|
"dotenvy_macro",
|
||||||
"log",
|
"log",
|
||||||
"redact-engine",
|
"redact-engine",
|
||||||
@ -1564,9 +1565,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "h2"
|
name = "h2"
|
||||||
version = "0.3.22"
|
version = "0.3.24"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4d6250322ef6e60f93f9a2162799302cd6f68f79f6e5d85c8c16f14d1d958178"
|
checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"fnv",
|
"fnv",
|
||||||
@ -1583,9 +1584,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "h2"
|
name = "h2"
|
||||||
version = "0.4.0"
|
version = "0.4.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e1d308f63daf4181410c242d34c11f928dcb3aa105852019e043c9d1f4e4368a"
|
checksum = "31d030e59af851932b72ceebadf4a2b5986dba4c3b99dd2493f8273a0f151943"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"fnv",
|
"fnv",
|
||||||
@ -1743,7 +1744,7 @@ dependencies = [
|
|||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"h2 0.3.22",
|
"h2 0.3.24",
|
||||||
"http 0.2.11",
|
"http 0.2.11",
|
||||||
"http-body 0.4.6",
|
"http-body 0.4.6",
|
||||||
"httparse",
|
"httparse",
|
||||||
@ -1766,7 +1767,7 @@ dependencies = [
|
|||||||
"bytes",
|
"bytes",
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"h2 0.4.0",
|
"h2 0.4.2",
|
||||||
"http 1.0.0",
|
"http 1.0.0",
|
||||||
"http-body 1.0.0",
|
"http-body 1.0.0",
|
||||||
"httparse",
|
"httparse",
|
||||||
@ -3070,7 +3071,7 @@ dependencies = [
|
|||||||
"encoding_rs",
|
"encoding_rs",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"h2 0.3.22",
|
"h2 0.3.24",
|
||||||
"http 0.2.11",
|
"http 0.2.11",
|
||||||
"http-body 0.4.6",
|
"http-body 0.4.6",
|
||||||
"hyper 0.14.28",
|
"hyper 0.14.28",
|
||||||
|
13
README.md
13
README.md
@ -33,11 +33,12 @@ Commands:
|
|||||||
|
|
||||||
Options:
|
Options:
|
||||||
--fix-openssl Get around the OpenSSL `unsafe legacy renegotiation` error
|
--fix-openssl Get around the OpenSSL `unsafe legacy renegotiation` error
|
||||||
|
--ignore-tls-errors Ignore the TLS errors
|
||||||
-h, --help Print help
|
-h, --help Print help
|
||||||
-V, --version Print version
|
-V, --version Print version
|
||||||
```
|
|
||||||
|
|
||||||
See `gpclient -h` for help.
|
See 'gpclient help <command>' for more information on a specific command.
|
||||||
|
```
|
||||||
|
|
||||||
### GUI
|
### GUI
|
||||||
|
|
||||||
@ -49,6 +50,10 @@ The GUI version is also available after you installed it. You can launch it from
|
|||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
> [!Note]
|
||||||
|
>
|
||||||
|
> This instruction is for the 2.x version. The 1.x version is still available on the [1.x](https://github.com/yuezk/GlobalProtect-openconnect/tree/1.x) branch, you can build it from the source code by following the instructions in the `README.md` file.
|
||||||
|
|
||||||
### Debian/Ubuntu based distributions
|
### Debian/Ubuntu based distributions
|
||||||
|
|
||||||
#### Install from PPA
|
#### Install from PPA
|
||||||
@ -104,10 +109,6 @@ Download the latest RPM package from [releases](https://github.com/yuezk/GlobalP
|
|||||||
|
|
||||||
The project depends on `openconnect`, `webkit2gtk`, `libsecret`, `libayatana-appindicator` or `libappindicator-gtk3`. You can install them first and then download the latest binary release (i.e., `*.bin.tar.gz`) from [releases](https://github.com/yuezk/GlobalProtect-openconnect/releases) page.
|
The project depends on `openconnect`, `webkit2gtk`, `libsecret`, `libayatana-appindicator` or `libappindicator-gtk3`. You can install them first and then download the latest binary release (i.e., `*.bin.tar.gz`) from [releases](https://github.com/yuezk/GlobalProtect-openconnect/releases) page.
|
||||||
|
|
||||||
### Install the Old Version (v1.4.9)
|
|
||||||
|
|
||||||
The 1.x version is still available on the [1.x](https://github.com/yuezk/GlobalProtect-openconnect/tree/1.x) branch, you can build it from the source code by following the instructions in the `README.md` file.
|
|
||||||
|
|
||||||
## [License](./LICENSE)
|
## [License](./LICENSE)
|
||||||
|
|
||||||
GPLv3
|
GPLv3
|
||||||
|
@ -8,7 +8,7 @@ license.workspace = true
|
|||||||
tauri-build = { version = "1.5", features = [] }
|
tauri-build = { version = "1.5", features = [] }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
gpapi = { path = "../../crates/gpapi", features = ["tauri"] }
|
gpapi = { path = "../../crates/gpapi", features = ["tauri", "clap"] }
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
clap.workspace = true
|
clap.workspace = true
|
||||||
env_logger.workspace = true
|
env_logger.workspace = true
|
||||||
|
@ -7,6 +7,7 @@ use std::{
|
|||||||
use anyhow::bail;
|
use anyhow::bail;
|
||||||
use gpapi::{
|
use gpapi::{
|
||||||
auth::SamlAuthData,
|
auth::SamlAuthData,
|
||||||
|
gp_params::GpParams,
|
||||||
portal::{prelogin, Prelogin},
|
portal::{prelogin, Prelogin},
|
||||||
utils::{redact::redact_uri, window::WindowExt},
|
utils::{redact::redact_uri, window::WindowExt},
|
||||||
};
|
};
|
||||||
@ -18,11 +19,13 @@ use tokio_util::sync::CancellationToken;
|
|||||||
use webkit2gtk::{
|
use webkit2gtk::{
|
||||||
gio::Cancellable,
|
gio::Cancellable,
|
||||||
glib::{GString, TimeSpan},
|
glib::{GString, TimeSpan},
|
||||||
LoadEvent, SettingsExt, URIResponse, URIResponseExt, WebContextExt, WebResource, WebResourceExt,
|
LoadEvent, SettingsExt, TLSErrorsPolicy, URIResponse, URIResponseExt, WebContextExt, WebResource,
|
||||||
WebView, WebViewExt, WebsiteDataManagerExtManual, WebsiteDataTypes,
|
WebResourceExt, WebView, WebViewExt, WebsiteDataManagerExtManual, WebsiteDataTypes,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum AuthDataError {
|
enum AuthDataError {
|
||||||
|
/// Failed to load page due to TLS error
|
||||||
|
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.
|
||||||
Invalid,
|
Invalid,
|
||||||
@ -37,6 +40,7 @@ pub(crate) struct AuthWindow<'a> {
|
|||||||
server: &'a str,
|
server: &'a str,
|
||||||
saml_request: &'a str,
|
saml_request: &'a str,
|
||||||
user_agent: &'a str,
|
user_agent: &'a str,
|
||||||
|
gp_params: Option<GpParams>,
|
||||||
clean: bool,
|
clean: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,6 +51,7 @@ impl<'a> AuthWindow<'a> {
|
|||||||
server: "",
|
server: "",
|
||||||
saml_request: "",
|
saml_request: "",
|
||||||
user_agent: "",
|
user_agent: "",
|
||||||
|
gp_params: None,
|
||||||
clean: false,
|
clean: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -66,6 +71,11 @@ impl<'a> AuthWindow<'a> {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn gp_params(mut self, gp_params: GpParams) -> Self {
|
||||||
|
self.gp_params.replace(gp_params);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn clean(mut self, clean: bool) -> Self {
|
pub fn clean(mut self, clean: bool) -> Self {
|
||||||
self.clean = clean;
|
self.clean = clean;
|
||||||
self
|
self
|
||||||
@ -119,6 +129,12 @@ impl<'a> AuthWindow<'a> {
|
|||||||
let saml_request = self.saml_request.to_string();
|
let saml_request = self.saml_request.to_string();
|
||||||
let (auth_result_tx, mut auth_result_rx) = mpsc::unbounded_channel::<AuthResult>();
|
let (auth_result_tx, mut auth_result_rx) = mpsc::unbounded_channel::<AuthResult>();
|
||||||
let raise_window_cancel_token: Arc<RwLock<Option<CancellationToken>>> = Default::default();
|
let raise_window_cancel_token: Arc<RwLock<Option<CancellationToken>>> = Default::default();
|
||||||
|
let gp_params = self.gp_params.as_ref().unwrap();
|
||||||
|
let tls_err_policy = if gp_params.ignore_tls_errors() {
|
||||||
|
TLSErrorsPolicy::Ignore
|
||||||
|
} else {
|
||||||
|
TLSErrorsPolicy::Fail
|
||||||
|
};
|
||||||
|
|
||||||
if self.clean {
|
if self.clean {
|
||||||
clear_webview_cookies(window).await?;
|
clear_webview_cookies(window).await?;
|
||||||
@ -128,6 +144,10 @@ impl<'a> AuthWindow<'a> {
|
|||||||
window.with_webview(move |wv| {
|
window.with_webview(move |wv| {
|
||||||
let wv = wv.inner();
|
let wv = wv.inner();
|
||||||
|
|
||||||
|
if let Some(context) = wv.context() {
|
||||||
|
context.set_tls_errors_policy(tls_err_policy);
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(settings) = wv.settings() {
|
if let Some(settings) = wv.settings() {
|
||||||
let ua = settings.user_agent().unwrap_or("".into());
|
let ua = settings.user_agent().unwrap_or("".into());
|
||||||
info!("Auth window user agent: {}", ua);
|
info!("Auth window user agent: {}", ua);
|
||||||
@ -168,12 +188,15 @@ impl<'a> AuthWindow<'a> {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
wv.connect_load_failed_with_tls_errors(|_wv, uri, cert, err| {
|
let auth_result_tx_clone = auth_result_tx.clone();
|
||||||
|
wv.connect_load_failed_with_tls_errors(move |_wv, uri, cert, err| {
|
||||||
let redacted_uri = redact_uri(uri);
|
let redacted_uri = redact_uri(uri);
|
||||||
warn!(
|
warn!(
|
||||||
"Failed to load uri: {} with error: {}, cert: {}",
|
"Failed to load uri: {} with error: {}, cert: {}",
|
||||||
redacted_uri, err, cert
|
redacted_uri, err, cert
|
||||||
);
|
);
|
||||||
|
|
||||||
|
send_auth_result(&auth_result_tx_clone, Err(AuthDataError::TlsError));
|
||||||
true
|
true
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -187,12 +210,14 @@ impl<'a> AuthWindow<'a> {
|
|||||||
})?;
|
})?;
|
||||||
|
|
||||||
let portal = self.server.to_string();
|
let portal = self.server.to_string();
|
||||||
let user_agent = self.user_agent.to_string();
|
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
if let Some(auth_result) = auth_result_rx.recv().await {
|
if let Some(auth_result) = auth_result_rx.recv().await {
|
||||||
match auth_result {
|
match auth_result {
|
||||||
Ok(auth_data) => return Ok(auth_data),
|
Ok(auth_data) => return Ok(auth_data),
|
||||||
|
Err(AuthDataError::TlsError) => {
|
||||||
|
return Err(anyhow::anyhow!("TLS error: certificate verify failed"))
|
||||||
|
}
|
||||||
Err(AuthDataError::NotFound) => {
|
Err(AuthDataError::NotFound) => {
|
||||||
info!("No auth data found, it may not be the /SAML20/SP/ACS endpoint");
|
info!("No auth data found, it may not be the /SAML20/SP/ACS endpoint");
|
||||||
|
|
||||||
@ -237,7 +262,7 @@ impl<'a> AuthWindow<'a> {
|
|||||||
);
|
);
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let saml_request = portal_prelogin(&portal, &user_agent).await?;
|
let saml_request = portal_prelogin(&portal, gp_params).await?;
|
||||||
window.with_webview(move |wv| {
|
window.with_webview(move |wv| {
|
||||||
let wv = wv.inner();
|
let wv = wv.inner();
|
||||||
load_saml_request(&wv, &saml_request);
|
load_saml_request(&wv, &saml_request);
|
||||||
@ -258,9 +283,10 @@ fn raise_window(window: &Arc<Window>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn portal_prelogin(portal: &str, user_agent: &str) -> anyhow::Result<String> {
|
pub(crate) async fn portal_prelogin(portal: &str, gp_params: &GpParams) -> anyhow::Result<String> {
|
||||||
info!("Portal prelogin...");
|
info!("Portal prelogin...");
|
||||||
match prelogin(portal, user_agent).await? {
|
|
||||||
|
match prelogin(portal, gp_params).await? {
|
||||||
Prelogin::Saml(prelogin) => Ok(prelogin.saml_request().to_string()),
|
Prelogin::Saml(prelogin) => Ok(prelogin.saml_request().to_string()),
|
||||||
Prelogin::Standard(_) => Err(anyhow::anyhow!("Received non-SAML prelogin response")),
|
Prelogin::Standard(_) => Err(anyhow::anyhow!("Received non-SAML prelogin response")),
|
||||||
}
|
}
|
||||||
@ -397,6 +423,11 @@ fn read_auth_data(main_resource: &WebResource, auth_result_tx: mpsc::UnboundedSe
|
|||||||
send_auth_result(&auth_result_tx, auth_result)
|
send_auth_result(&auth_result_tx, auth_result)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Err(AuthDataError::TlsError) => {
|
||||||
|
// NOTE: This is unreachable
|
||||||
|
info!("TLS error found in headers, trying to read from body...");
|
||||||
|
send_auth_result(&auth_result_tx, Err(AuthDataError::TlsError));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use gpapi::{
|
use gpapi::{
|
||||||
auth::{SamlAuthData, SamlAuthResult},
|
auth::{SamlAuthData, SamlAuthResult},
|
||||||
|
clap::args::Os,
|
||||||
|
gp_params::{ClientOs, GpParams},
|
||||||
utils::{normalize_server, openssl},
|
utils::{normalize_server, openssl},
|
||||||
GP_USER_AGENT,
|
GP_USER_AGENT,
|
||||||
};
|
};
|
||||||
@ -26,23 +28,35 @@ struct Cli {
|
|||||||
saml_request: Option<String>,
|
saml_request: Option<String>,
|
||||||
#[arg(long, default_value = GP_USER_AGENT)]
|
#[arg(long, default_value = GP_USER_AGENT)]
|
||||||
user_agent: String,
|
user_agent: String,
|
||||||
|
#[arg(long, default_value = "Linux")]
|
||||||
|
os: Os,
|
||||||
|
#[arg(long)]
|
||||||
|
os_version: Option<String>,
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
hidpi: bool,
|
hidpi: bool,
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
fix_openssl: bool,
|
fix_openssl: bool,
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
|
ignore_tls_errors: bool,
|
||||||
|
#[arg(long)]
|
||||||
clean: bool,
|
clean: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Cli {
|
impl Cli {
|
||||||
async fn run(&mut self) -> anyhow::Result<()> {
|
async fn run(&mut self) -> anyhow::Result<()> {
|
||||||
|
if self.ignore_tls_errors {
|
||||||
|
info!("TLS errors will be ignored");
|
||||||
|
}
|
||||||
|
|
||||||
let mut openssl_conf = self.prepare_env()?;
|
let mut openssl_conf = self.prepare_env()?;
|
||||||
|
|
||||||
self.server = normalize_server(&self.server)?;
|
self.server = normalize_server(&self.server)?;
|
||||||
|
let gp_params = self.build_gp_params();
|
||||||
|
|
||||||
// Get the initial SAML request
|
// Get the initial SAML request
|
||||||
let saml_request = match self.saml_request {
|
let saml_request = match self.saml_request {
|
||||||
Some(ref saml_request) => saml_request.clone(),
|
Some(ref saml_request) => saml_request.clone(),
|
||||||
None => portal_prelogin(&self.server, &self.user_agent).await?,
|
None => portal_prelogin(&self.server, &gp_params).await?,
|
||||||
};
|
};
|
||||||
|
|
||||||
self.saml_request.replace(saml_request);
|
self.saml_request.replace(saml_request);
|
||||||
@ -82,10 +96,22 @@ impl Cli {
|
|||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn build_gp_params(&self) -> GpParams {
|
||||||
|
let gp_params = GpParams::builder()
|
||||||
|
.user_agent(&self.user_agent)
|
||||||
|
.client_os(ClientOs::from(&self.os))
|
||||||
|
.os_version(self.os_version.clone())
|
||||||
|
.ignore_tls_errors(self.ignore_tls_errors)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
gp_params
|
||||||
|
}
|
||||||
|
|
||||||
async fn saml_auth(&self, app_handle: AppHandle) -> anyhow::Result<SamlAuthData> {
|
async fn saml_auth(&self, app_handle: AppHandle) -> anyhow::Result<SamlAuthData> {
|
||||||
let auth_window = AuthWindow::new(app_handle)
|
let auth_window = AuthWindow::new(app_handle)
|
||||||
.server(&self.server)
|
.server(&self.server)
|
||||||
.user_agent(&self.user_agent)
|
.user_agent(&self.user_agent)
|
||||||
|
.gp_params(self.build_gp_params())
|
||||||
.saml_request(self.saml_request.as_ref().unwrap())
|
.saml_request(self.saml_request.as_ref().unwrap())
|
||||||
.clean(self.clean);
|
.clean(self.clean);
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ edition.workspace = true
|
|||||||
license.workspace = true
|
license.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
gpapi = { path = "../../crates/gpapi" }
|
gpapi = { path = "../../crates/gpapi", features = ["clap"] }
|
||||||
openconnect = { path = "../../crates/openconnect" }
|
openconnect = { path = "../../crates/openconnect" }
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
clap.workspace = true
|
clap.workspace = true
|
||||||
|
@ -16,6 +16,11 @@ const VERSION: &str = concat!(
|
|||||||
")"
|
")"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
pub(crate) struct SharedArgs {
|
||||||
|
pub(crate) fix_openssl: bool,
|
||||||
|
pub(crate) ignore_tls_errors: bool,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Subcommand)]
|
#[derive(Subcommand)]
|
||||||
enum CliCommand {
|
enum CliCommand {
|
||||||
#[command(about = "Connect to a portal server")]
|
#[command(about = "Connect to a portal server")]
|
||||||
@ -40,6 +45,8 @@ enum CliCommand {
|
|||||||
{usage-heading} {usage}
|
{usage-heading} {usage}
|
||||||
|
|
||||||
{all-args}{after-help}
|
{all-args}{after-help}
|
||||||
|
|
||||||
|
See 'gpclient help <command>' for more information on a specific command.
|
||||||
"
|
"
|
||||||
)]
|
)]
|
||||||
struct Cli {
|
struct Cli {
|
||||||
@ -51,6 +58,8 @@ struct Cli {
|
|||||||
help = "Get around the OpenSSL `unsafe legacy renegotiation` error"
|
help = "Get around the OpenSSL `unsafe legacy renegotiation` error"
|
||||||
)]
|
)]
|
||||||
fix_openssl: bool,
|
fix_openssl: bool,
|
||||||
|
#[arg(long, help = "Ignore the TLS errors")]
|
||||||
|
ignore_tls_errors: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Cli {
|
impl Cli {
|
||||||
@ -67,9 +76,17 @@ impl Cli {
|
|||||||
// 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()?;
|
||||||
|
let shared_args = SharedArgs {
|
||||||
|
fix_openssl: self.fix_openssl,
|
||||||
|
ignore_tls_errors: self.ignore_tls_errors,
|
||||||
|
};
|
||||||
|
|
||||||
|
if self.ignore_tls_errors {
|
||||||
|
info!("TLS errors will be ignored");
|
||||||
|
}
|
||||||
|
|
||||||
match &self.command {
|
match &self.command {
|
||||||
CliCommand::Connect(args) => ConnectHandler::new(args, self.fix_openssl).handle().await,
|
CliCommand::Connect(args) => ConnectHandler::new(args, &shared_args).handle().await,
|
||||||
CliCommand::Disconnect => DisconnectHandler::new().handle(),
|
CliCommand::Disconnect => DisconnectHandler::new().handle(),
|
||||||
CliCommand::LaunchGui(args) => LaunchGuiHandler::new(args).handle().await,
|
CliCommand::LaunchGui(args) => LaunchGuiHandler::new(args).handle().await,
|
||||||
}
|
}
|
||||||
@ -89,13 +106,24 @@ pub(crate) async fn run() {
|
|||||||
if let Err(err) = cli.run().await {
|
if let Err(err) = cli.run().await {
|
||||||
eprintln!("\nError: {}", err);
|
eprintln!("\nError: {}", err);
|
||||||
|
|
||||||
if err.to_string().contains("unsafe legacy renegotiation") && !cli.fix_openssl {
|
let err = err.to_string();
|
||||||
|
|
||||||
|
if err.contains("unsafe legacy renegotiation") && !cli.fix_openssl {
|
||||||
eprintln!("\nRe-run it with the `--fix-openssl` option to work around this issue, e.g.:\n");
|
eprintln!("\nRe-run it with the `--fix-openssl` option to work around this issue, e.g.:\n");
|
||||||
// Print the command
|
// Print the command
|
||||||
let args = std::env::args().collect::<Vec<_>>();
|
let args = std::env::args().collect::<Vec<_>>();
|
||||||
eprintln!("{} --fix-openssl {}\n", args[0], args[1..].join(" "));
|
eprintln!("{} --fix-openssl {}\n", args[0], args[1..].join(" "));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err.contains("certificate verify failed") {
|
||||||
|
eprintln!(
|
||||||
|
"\nRe-run it with the `--ignore-tls-errors` option to ignore the certificate error, e.g.:\n"
|
||||||
|
);
|
||||||
|
// Print the command
|
||||||
|
let args = std::env::args().collect::<Vec<_>>();
|
||||||
|
eprintln!("{} --ignore-tls-errors {}\n", args[0], args[1..].join(" "));
|
||||||
|
}
|
||||||
|
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,10 @@ use std::{fs, sync::Arc};
|
|||||||
|
|
||||||
use clap::Args;
|
use clap::Args;
|
||||||
use gpapi::{
|
use gpapi::{
|
||||||
|
clap::args::Os,
|
||||||
credential::{Credential, PasswordCredential},
|
credential::{Credential, PasswordCredential},
|
||||||
gateway::gateway_login,
|
gateway::gateway_login,
|
||||||
gp_params::GpParams,
|
gp_params::{ClientOs, GpParams},
|
||||||
portal::{prelogin, retrieve_config, Prelogin},
|
portal::{prelogin, retrieve_config, Prelogin},
|
||||||
process::auth_launcher::SamlAuthLauncher,
|
process::auth_launcher::SamlAuthLauncher,
|
||||||
utils::{self, shutdown_signal},
|
utils::{self, shutdown_signal},
|
||||||
@ -14,7 +15,7 @@ use inquire::{Password, PasswordDisplayMode, Select, Text};
|
|||||||
use log::info;
|
use log::info;
|
||||||
use openconnect::Vpn;
|
use openconnect::Vpn;
|
||||||
|
|
||||||
use crate::GP_CLIENT_LOCK_FILE;
|
use crate::{cli::SharedArgs, GP_CLIENT_LOCK_FILE};
|
||||||
|
|
||||||
#[derive(Args)]
|
#[derive(Args)]
|
||||||
pub(crate) struct ConnectArgs {
|
pub(crate) struct ConnectArgs {
|
||||||
@ -36,6 +37,10 @@ pub(crate) struct ConnectArgs {
|
|||||||
script: Option<String>,
|
script: Option<String>,
|
||||||
#[arg(long, default_value = GP_USER_AGENT, help = "The user agent to use")]
|
#[arg(long, default_value = GP_USER_AGENT, help = "The user agent to use")]
|
||||||
user_agent: String,
|
user_agent: String,
|
||||||
|
#[arg(long, default_value = "Linux")]
|
||||||
|
os: Os,
|
||||||
|
#[arg(long)]
|
||||||
|
os_version: Option<String>,
|
||||||
#[arg(long, help = "The HiDPI mode, useful for high resolution screens")]
|
#[arg(long, help = "The HiDPI mode, useful for high resolution screens")]
|
||||||
hidpi: bool,
|
hidpi: bool,
|
||||||
#[arg(long, help = "Do not reuse the remembered authentication cookie")]
|
#[arg(long, help = "Do not reuse the remembered authentication cookie")]
|
||||||
@ -44,12 +49,12 @@ pub(crate) struct ConnectArgs {
|
|||||||
|
|
||||||
pub(crate) struct ConnectHandler<'a> {
|
pub(crate) struct ConnectHandler<'a> {
|
||||||
args: &'a ConnectArgs,
|
args: &'a ConnectArgs,
|
||||||
fix_openssl: bool,
|
shared_args: &'a SharedArgs,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> ConnectHandler<'a> {
|
impl<'a> ConnectHandler<'a> {
|
||||||
pub(crate) fn new(args: &'a ConnectArgs, fix_openssl: bool) -> Self {
|
pub(crate) fn new(args: &'a ConnectArgs, shared_args: &'a SharedArgs) -> Self {
|
||||||
Self { args, fix_openssl }
|
Self { args, shared_args }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn handle(&self) -> anyhow::Result<()> {
|
pub(crate) async fn handle(&self) -> anyhow::Result<()> {
|
||||||
@ -57,9 +62,12 @@ impl<'a> ConnectHandler<'a> {
|
|||||||
|
|
||||||
let gp_params = GpParams::builder()
|
let gp_params = GpParams::builder()
|
||||||
.user_agent(&self.args.user_agent)
|
.user_agent(&self.args.user_agent)
|
||||||
|
.client_os(ClientOs::from(&self.args.os))
|
||||||
|
.os_version(self.args.os_version.clone())
|
||||||
|
.ignore_tls_errors(self.shared_args.ignore_tls_errors)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let prelogin = prelogin(&portal, &self.args.user_agent).await?;
|
let prelogin = prelogin(&portal, &gp_params).await?;
|
||||||
let portal_credential = self.obtain_portal_credential(&prelogin).await?;
|
let portal_credential = self.obtain_portal_credential(&prelogin).await?;
|
||||||
let mut portal_config = retrieve_config(&portal, &portal_credential, &gp_params).await?;
|
let mut portal_config = retrieve_config(&portal, &portal_credential, &gp_params).await?;
|
||||||
|
|
||||||
@ -114,10 +122,13 @@ impl<'a> ConnectHandler<'a> {
|
|||||||
match prelogin {
|
match prelogin {
|
||||||
Prelogin::Saml(prelogin) => {
|
Prelogin::Saml(prelogin) => {
|
||||||
SamlAuthLauncher::new(&self.args.server)
|
SamlAuthLauncher::new(&self.args.server)
|
||||||
.user_agent(&self.args.user_agent)
|
|
||||||
.saml_request(prelogin.saml_request())
|
.saml_request(prelogin.saml_request())
|
||||||
|
.user_agent(&self.args.user_agent)
|
||||||
|
.os(self.args.os.as_str())
|
||||||
|
.os_version(self.args.os_version.as_deref())
|
||||||
.hidpi(self.args.hidpi)
|
.hidpi(self.args.hidpi)
|
||||||
.fix_openssl(self.fix_openssl)
|
.fix_openssl(self.shared_args.fix_openssl)
|
||||||
|
.ignore_tls_errors(self.shared_args.ignore_tls_errors)
|
||||||
.clean(self.args.clean)
|
.clean(self.args.clean)
|
||||||
.launch()
|
.launch()
|
||||||
.await
|
.await
|
||||||
|
@ -27,6 +27,8 @@ dotenvy_macro.workspace = true
|
|||||||
uzers.workspace = true
|
uzers.workspace = true
|
||||||
|
|
||||||
tauri = { workspace = true, optional = true }
|
tauri = { workspace = true, optional = true }
|
||||||
|
clap = { workspace = true, optional = true }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
tauri = ["dep:tauri"]
|
tauri = ["dep:tauri"]
|
||||||
|
clap = ["dep:clap"]
|
||||||
|
64
crates/gpapi/src/clap/args.rs
Normal file
64
crates/gpapi/src/clap/args.rs
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
use clap::{builder::PossibleValue, ValueEnum};
|
||||||
|
|
||||||
|
use crate::gp_params::ClientOs;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Os {
|
||||||
|
Linux,
|
||||||
|
Windows,
|
||||||
|
Mac,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Os {
|
||||||
|
pub fn as_str(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Os::Linux => "Linux",
|
||||||
|
Os::Windows => "Windows",
|
||||||
|
Os::Mac => "Mac",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&str> for Os {
|
||||||
|
fn from(os: &str) -> Self {
|
||||||
|
match os.to_lowercase().as_str() {
|
||||||
|
"linux" => Os::Linux,
|
||||||
|
"windows" => Os::Windows,
|
||||||
|
"mac" => Os::Mac,
|
||||||
|
_ => Os::Linux,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&Os> for ClientOs {
|
||||||
|
fn from(value: &Os) -> Self {
|
||||||
|
match value {
|
||||||
|
Os::Linux => ClientOs::Linux,
|
||||||
|
Os::Windows => ClientOs::Windows,
|
||||||
|
Os::Mac => ClientOs::Mac,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ValueEnum for Os {
|
||||||
|
fn value_variants<'a>() -> &'a [Self] {
|
||||||
|
&[Os::Linux, Os::Windows, Os::Mac]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_possible_value(&self) -> Option<clap::builder::PossibleValue> {
|
||||||
|
match self {
|
||||||
|
Os::Linux => Some(PossibleValue::new("Linux")),
|
||||||
|
Os::Windows => Some(PossibleValue::new("Windows")),
|
||||||
|
Os::Mac => Some(PossibleValue::new("Mac")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_str(input: &str, _: bool) -> Result<Self, String> {
|
||||||
|
match input.to_lowercase().as_str() {
|
||||||
|
"linux" => Ok(Os::Linux),
|
||||||
|
"windows" => Ok(Os::Windows),
|
||||||
|
"mac" => Ok(Os::Mac),
|
||||||
|
_ => Err(format!("Invalid OS: {}", input)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1
crates/gpapi/src/clap/mod.rs
Normal file
1
crates/gpapi/src/clap/mod.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
pub mod args;
|
@ -7,23 +7,32 @@ use crate::GP_USER_AGENT;
|
|||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, Type, Default)]
|
#[derive(Debug, Serialize, Deserialize, Clone, Type, Default)]
|
||||||
pub enum ClientOs {
|
pub enum ClientOs {
|
||||||
Linux,
|
|
||||||
#[default]
|
#[default]
|
||||||
|
Linux,
|
||||||
Windows,
|
Windows,
|
||||||
Mac,
|
Mac,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&ClientOs> for &str {
|
impl From<&str> for ClientOs {
|
||||||
fn from(os: &ClientOs) -> Self {
|
fn from(os: &str) -> Self {
|
||||||
match os {
|
match os {
|
||||||
ClientOs::Linux => "Linux",
|
"Linux" => ClientOs::Linux,
|
||||||
ClientOs::Windows => "Windows",
|
"Windows" => ClientOs::Windows,
|
||||||
ClientOs::Mac => "Mac",
|
"Mac" => ClientOs::Mac,
|
||||||
|
_ => ClientOs::Linux,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ClientOs {
|
impl ClientOs {
|
||||||
|
pub fn as_str(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
ClientOs::Linux => "Linux",
|
||||||
|
ClientOs::Windows => "Windows",
|
||||||
|
ClientOs::Mac => "Mac",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn to_openconnect_os(&self) -> &str {
|
pub fn to_openconnect_os(&self) -> &str {
|
||||||
match self {
|
match self {
|
||||||
ClientOs::Linux => "linux",
|
ClientOs::Linux => "linux",
|
||||||
@ -40,6 +49,7 @@ pub struct GpParams {
|
|||||||
os_version: Option<String>,
|
os_version: Option<String>,
|
||||||
client_version: Option<String>,
|
client_version: Option<String>,
|
||||||
computer: Option<String>,
|
computer: Option<String>,
|
||||||
|
ignore_tls_errors: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GpParams {
|
impl GpParams {
|
||||||
@ -54,13 +64,17 @@ impl GpParams {
|
|||||||
pub(crate) fn computer(&self) -> &str {
|
pub(crate) fn computer(&self) -> &str {
|
||||||
match self.computer {
|
match self.computer {
|
||||||
Some(ref computer) => computer,
|
Some(ref computer) => computer,
|
||||||
None => (&self.client_os).into()
|
None => self.client_os.as_str(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn ignore_tls_errors(&self) -> bool {
|
||||||
|
self.ignore_tls_errors
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn to_params(&self) -> HashMap<&str, &str> {
|
pub(crate) fn to_params(&self) -> HashMap<&str, &str> {
|
||||||
let mut params: HashMap<&str, &str> = HashMap::new();
|
let mut params: HashMap<&str, &str> = HashMap::new();
|
||||||
let client_os: &str = (&self.client_os).into();
|
let client_os = self.client_os.as_str();
|
||||||
|
|
||||||
// Common params
|
// Common params
|
||||||
params.insert("prot", "https:");
|
params.insert("prot", "https:");
|
||||||
@ -97,6 +111,7 @@ pub struct GpParamsBuilder {
|
|||||||
os_version: Option<String>,
|
os_version: Option<String>,
|
||||||
client_version: Option<String>,
|
client_version: Option<String>,
|
||||||
computer: Option<String>,
|
computer: Option<String>,
|
||||||
|
ignore_tls_errors: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GpParamsBuilder {
|
impl GpParamsBuilder {
|
||||||
@ -107,6 +122,7 @@ impl GpParamsBuilder {
|
|||||||
os_version: Default::default(),
|
os_version: Default::default(),
|
||||||
client_version: Default::default(),
|
client_version: Default::default(),
|
||||||
computer: Default::default(),
|
computer: Default::default(),
|
||||||
|
ignore_tls_errors: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,13 +136,13 @@ impl GpParamsBuilder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn os_version(&mut self, os_version: &str) -> &mut Self {
|
pub fn os_version<T: Into<Option<String>>>(&mut self, os_version: T) -> &mut Self {
|
||||||
self.os_version = Some(os_version.to_string());
|
self.os_version = os_version.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn client_version(&mut self, client_version: &str) -> &mut Self {
|
pub fn client_version<T: Into<Option<String>>>(&mut self, client_version: T) -> &mut Self {
|
||||||
self.client_version = Some(client_version.to_string());
|
self.client_version = client_version.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,6 +151,11 @@ impl GpParamsBuilder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn ignore_tls_errors(&mut self, ignore_tls_errors: bool) -> &mut Self {
|
||||||
|
self.ignore_tls_errors = ignore_tls_errors;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn build(&self) -> GpParams {
|
pub fn build(&self) -> GpParams {
|
||||||
GpParams {
|
GpParams {
|
||||||
user_agent: self.user_agent.clone(),
|
user_agent: self.user_agent.clone(),
|
||||||
@ -142,6 +163,7 @@ impl GpParamsBuilder {
|
|||||||
os_version: self.os_version.clone(),
|
os_version: self.os_version.clone(),
|
||||||
client_version: self.client_version.clone(),
|
client_version: self.client_version.clone(),
|
||||||
computer: self.computer.clone(),
|
computer: self.computer.clone(),
|
||||||
|
ignore_tls_errors: self.ignore_tls_errors,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,12 +7,17 @@ pub mod process;
|
|||||||
pub mod service;
|
pub mod service;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
|
|
||||||
|
#[cfg(feature = "clap")]
|
||||||
|
pub mod clap;
|
||||||
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
pub const GP_API_KEY: &[u8; 32] = &[0; 32];
|
pub const GP_API_KEY: &[u8; 32] = &[0; 32];
|
||||||
|
|
||||||
pub const GP_USER_AGENT: &str = "PAN GlobalProtect";
|
pub const GP_USER_AGENT: &str = "PAN GlobalProtect";
|
||||||
pub const GP_SERVICE_LOCK_FILE: &str = "/var/run/gpservice.lock";
|
pub const GP_SERVICE_LOCK_FILE: &str = "/var/run/gpservice.lock";
|
||||||
|
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
pub const GP_CLIENT_BINARY: &str = "/usr/bin/gpclient";
|
||||||
#[cfg(not(debug_assertions))]
|
#[cfg(not(debug_assertions))]
|
||||||
pub const GP_SERVICE_BINARY: &str = "/usr/bin/gpservice";
|
pub const GP_SERVICE_BINARY: &str = "/usr/bin/gpservice";
|
||||||
#[cfg(not(debug_assertions))]
|
#[cfg(not(debug_assertions))]
|
||||||
@ -20,6 +25,8 @@ pub const GP_GUI_BINARY: &str = "/usr/bin/gpgui";
|
|||||||
#[cfg(not(debug_assertions))]
|
#[cfg(not(debug_assertions))]
|
||||||
pub(crate) const GP_AUTH_BINARY: &str = "/usr/bin/gpauth";
|
pub(crate) const GP_AUTH_BINARY: &str = "/usr/bin/gpauth";
|
||||||
|
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
pub const GP_CLIENT_BINARY: &str = dotenvy_macro::dotenv!("GP_CLIENT_BINARY");
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
pub const GP_SERVICE_BINARY: &str = dotenvy_macro::dotenv!("GP_SERVICE_BINARY");
|
pub const GP_SERVICE_BINARY: &str = dotenvy_macro::dotenv!("GP_SERVICE_BINARY");
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
|
@ -5,7 +5,21 @@ use roxmltree::Document;
|
|||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use specta::Type;
|
use specta::Type;
|
||||||
|
|
||||||
use crate::utils::{base64, normalize_server, xml};
|
use crate::{
|
||||||
|
gp_params::GpParams,
|
||||||
|
utils::{base64, normalize_server, xml},
|
||||||
|
};
|
||||||
|
|
||||||
|
const REQUIRED_PARAMS: [&str; 8] = [
|
||||||
|
"tmp",
|
||||||
|
"clientVer",
|
||||||
|
"clientos",
|
||||||
|
"os-version",
|
||||||
|
"host-id",
|
||||||
|
"ipv6-support",
|
||||||
|
"default-browser",
|
||||||
|
"cas-support",
|
||||||
|
];
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Type, Clone)]
|
#[derive(Debug, Serialize, Type, Clone)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
@ -67,20 +81,33 @@ impl Prelogin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn prelogin(portal: &str, user_agent: &str) -> anyhow::Result<Prelogin> {
|
pub async fn prelogin(portal: &str, gp_params: &GpParams) -> anyhow::Result<Prelogin> {
|
||||||
|
let user_agent = gp_params.user_agent();
|
||||||
info!("Portal prelogin, user_agent: {}", user_agent);
|
info!("Portal prelogin, user_agent: {}", user_agent);
|
||||||
|
|
||||||
let portal = normalize_server(portal)?;
|
let portal = normalize_server(portal)?;
|
||||||
let prelogin_url = format!("{}/global-protect/prelogin.esp", portal);
|
let prelogin_url = format!(
|
||||||
let client = Client::builder().user_agent(user_agent).build()?;
|
"{}/global-protect/prelogin.esp?kerberos-support=yes",
|
||||||
|
portal
|
||||||
|
);
|
||||||
|
let mut params = gp_params.to_params();
|
||||||
|
params.insert("tmp", "tmp");
|
||||||
|
params.insert("default-browser", "0");
|
||||||
|
params.insert("cas-support", "yes");
|
||||||
|
|
||||||
let res_xml = client
|
params.retain(|k, _| {
|
||||||
.get(&prelogin_url)
|
REQUIRED_PARAMS
|
||||||
.send()
|
.iter()
|
||||||
.await?
|
.any(|required_param| required_param == k)
|
||||||
.error_for_status()?
|
});
|
||||||
.text()
|
|
||||||
.await?;
|
let client = Client::builder()
|
||||||
|
.danger_accept_invalid_certs(gp_params.ignore_tls_errors())
|
||||||
|
.user_agent(user_agent)
|
||||||
|
.build()?;
|
||||||
|
|
||||||
|
let res = client.post(&prelogin_url).form(¶ms).send().await?;
|
||||||
|
let res_xml = res.error_for_status()?.text().await?;
|
||||||
|
|
||||||
trace!("Prelogin response: {}", res_xml);
|
trace!("Prelogin response: {}", res_xml);
|
||||||
let doc = Document::parse(&res_xml)?;
|
let doc = Document::parse(&res_xml)?;
|
||||||
|
@ -8,10 +8,13 @@ use super::command_traits::CommandExt;
|
|||||||
|
|
||||||
pub struct SamlAuthLauncher<'a> {
|
pub struct SamlAuthLauncher<'a> {
|
||||||
server: &'a str,
|
server: &'a str,
|
||||||
user_agent: Option<&'a str>,
|
|
||||||
saml_request: Option<&'a str>,
|
saml_request: Option<&'a str>,
|
||||||
|
user_agent: Option<&'a str>,
|
||||||
|
os: Option<&'a str>,
|
||||||
|
os_version: Option<&'a str>,
|
||||||
hidpi: bool,
|
hidpi: bool,
|
||||||
fix_openssl: bool,
|
fix_openssl: bool,
|
||||||
|
ignore_tls_errors: bool,
|
||||||
clean: bool,
|
clean: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -19,21 +22,34 @@ impl<'a> SamlAuthLauncher<'a> {
|
|||||||
pub fn new(server: &'a str) -> Self {
|
pub fn new(server: &'a str) -> Self {
|
||||||
Self {
|
Self {
|
||||||
server,
|
server,
|
||||||
user_agent: None,
|
|
||||||
saml_request: None,
|
saml_request: None,
|
||||||
|
user_agent: None,
|
||||||
|
os: None,
|
||||||
|
os_version: None,
|
||||||
hidpi: false,
|
hidpi: false,
|
||||||
fix_openssl: false,
|
fix_openssl: false,
|
||||||
|
ignore_tls_errors: false,
|
||||||
clean: false,
|
clean: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn saml_request(mut self, saml_request: &'a str) -> Self {
|
||||||
|
self.saml_request = Some(saml_request);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn user_agent(mut self, user_agent: &'a str) -> Self {
|
pub fn user_agent(mut self, user_agent: &'a str) -> Self {
|
||||||
self.user_agent = Some(user_agent);
|
self.user_agent = Some(user_agent);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn saml_request(mut self, saml_request: &'a str) -> Self {
|
pub fn os(mut self, os: &'a str) -> Self {
|
||||||
self.saml_request = Some(saml_request);
|
self.os = Some(os);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn os_version(mut self, os_version: Option<&'a str>) -> Self {
|
||||||
|
self.os_version = os_version;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,6 +63,11 @@ impl<'a> SamlAuthLauncher<'a> {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn ignore_tls_errors(mut self, ignore_tls_errors: bool) -> Self {
|
||||||
|
self.ignore_tls_errors = ignore_tls_errors;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn clean(mut self, clean: bool) -> Self {
|
pub fn clean(mut self, clean: bool) -> Self {
|
||||||
self.clean = clean;
|
self.clean = clean;
|
||||||
self
|
self
|
||||||
@ -57,20 +78,32 @@ impl<'a> SamlAuthLauncher<'a> {
|
|||||||
let mut auth_cmd = Command::new(GP_AUTH_BINARY);
|
let mut auth_cmd = Command::new(GP_AUTH_BINARY);
|
||||||
auth_cmd.arg(self.server);
|
auth_cmd.arg(self.server);
|
||||||
|
|
||||||
|
if let Some(saml_request) = self.saml_request {
|
||||||
|
auth_cmd.arg("--saml-request").arg(saml_request);
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(user_agent) = self.user_agent {
|
if let Some(user_agent) = self.user_agent {
|
||||||
auth_cmd.arg("--user-agent").arg(user_agent);
|
auth_cmd.arg("--user-agent").arg(user_agent);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(saml_request) = self.saml_request {
|
if let Some(os) = self.os {
|
||||||
auth_cmd.arg("--saml-request").arg(saml_request);
|
auth_cmd.arg("--os").arg(os);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(os_version) = self.os_version {
|
||||||
|
auth_cmd.arg("--os-version").arg(os_version);
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.hidpi {
|
||||||
|
auth_cmd.arg("--hidpi");
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.fix_openssl {
|
if self.fix_openssl {
|
||||||
auth_cmd.arg("--fix-openssl");
|
auth_cmd.arg("--fix-openssl");
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.hidpi {
|
if self.ignore_tls_errors {
|
||||||
auth_cmd.arg("--hidpi");
|
auth_cmd.arg("--ignore-tls-errors");
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.clean {
|
if self.clean {
|
||||||
|
@ -27,7 +27,6 @@ pub fn raise_window(win: &Window) -> anyhow::Result<()> {
|
|||||||
}
|
}
|
||||||
let title = win.title()?;
|
let title = win.title()?;
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
info!("Raising window: {}", title);
|
|
||||||
if let Err(err) = wmctrl_raise_window(&title).await {
|
if let Err(err) = wmctrl_raise_window(&title).await {
|
||||||
warn!("Failed to raise window: {}", err);
|
warn!("Failed to raise window: {}", err);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user