Compare commits

..

9 Commits

Author SHA1 Message Date
Kevin Yue
90a8c11acb feat: add disable_ipv6 option (related #364) 2024-05-19 09:04:45 +08:00
Kevin Yue
92b858884c fix: check executable for file 2024-05-10 10:26:45 -04:00
Kevin Yue
159673652c Refactor prelogin.rs to use default labels for username and password 2024-05-09 01:48:02 -04:00
Kevin Yue
200d13ef15 Release 2.2.1 2024-05-07 11:58:15 -04:00
Kevin Yue
ddeef46d2e Restore the browser auth, related #360 2024-05-07 11:40:44 -04:00
Dr. Larry D. Pyeatt
97c3998383 Install instructions for Gentoo (#352)
* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

Add install instructions for Gentoo
2024-05-06 19:00:17 +08:00
Kevin Yue
93aea4ee60 doc: using the default browser for CLI 2024-04-30 18:47:38 +08:00
Kevin Yue
546dbf542e Update README.md 2024-04-30 13:28:20 +08:00
Kevin Yue
005410d40b Update README.md 2024-04-30 13:19:52 +08:00
19 changed files with 143 additions and 62 deletions

16
Cargo.lock generated
View File

@@ -564,7 +564,7 @@ dependencies = [
[[package]] [[package]]
name = "common" name = "common"
version = "2.2.0" version = "2.2.1"
dependencies = [ dependencies = [
"is_executable", "is_executable",
] ]
@@ -1430,7 +1430,7 @@ dependencies = [
[[package]] [[package]]
name = "gpapi" name = "gpapi"
version = "2.2.0" version = "2.2.1"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"base64 0.21.5", "base64 0.21.5",
@@ -1439,6 +1439,7 @@ dependencies = [
"dotenvy_macro", "dotenvy_macro",
"log", "log",
"md5", "md5",
"open",
"redact-engine", "redact-engine",
"regex", "regex",
"reqwest", "reqwest",
@@ -1461,7 +1462,7 @@ dependencies = [
[[package]] [[package]]
name = "gpauth" name = "gpauth"
version = "2.2.0" version = "2.2.1"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"clap", "clap",
@@ -1470,7 +1471,6 @@ dependencies = [
"gpapi", "gpapi",
"html-escape", "html-escape",
"log", "log",
"open",
"regex", "regex",
"serde_json", "serde_json",
"tauri", "tauri",
@@ -1483,7 +1483,7 @@ dependencies = [
[[package]] [[package]]
name = "gpclient" name = "gpclient"
version = "2.2.0" version = "2.2.1"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"clap", "clap",
@@ -1505,7 +1505,7 @@ dependencies = [
[[package]] [[package]]
name = "gpgui-helper" name = "gpgui-helper"
version = "2.2.0" version = "2.2.1"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"clap", "clap",
@@ -1523,7 +1523,7 @@ dependencies = [
[[package]] [[package]]
name = "gpservice" name = "gpservice"
version = "2.2.0" version = "2.2.1"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"axum", "axum",
@@ -2537,7 +2537,7 @@ dependencies = [
[[package]] [[package]]
name = "openconnect" name = "openconnect"
version = "2.2.0" version = "2.2.1"
dependencies = [ dependencies = [
"cc", "cc",
"common", "common",

View File

@@ -5,7 +5,7 @@ members = ["crates/*", "apps/gpclient", "apps/gpservice", "apps/gpauth", "apps/g
[workspace.package] [workspace.package]
rust-version = "1.70" rust-version = "1.70"
version = "2.2.0" version = "2.2.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"
@@ -44,7 +44,6 @@ compile-time = "0.2"
serde_urlencoded = "0.7" serde_urlencoded = "0.7"
md5="0.7" md5="0.7"
sha256="1" sha256="1"
open = "5"
# Tauri dependencies # Tauri dependencies
tauri = { version = "1.5" } tauri = { version = "1.5" }

View File

@@ -43,6 +43,12 @@ Options:
See 'gpclient help <command>' for more information on a specific command. 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:
```bash
sudo -E gpclient connect --default-browser <portal>
```
### GUI ### 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. 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.
@@ -70,7 +76,7 @@ sudo apt-get install globalprotect-openconnect
#### **Ubuntu 24.04** #### **Ubuntu 24.04**
The `libwebkit2gtk-4.0-37` 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, before [the issue](https://github.com/yuezk/GlobalProtect-openconnect/issues/351) gets resolved, you need to install them manually:
```bash ```bash
wget http://launchpadlibrarian.net/704701349/libwebkit2gtk-4.0-37_2.43.3-1_amd64.deb wget http://launchpadlibrarian.net/704701349/libwebkit2gtk-4.0-37_2.43.3-1_amd64.deb
@@ -87,10 +93,10 @@ The latest package is not available in the PPA either, but you still needs to ad
#### 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 `dpkg`: Download the latest deb package from [releases](https://github.com/yuezk/GlobalProtect-openconnect/releases) page. Then install it with `apt`:
```bash ```bash
sudo dpkg -i globalprotect-openconnect_*.deb sudo apt install --fix-broken globalprotect-openconnect_*.deb
``` ```
### Arch Linux / Manjaro ### Arch Linux / Manjaro
@@ -137,6 +143,30 @@ Download the latest RPM package from [releases](https://github.com/yuezk/GlobalP
```bash ```bash
sudo rpm -i globalprotect-openconnect-*.rpm sudo rpm -i globalprotect-openconnect-*.rpm
``` ```
### Gentoo
Install from the ```rios``` or ```slonko``` overlays. Example using rios:
#### 1. Enable the overlay
```
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 ### Other distributions

View File

@@ -8,7 +8,11 @@ license.workspace = true
tauri-build = { version = "1.5", features = [] } tauri-build = { version = "1.5", features = [] }
[dependencies] [dependencies]
gpapi = { path = "../../crates/gpapi", features = ["tauri", "clap"] } gpapi = { path = "../../crates/gpapi", features = [
"tauri",
"clap",
"browser-auth",
] }
anyhow.workspace = true anyhow.workspace = true
clap.workspace = true clap.workspace = true
env_logger.workspace = true env_logger.workspace = true
@@ -22,4 +26,3 @@ html-escape = "0.2.13"
webkit2gtk = "0.18.2" webkit2gtk = "0.18.2"
tauri = { workspace = true, features = ["http-all"] } tauri = { workspace = true, features = ["http-all"] }
compile-time.workspace = true compile-time.workspace = true
open.workspace = true

View File

@@ -3,6 +3,7 @@ use gpapi::{
auth::{SamlAuthData, SamlAuthResult}, auth::{SamlAuthData, SamlAuthResult},
clap::args::Os, clap::args::Os,
gp_params::{ClientOs, GpParams}, gp_params::{ClientOs, GpParams},
process::browser_authenticator::BrowserAuthenticator,
utils::{normalize_server, openssl}, utils::{normalize_server, openssl},
GP_USER_AGENT, GP_USER_AGENT,
}; };
@@ -11,10 +12,7 @@ use serde_json::json;
use tauri::{App, AppHandle, RunEvent}; use tauri::{App, AppHandle, RunEvent};
use tempfile::NamedTempFile; use tempfile::NamedTempFile;
use crate::{ use crate::auth_window::{portal_prelogin, AuthWindow};
auth_window::{portal_prelogin, AuthWindow},
browser_authenticator::BrowserAuthenticator,
};
const VERSION: &str = concat!(env!("CARGO_PKG_VERSION"), " (", compile_time::date_str!(), ")"); const VERSION: &str = concat!(env!("CARGO_PKG_VERSION"), " (", compile_time::date_str!(), ")");

View File

@@ -1,7 +1,6 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
mod auth_window; mod auth_window;
mod browser_authenticator;
mod cli; mod cli;
#[tokio::main] #[tokio::main]

View File

@@ -50,6 +50,8 @@ pub(crate) struct ConnectArgs {
#[arg(short, long, help = "Request MTU from server (legacy servers only)")] #[arg(short, long, help = "Request MTU from server (legacy servers only)")]
mtu: Option<u32>, mtu: Option<u32>,
#[arg(long, help = "Do not ask for IPv6 connectivity")]
disable_ipv6: bool,
#[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,
@@ -216,6 +218,7 @@ impl<'a> ConnectHandler<'a> {
.csd_uid(csd_uid) .csd_uid(csd_uid)
.csd_wrapper(csd_wrapper) .csd_wrapper(csd_wrapper)
.mtu(mtu) .mtu(mtu)
.disable_ipv6(self.args.disable_ipv6)
.build()?; .build()?;
let vpn = Arc::new(vpn); let vpn = Arc::new(vpn);

View File

@@ -42,6 +42,7 @@ impl VpnTaskContext {
.csd_wrapper(args.csd_wrapper()) .csd_wrapper(args.csd_wrapper())
.mtu(args.mtu()) .mtu(args.mtu())
.os(args.openconnect_os()) .os(args.openconnect_os())
.disable_ipv6(args.disable_ipv6())
.build() .build()
{ {
Ok(vpn) => vpn, Ok(vpn) => vpn,

View File

@@ -1,5 +1,9 @@
# Changelog # Changelog
## 2.2.1 - 2024-05-07
- GUI: Restore the default browser auth implementation (fix [#360](https://github.com/yuezk/GlobalProtect-openconnect/issues/360))
## 2.2.0 - 2024-04-30 ## 2.2.0 - 2024-04-30
- CLI: support authentication with external browser (fix [#298](https://github.com/yuezk/GlobalProtect-openconnect/issues/298)) - CLI: support authentication with external browser (fix [#298](https://github.com/yuezk/GlobalProtect-openconnect/issues/298))

View File

@@ -1,7 +1,6 @@
use is_executable::IsExecutable; use std::{io, path::Path};
use std::path::Path;
pub use is_executable::is_executable; use is_executable::IsExecutable;
const VPNC_SCRIPT_LOCATIONS: [&str; 6] = [ const VPNC_SCRIPT_LOCATIONS: [&str; 6] = [
"/usr/local/share/vpnc-scripts/vpnc-script", "/usr/local/share/vpnc-scripts/vpnc-script",
@@ -39,3 +38,17 @@ pub fn find_vpnc_script() -> Option<String> {
pub fn find_csd_wrapper() -> Option<String> { pub fn find_csd_wrapper() -> Option<String> {
find_executable(&CSD_WRAPPER_LOCATIONS) find_executable(&CSD_WRAPPER_LOCATIONS)
} }
/// If file exists, check if it is executable
pub fn check_executable(file: &str) -> Result<(), io::Error> {
let path = Path::new(file);
if path.exists() && !path.is_executable() {
return Err(io::Error::new(
io::ErrorKind::PermissionDenied,
format!("{} is not executable", file),
));
}
Ok(())
}

View File

@@ -31,7 +31,9 @@ sha256.workspace = true
tauri = { workspace = true, optional = true } tauri = { workspace = true, optional = true }
clap = { workspace = true, optional = true } clap = { workspace = true, optional = true }
open = { version = "5", optional = true }
[features] [features]
tauri = ["dep:tauri"] tauri = ["dep:tauri"]
clap = ["dep:clap"] clap = ["dep:clap"]
browser-auth = ["dep:open"]

View File

@@ -181,22 +181,24 @@ fn parse_res_xml(res_xml: &str, is_gateway: bool) -> anyhow::Result<Prelogin> {
return Ok(Prelogin::Saml(saml_prelogin)); return Ok(Prelogin::Saml(saml_prelogin));
} }
let label_username = xml::get_child_text(&doc, "username-label"); let label_username = xml::get_child_text(&doc, "username-label").unwrap_or_else(|| {
let label_password = xml::get_child_text(&doc, "password-label"); info!("Username label has no value, using default");
// Check if the prelogin response is standard login String::from("Username")
if label_username.is_some() && label_password.is_some() { });
let auth_message = let label_password = xml::get_child_text(&doc, "password-label").unwrap_or_else(|| {
xml::get_child_text(&doc, "authentication-message").unwrap_or(String::from("Please enter the login credentials")); info!("Password label has no value, using default");
let standard_prelogin = StandardPrelogin { String::from("Password")
region, });
is_gateway,
auth_message,
label_username: label_username.unwrap(),
label_password: label_password.unwrap(),
};
Ok(Prelogin::Standard(standard_prelogin)) let auth_message =
} else { xml::get_child_text(&doc, "authentication-message").unwrap_or(String::from("Please enter the login credentials"));
Err(anyhow!("Invalid prelogin response")) let standard_prelogin = StandardPrelogin {
} region,
is_gateway,
auth_message,
label_username,
label_password,
};
Ok(Prelogin::Standard(standard_prelogin))
} }

View File

@@ -2,6 +2,8 @@ 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;
#[cfg(feature = "browser-auth")]
pub mod browser_authenticator;
pub mod gui_launcher; pub mod gui_launcher;
pub mod hip_launcher; pub mod hip_launcher;
pub mod service_launcher; pub mod service_launcher;

View File

@@ -36,6 +36,7 @@ pub struct ConnectArgs {
csd_wrapper: Option<String>, csd_wrapper: Option<String>,
mtu: u32, mtu: u32,
os: Option<ClientOs>, os: Option<ClientOs>,
disable_ipv6: bool,
} }
impl ConnectArgs { impl ConnectArgs {
@@ -48,6 +49,7 @@ impl ConnectArgs {
csd_uid: 0, csd_uid: 0,
csd_wrapper: None, csd_wrapper: None,
mtu: 0, mtu: 0,
disable_ipv6: false,
} }
} }
@@ -78,6 +80,10 @@ impl ConnectArgs {
pub fn mtu(&self) -> u32 { pub fn mtu(&self) -> u32 {
self.mtu self.mtu
} }
pub fn disable_ipv6(&self) -> bool {
self.disable_ipv6
}
} }
#[derive(Debug, Deserialize, Serialize, Type)] #[derive(Debug, Deserialize, Serialize, Type)]
@@ -109,11 +115,6 @@ impl ConnectRequest {
self self
} }
pub fn with_mtu(mut self, mtu: u32) -> Self {
self.args.mtu = mtu;
self
}
pub fn with_user_agent<T: Into<Option<String>>>(mut self, user_agent: T) -> Self { pub fn with_user_agent<T: Into<Option<String>>>(mut self, user_agent: T) -> Self {
self.args.user_agent = user_agent.into(); self.args.user_agent = user_agent.into();
self self
@@ -124,6 +125,16 @@ impl ConnectRequest {
self self
} }
pub fn with_mtu(mut self, mtu: u32) -> Self {
self.args.mtu = mtu;
self
}
pub fn with_disable_ipv6(mut self, disable_ipv6: bool) -> Self {
self.args.disable_ipv6 = disable_ipv6;
self
}
pub fn gateway(&self) -> &Gateway { pub fn gateway(&self) -> &Gateway {
self.info.gateway() self.info.gateway()
} }

View File

@@ -20,6 +20,7 @@ pub(crate) struct ConnectOptions {
pub csd_wrapper: *const c_char, pub csd_wrapper: *const c_char,
pub mtu: u32, pub mtu: u32,
pub disable_ipv6: u32,
} }
#[link(name = "vpn")] #[link(name = "vpn")]

View File

@@ -64,6 +64,7 @@ int vpn_connect(const vpn_options *options, vpn_connected_callback callback)
INFO("CSD_USER: %d", options->csd_uid); INFO("CSD_USER: %d", options->csd_uid);
INFO("CSD_WRAPPER: %s", options->csd_wrapper); INFO("CSD_WRAPPER: %s", options->csd_wrapper);
INFO("MTU: %d", options->mtu); INFO("MTU: %d", options->mtu);
INFO("DISABLE_IPV6: %d", options->disable_ipv6);
vpninfo = openconnect_vpninfo_new(options->user_agent, validate_peer_cert, NULL, NULL, print_progress, NULL); vpninfo = openconnect_vpninfo_new(options->user_agent, validate_peer_cert, NULL, NULL, print_progress, NULL);
@@ -103,6 +104,10 @@ int vpn_connect(const vpn_options *options, vpn_connected_callback callback)
openconnect_set_reqmtu(vpninfo, mtu); openconnect_set_reqmtu(vpninfo, mtu);
} }
if (options->disable_ipv6) {
openconnect_disable_ipv6(vpninfo);
}
g_cmd_pipe_fd = openconnect_setup_cmd_pipe(vpninfo); g_cmd_pipe_fd = openconnect_setup_cmd_pipe(vpninfo);
if (g_cmd_pipe_fd < 0) if (g_cmd_pipe_fd < 0)
{ {

View File

@@ -21,6 +21,8 @@ typedef struct vpn_options
const char *csd_wrapper; const char *csd_wrapper;
const int mtu; const int mtu;
const int disable_ipv6;
} vpn_options; } vpn_options;
int vpn_connect(const vpn_options *options, vpn_connected_callback callback); int vpn_connect(const vpn_options *options, vpn_connected_callback callback);
@@ -35,7 +37,7 @@ static char *format_message(const char *format, va_list args)
int len = vsnprintf(NULL, 0, format, args_copy); int len = vsnprintf(NULL, 0, format, args_copy);
va_end(args_copy); va_end(args_copy);
char *buffer = malloc(len + 1); char *buffer = (char*)malloc(len + 1);
if (buffer == NULL) if (buffer == NULL)
{ {
return NULL; return NULL;

View File

@@ -4,7 +4,7 @@ use std::{
sync::{Arc, RwLock}, sync::{Arc, RwLock},
}; };
use common::vpn_utils::{find_vpnc_script, is_executable}; use common::vpn_utils::{check_executable, find_vpnc_script};
use log::info; use log::info;
use crate::ffi; use crate::ffi;
@@ -24,6 +24,7 @@ pub struct Vpn {
csd_wrapper: Option<CString>, csd_wrapper: Option<CString>,
mtu: u32, mtu: u32,
disable_ipv6: bool,
callback: OnConnectedCallback, callback: OnConnectedCallback,
} }
@@ -68,6 +69,7 @@ impl Vpn {
csd_wrapper: Self::option_to_ptr(&self.csd_wrapper), csd_wrapper: Self::option_to_ptr(&self.csd_wrapper),
mtu: self.mtu, mtu: self.mtu,
disable_ipv6: self.disable_ipv6 as u32,
} }
} }
@@ -80,23 +82,23 @@ impl Vpn {
} }
#[derive(Debug)] #[derive(Debug)]
pub struct VpnError<'a> { pub struct VpnError {
message: &'a str, message: String,
} }
impl<'a> VpnError<'a> { impl VpnError {
fn new(message: &'a str) -> Self { fn new(message: String) -> Self {
Self { message } Self { message }
} }
} }
impl fmt::Display for VpnError<'_> { impl fmt::Display for VpnError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.message) write!(f, "{}", self.message)
} }
} }
impl std::error::Error for VpnError<'_> {} impl std::error::Error for VpnError {}
pub struct VpnBuilder { pub struct VpnBuilder {
server: String, server: String,
@@ -110,6 +112,7 @@ pub struct VpnBuilder {
csd_wrapper: Option<String>, csd_wrapper: Option<String>,
mtu: u32, mtu: u32,
disable_ipv6: bool,
} }
impl VpnBuilder { impl VpnBuilder {
@@ -126,6 +129,7 @@ impl VpnBuilder {
csd_wrapper: None, csd_wrapper: None,
mtu: 0, mtu: 0,
disable_ipv6: false,
} }
} }
@@ -159,21 +163,22 @@ impl VpnBuilder {
self self
} }
pub fn build(self) -> Result<Vpn, VpnError<'static>> { pub fn disable_ipv6(mut self, disable_ipv6: bool) -> Self {
self.disable_ipv6 = disable_ipv6;
self
}
pub fn build(self) -> Result<Vpn, VpnError> {
let script = match self.script { let script = match self.script {
Some(script) => { Some(script) => {
if !is_executable(&script) { check_executable(&script).map_err(|e| VpnError::new(e.to_string()))?;
return Err(VpnError::new("vpnc script is not executable"));
}
script script
} }
None => find_vpnc_script().ok_or_else(|| VpnError::new("Failed to find vpnc-script"))?, None => find_vpnc_script().ok_or_else(|| VpnError::new(String::from("Failed to find vpnc-script")))?,
}; };
if let Some(csd_wrapper) = &self.csd_wrapper { if let Some(csd_wrapper) = &self.csd_wrapper {
if !is_executable(csd_wrapper) { check_executable(csd_wrapper).map_err(|e| VpnError::new(e.to_string()))?;
return Err(VpnError::new("CSD wrapper is not executable"));
}
} }
let user_agent = self.user_agent.unwrap_or_default(); let user_agent = self.user_agent.unwrap_or_default();
@@ -192,6 +197,7 @@ impl VpnBuilder {
csd_wrapper: self.csd_wrapper.as_deref().map(Self::to_cstring), csd_wrapper: self.csd_wrapper.as_deref().map(Self::to_cstring),
mtu: self.mtu, mtu: self.mtu,
disable_ipv6: self.disable_ipv6,
callback: Default::default(), callback: Default::default(),
}) })