mirror of
https://github.com/yuezk/GlobalProtect-openconnect.git
synced 2025-05-20 07:26:58 -04:00
Compare commits
2 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
80134f5a2b | ||
|
57e20fe478 |
14
Cargo.lock
generated
14
Cargo.lock
generated
@@ -570,7 +570,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "common"
|
name = "common"
|
||||||
version = "2.3.5"
|
version = "2.3.6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"is_executable",
|
"is_executable",
|
||||||
]
|
]
|
||||||
@@ -1430,7 +1430,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gpapi"
|
name = "gpapi"
|
||||||
version = "2.3.5"
|
version = "2.3.6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64 0.21.5",
|
"base64 0.21.5",
|
||||||
@@ -1465,7 +1465,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gpauth"
|
name = "gpauth"
|
||||||
version = "2.3.5"
|
version = "2.3.6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"clap",
|
"clap",
|
||||||
@@ -1486,7 +1486,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gpclient"
|
name = "gpclient"
|
||||||
version = "2.3.5"
|
version = "2.3.6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"clap",
|
"clap",
|
||||||
@@ -1508,7 +1508,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gpgui-helper"
|
name = "gpgui-helper"
|
||||||
version = "2.3.5"
|
version = "2.3.6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"clap",
|
"clap",
|
||||||
@@ -1526,7 +1526,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gpservice"
|
name = "gpservice"
|
||||||
version = "2.3.5"
|
version = "2.3.6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"axum",
|
"axum",
|
||||||
@@ -2549,7 +2549,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openconnect"
|
name = "openconnect"
|
||||||
version = "2.3.5"
|
version = "2.3.6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
"common",
|
"common",
|
||||||
|
@@ -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.3.5"
|
version = "2.3.6"
|
||||||
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"
|
||||||
|
12
README.md
12
README.md
@@ -44,12 +44,20 @@ 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:
|
To use the external browser for authentication with the CLI version, you need to use the following command:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo -E gpclient connect --default-browser <portal>
|
sudo -E gpclient connect --browser default <portal>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Or you can try the following command if the above command does not work:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
gpauth <portal> --browser default 2>/dev/null | sudo gpclient connect <portal> --cookie-on-stdin
|
||||||
|
```
|
||||||
|
|
||||||
|
You can specify the browser with the `--browser <browser>` option, e.g., `--browser firefox`, `--browser chrome`, etc.
|
||||||
|
|
||||||
### GUI
|
### 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.
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "gpauth"
|
name = "gpauth"
|
||||||
|
authors.workspace = true
|
||||||
version.workspace = true
|
version.workspace = true
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
|
@@ -1,3 +1,5 @@
|
|||||||
|
use std::{env::temp_dir, fs, os::unix::fs::PermissionsExt};
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use gpapi::{
|
use gpapi::{
|
||||||
auth::{SamlAuthData, SamlAuthResult},
|
auth::{SamlAuthData, SamlAuthResult},
|
||||||
@@ -11,36 +13,68 @@ use log::{info, LevelFilter};
|
|||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use tauri::{App, AppHandle, RunEvent};
|
use tauri::{App, AppHandle, RunEvent};
|
||||||
use tempfile::NamedTempFile;
|
use tempfile::NamedTempFile;
|
||||||
|
use tokio::{io::AsyncReadExt, net::TcpListener};
|
||||||
|
|
||||||
use crate::auth_window::{portal_prelogin, AuthWindow};
|
use crate::auth_window::{portal_prelogin, AuthWindow};
|
||||||
|
|
||||||
const VERSION: &str = concat!(env!("CARGO_PKG_VERSION"), " (", compile_time::date_str!(), ")");
|
const VERSION: &str = concat!(env!("CARGO_PKG_VERSION"), " (", compile_time::date_str!(), ")");
|
||||||
|
|
||||||
#[derive(Parser, Clone)]
|
#[derive(Parser, Clone)]
|
||||||
#[command(version = VERSION)]
|
#[command(
|
||||||
|
version = VERSION,
|
||||||
|
author,
|
||||||
|
about = "The authentication component for the GlobalProtect VPN client, supports the SSO authentication method.",
|
||||||
|
help_template = "\
|
||||||
|
{before-help}{name} {version}
|
||||||
|
{author}
|
||||||
|
|
||||||
|
{about}
|
||||||
|
|
||||||
|
{usage-heading} {usage}
|
||||||
|
|
||||||
|
{all-args}{after-help}
|
||||||
|
|
||||||
|
See 'gpauth -h' for more information.
|
||||||
|
"
|
||||||
|
)]
|
||||||
struct Cli {
|
struct Cli {
|
||||||
|
#[arg(help = "The portal server to authenticate")]
|
||||||
server: String,
|
server: String,
|
||||||
#[arg(long)]
|
|
||||||
|
#[arg(long, help = "Treating the server as a gateway")]
|
||||||
gateway: bool,
|
gateway: bool,
|
||||||
#[arg(long)]
|
|
||||||
|
#[arg(long, help = "The SAML authentication request")]
|
||||||
saml_request: Option<String>,
|
saml_request: Option<String>,
|
||||||
#[arg(long, default_value = GP_USER_AGENT)]
|
|
||||||
|
#[arg(long, default_value = GP_USER_AGENT, help = "The user agent to use")]
|
||||||
user_agent: String,
|
user_agent: String,
|
||||||
|
|
||||||
#[arg(long, default_value = "Linux")]
|
#[arg(long, default_value = "Linux")]
|
||||||
os: Os,
|
os: Os,
|
||||||
|
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
os_version: Option<String>,
|
os_version: Option<String>,
|
||||||
#[arg(long)]
|
|
||||||
|
#[arg(long, help = "The HiDPI mode, useful for high-resolution screens")]
|
||||||
hidpi: bool,
|
hidpi: bool,
|
||||||
#[arg(long)]
|
|
||||||
|
#[arg(long, help = "Get around the OpenSSL `unsafe legacy renegotiation` error")]
|
||||||
fix_openssl: bool,
|
fix_openssl: bool,
|
||||||
#[arg(long)]
|
|
||||||
|
#[arg(long, help = "Ignore TLS errors")]
|
||||||
ignore_tls_errors: bool,
|
ignore_tls_errors: bool,
|
||||||
#[arg(long)]
|
|
||||||
|
#[arg(long, help = "Clean the cache of the embedded browser")]
|
||||||
clean: bool,
|
clean: bool,
|
||||||
#[arg(long)]
|
|
||||||
|
#[arg(long, help = "Use the default browser for authentication")]
|
||||||
default_browser: bool,
|
default_browser: bool,
|
||||||
#[arg(long)]
|
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "The browser to use for authentication, e.g., `default`, `firefox`, `chrome`, `chromium`, or the path to the browser executable"
|
||||||
|
)]
|
||||||
browser: Option<String>,
|
browser: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,6 +108,15 @@ impl Cli {
|
|||||||
|
|
||||||
info!("Please continue the authentication process in the default browser");
|
info!("Please continue the authentication process in the default browser");
|
||||||
|
|
||||||
|
let auth_result = match wait_auth_data().await {
|
||||||
|
Ok(auth_data) => SamlAuthResult::Success(auth_data),
|
||||||
|
Err(err) => SamlAuthResult::Failure(format!("{}", err)),
|
||||||
|
};
|
||||||
|
|
||||||
|
info!("Authentication completed");
|
||||||
|
|
||||||
|
println!("{}", json!(auth_result));
|
||||||
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,3 +224,35 @@ pub async fn run() {
|
|||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn wait_auth_data() -> anyhow::Result<SamlAuthData> {
|
||||||
|
// Start a local server to receive the browser authentication data
|
||||||
|
let listener = TcpListener::bind("127.0.0.1:0").await?;
|
||||||
|
let port = listener.local_addr()?.port();
|
||||||
|
let port_file = temp_dir().join("gpcallback.port");
|
||||||
|
|
||||||
|
// Write the port to a file
|
||||||
|
fs::write(&port_file, port.to_string())?;
|
||||||
|
fs::set_permissions(&port_file, fs::Permissions::from_mode(0o600))?;
|
||||||
|
|
||||||
|
// Remove the previous log file
|
||||||
|
let callback_log = temp_dir().join("gpcallback.log");
|
||||||
|
let _ = fs::remove_file(&callback_log);
|
||||||
|
|
||||||
|
info!("Listening authentication data on port {}", port);
|
||||||
|
info!(
|
||||||
|
"If it hangs, please check the logs at `{}` for more information",
|
||||||
|
callback_log.display()
|
||||||
|
);
|
||||||
|
let (mut socket, _) = listener.accept().await?;
|
||||||
|
|
||||||
|
info!("Received the browser authentication data from the socket");
|
||||||
|
let mut data = String::new();
|
||||||
|
socket.read_to_string(&mut data).await?;
|
||||||
|
|
||||||
|
// Remove the port file
|
||||||
|
fs::remove_file(&port_file)?;
|
||||||
|
|
||||||
|
let auth_data = SamlAuthData::from_gpcallback(&data)?;
|
||||||
|
Ok(auth_data)
|
||||||
|
}
|
||||||
|
@@ -1,3 +1,5 @@
|
|||||||
|
use std::{env::temp_dir, fs::File};
|
||||||
|
|
||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
use gpapi::utils::openssl;
|
use gpapi::utils::openssl;
|
||||||
use log::{info, LevelFilter};
|
use log::{info, LevelFilter};
|
||||||
@@ -85,14 +87,29 @@ impl Cli {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init_logger() {
|
fn init_logger(command: &CliCommand) {
|
||||||
env_logger::builder().filter_level(LevelFilter::Info).init();
|
let mut builder = env_logger::builder();
|
||||||
|
builder.filter_level(LevelFilter::Info);
|
||||||
|
|
||||||
|
// Output the log messages to a file if the command is the auth callback
|
||||||
|
if let CliCommand::LaunchGui(args) = command {
|
||||||
|
let auth_data = args.auth_data.as_deref().unwrap_or_default();
|
||||||
|
if !auth_data.is_empty() {
|
||||||
|
if let Ok(log_file) = File::create(temp_dir().join("gpcallback.log")) {
|
||||||
|
let target = Box::new(log_file);
|
||||||
|
builder.target(env_logger::Target::Pipe(target));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn run() {
|
pub(crate) async fn run() {
|
||||||
let cli = Cli::parse();
|
let cli = Cli::parse();
|
||||||
|
|
||||||
init_logger();
|
init_logger(&cli.command);
|
||||||
|
|
||||||
info!("gpclient started: {}", VERSION);
|
info!("gpclient started: {}", VERSION);
|
||||||
|
|
||||||
if let Err(err) = cli.run().await {
|
if let Err(err) = cli.run().await {
|
||||||
|
@@ -1,8 +1,10 @@
|
|||||||
use std::{cell::RefCell, fs, sync::Arc};
|
use std::{cell::RefCell, fs, sync::Arc};
|
||||||
|
|
||||||
|
use anyhow::bail;
|
||||||
use clap::Args;
|
use clap::Args;
|
||||||
use common::vpn_utils::find_csd_wrapper;
|
use common::vpn_utils::find_csd_wrapper;
|
||||||
use gpapi::{
|
use gpapi::{
|
||||||
|
auth::SamlAuthResult,
|
||||||
clap::args::Os,
|
clap::args::Os,
|
||||||
credential::{Credential, PasswordCredential},
|
credential::{Credential, PasswordCredential},
|
||||||
error::PortalError,
|
error::PortalError,
|
||||||
@@ -19,9 +21,8 @@ use gpapi::{
|
|||||||
use inquire::{Password, PasswordDisplayMode, Select, Text};
|
use inquire::{Password, PasswordDisplayMode, Select, Text};
|
||||||
use log::info;
|
use log::info;
|
||||||
use openconnect::Vpn;
|
use openconnect::Vpn;
|
||||||
use tokio::{io::AsyncReadExt, net::TcpListener};
|
|
||||||
|
|
||||||
use crate::{cli::SharedArgs, GP_CLIENT_LOCK_FILE, GP_CLIENT_PORT_FILE};
|
use crate::{cli::SharedArgs, GP_CLIENT_LOCK_FILE};
|
||||||
|
|
||||||
#[derive(Args)]
|
#[derive(Args)]
|
||||||
pub(crate) struct ConnectArgs {
|
pub(crate) struct ConnectArgs {
|
||||||
@@ -37,6 +38,9 @@ pub(crate) struct ConnectArgs {
|
|||||||
#[arg(long, help = "Read the password from standard input")]
|
#[arg(long, help = "Read the password from standard input")]
|
||||||
passwd_on_stdin: bool,
|
passwd_on_stdin: bool,
|
||||||
|
|
||||||
|
#[arg(long, help = "Read the cookie from standard input")]
|
||||||
|
cookie_on_stdin: bool,
|
||||||
|
|
||||||
#[arg(long, short, help = "The VPNC script to use")]
|
#[arg(long, short, help = "The VPNC script to use")]
|
||||||
script: Option<String>,
|
script: Option<String>,
|
||||||
|
|
||||||
@@ -89,7 +93,7 @@ pub(crate) struct ConnectArgs {
|
|||||||
#[arg(long, help = "Disable DTLS and ESP")]
|
#[arg(long, help = "Disable DTLS and ESP")]
|
||||||
no_dtls: bool,
|
no_dtls: bool,
|
||||||
|
|
||||||
#[arg(long, help = "The HiDPI mode, useful for high resolution screens")]
|
#[arg(long, help = "The HiDPI mode, useful for high-resolution screens")]
|
||||||
hidpi: bool,
|
hidpi: bool,
|
||||||
|
|
||||||
#[arg(long, help = "Do not reuse the remembered authentication cookie")]
|
#[arg(long, help = "Do not reuse the remembered authentication cookie")]
|
||||||
@@ -100,7 +104,7 @@ pub(crate) struct ConnectArgs {
|
|||||||
|
|
||||||
#[arg(
|
#[arg(
|
||||||
long,
|
long,
|
||||||
help = "Use the specified browser to authenticate, e.g., firefox, chromium, chrome, or the path to the browser"
|
help = "Use the specified browser to authenticate, e.g., `default`, `firefox`, `chrome`, `chromium`, or the path to the browser executable"
|
||||||
)]
|
)]
|
||||||
browser: Option<String>,
|
browser: Option<String>,
|
||||||
}
|
}
|
||||||
@@ -147,6 +151,10 @@ impl<'a> ConnectHandler<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn handle(&self) -> anyhow::Result<()> {
|
pub(crate) async fn handle(&self) -> anyhow::Result<()> {
|
||||||
|
if self.args.default_browser && self.args.browser.is_some() {
|
||||||
|
bail!("Cannot use `--default-browser` and `--browser` options at the same time");
|
||||||
|
}
|
||||||
|
|
||||||
self.latest_key_password.replace(self.args.key_password.clone());
|
self.latest_key_password.replace(self.args.key_password.clone());
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
@@ -327,6 +335,10 @@ impl<'a> ConnectHandler<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn obtain_credential(&self, prelogin: &Prelogin, server: &str) -> anyhow::Result<Credential> {
|
async fn obtain_credential(&self, prelogin: &Prelogin, server: &str) -> anyhow::Result<Credential> {
|
||||||
|
if self.args.cookie_on_stdin {
|
||||||
|
return read_cookie_from_stdin();
|
||||||
|
}
|
||||||
|
|
||||||
let is_gateway = prelogin.is_gateway();
|
let is_gateway = prelogin.is_gateway();
|
||||||
|
|
||||||
match prelogin {
|
match prelogin {
|
||||||
@@ -353,18 +365,9 @@ impl<'a> ConnectHandler<'a> {
|
|||||||
.launch()
|
.launch()
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
if let Some(cred) = cred {
|
Ok(cred)
|
||||||
return Ok(cred);
|
|
||||||
}
|
|
||||||
|
|
||||||
if !use_default_browser {
|
|
||||||
// This should never happen
|
|
||||||
unreachable!("SAML authentication failed without using the default browser");
|
|
||||||
}
|
|
||||||
|
|
||||||
info!("Waiting for the browser authentication to complete...");
|
|
||||||
wait_credentials().await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Prelogin::Standard(prelogin) => {
|
Prelogin::Standard(prelogin) => {
|
||||||
let prefix = if is_gateway { "Gateway" } else { "Portal" };
|
let prefix = if is_gateway { "Gateway" } else { "Portal" };
|
||||||
println!("{} ({}: {})", prelogin.auth_message(), prefix, server);
|
println!("{} ({}: {})", prelogin.auth_message(), prefix, server);
|
||||||
@@ -394,25 +397,17 @@ impl<'a> ConnectHandler<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn wait_credentials() -> anyhow::Result<Credential> {
|
fn read_cookie_from_stdin() -> anyhow::Result<Credential> {
|
||||||
// Start a local server to receive the browser authentication data
|
info!("Reading cookie from standard input");
|
||||||
let listener = TcpListener::bind("127.0.0.1:0").await?;
|
|
||||||
let port = listener.local_addr()?.port();
|
|
||||||
|
|
||||||
// Write the port to a file
|
let mut cookie = String::new();
|
||||||
fs::write(GP_CLIENT_PORT_FILE, port.to_string())?;
|
std::io::stdin().read_line(&mut cookie)?;
|
||||||
|
|
||||||
info!("Listening authentication data on port {}", port);
|
let Ok(auth_result) = serde_json::from_str::<SamlAuthResult>(cookie.trim_end()) else {
|
||||||
let (mut socket, _) = listener.accept().await?;
|
bail!("Failed to parse auth data")
|
||||||
|
};
|
||||||
|
|
||||||
info!("Received the browser authentication data from the socket");
|
Credential::try_from(auth_result)
|
||||||
let mut data = String::new();
|
|
||||||
socket.read_to_string(&mut data).await?;
|
|
||||||
|
|
||||||
// Remove the port file
|
|
||||||
fs::remove_file(GP_CLIENT_PORT_FILE)?;
|
|
||||||
|
|
||||||
Credential::from_gpcallback(&data)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_pid_file() {
|
fn write_pid_file() {
|
||||||
|
@@ -9,15 +9,13 @@ use gpapi::{
|
|||||||
use log::info;
|
use log::info;
|
||||||
use tokio::io::AsyncWriteExt;
|
use tokio::io::AsyncWriteExt;
|
||||||
|
|
||||||
use crate::GP_CLIENT_PORT_FILE;
|
|
||||||
|
|
||||||
#[derive(Args)]
|
#[derive(Args)]
|
||||||
pub(crate) struct LaunchGuiArgs {
|
pub(crate) struct LaunchGuiArgs {
|
||||||
#[arg(
|
#[arg(
|
||||||
required = false,
|
required = false,
|
||||||
help = "The authentication data, used for the default browser authentication"
|
help = "The authentication data, used for the default browser authentication"
|
||||||
)]
|
)]
|
||||||
auth_data: Option<String>,
|
pub auth_data: Option<String>,
|
||||||
#[arg(long, help = "Launch the GUI minimized")]
|
#[arg(long, help = "Launch the GUI minimized")]
|
||||||
minimized: bool,
|
minimized: bool,
|
||||||
}
|
}
|
||||||
@@ -40,6 +38,7 @@ impl<'a> LaunchGuiHandler<'a> {
|
|||||||
|
|
||||||
let auth_data = self.args.auth_data.as_deref().unwrap_or_default();
|
let auth_data = self.args.auth_data.as_deref().unwrap_or_default();
|
||||||
if !auth_data.is_empty() {
|
if !auth_data.is_empty() {
|
||||||
|
info!("Received auth callback data");
|
||||||
// Process the authentication data, its format is `globalprotectcallback:<data>`
|
// Process the authentication data, its format is `globalprotectcallback:<data>`
|
||||||
return feed_auth_data(auth_data).await;
|
return feed_auth_data(auth_data).await;
|
||||||
}
|
}
|
||||||
@@ -81,16 +80,26 @@ impl<'a> LaunchGuiHandler<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn feed_auth_data(auth_data: &str) -> anyhow::Result<()> {
|
async fn feed_auth_data(auth_data: &str) -> anyhow::Result<()> {
|
||||||
let _ = tokio::join!(feed_auth_data_gui(auth_data), feed_auth_data_cli(auth_data));
|
let (res_gui, res_cli) = tokio::join!(feed_auth_data_gui(auth_data), feed_auth_data_cli(auth_data));
|
||||||
|
if let Err(err) = res_gui {
|
||||||
|
info!("Failed to feed auth data to the GUI: {}", err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Err(err) = res_cli {
|
||||||
|
info!("Failed to feed auth data to the CLI: {}", err);
|
||||||
|
}
|
||||||
|
|
||||||
// Cleanup the temporary file
|
// Cleanup the temporary file
|
||||||
let html_file = temp_dir().join("gpauth.html");
|
let html_file = temp_dir().join("gpauth.html");
|
||||||
let _ = std::fs::remove_file(html_file);
|
if let Err(err) = std::fs::remove_file(&html_file) {
|
||||||
|
info!("Failed to remove {}: {}", html_file.display(), err);
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn feed_auth_data_gui(auth_data: &str) -> anyhow::Result<()> {
|
async fn feed_auth_data_gui(auth_data: &str) -> anyhow::Result<()> {
|
||||||
|
info!("Feeding auth data to the GUI");
|
||||||
let service_endpoint = http_endpoint().await?;
|
let service_endpoint = http_endpoint().await?;
|
||||||
|
|
||||||
reqwest::Client::default()
|
reqwest::Client::default()
|
||||||
@@ -104,7 +113,10 @@ async fn feed_auth_data_gui(auth_data: &str) -> anyhow::Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn feed_auth_data_cli(auth_data: &str) -> anyhow::Result<()> {
|
async fn feed_auth_data_cli(auth_data: &str) -> anyhow::Result<()> {
|
||||||
let port = tokio::fs::read_to_string(GP_CLIENT_PORT_FILE).await?;
|
info!("Feeding auth data to the CLI");
|
||||||
|
|
||||||
|
let port_file = temp_dir().join("gpcallback.port");
|
||||||
|
let port = tokio::fs::read_to_string(port_file).await?;
|
||||||
let mut stream = tokio::net::TcpStream::connect(format!("127.0.0.1:{}", port.trim())).await?;
|
let mut stream = tokio::net::TcpStream::connect(format!("127.0.0.1:{}", port.trim())).await?;
|
||||||
|
|
||||||
stream.write_all(auth_data.as_bytes()).await?;
|
stream.write_all(auth_data.as_bytes()).await?;
|
||||||
@@ -124,7 +136,7 @@ async fn try_active_gui() -> anyhow::Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_log_file() -> anyhow::Result<PathBuf> {
|
fn get_log_file() -> anyhow::Result<PathBuf> {
|
||||||
let dirs = ProjectDirs::from("com.yuezk", "GlobalProtect-openconnect", "gpclient")
|
let dirs = ProjectDirs::from("com.yuezk", "GlobalProtect-openconnect", "gpclient")
|
||||||
.ok_or_else(|| anyhow::anyhow!("Failed to get project dirs"))?;
|
.ok_or_else(|| anyhow::anyhow!("Failed to get project dirs"))?;
|
||||||
|
|
||||||
|
@@ -4,7 +4,6 @@ mod disconnect;
|
|||||||
mod launch_gui;
|
mod launch_gui;
|
||||||
|
|
||||||
pub(crate) const GP_CLIENT_LOCK_FILE: &str = "/var/run/gpclient.lock";
|
pub(crate) const GP_CLIENT_LOCK_FILE: &str = "/var/run/gpclient.lock";
|
||||||
pub(crate) const GP_CLIENT_PORT_FILE: &str = "/var/run/gpclient.port";
|
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
|
@@ -1,5 +1,12 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2.3.6 - 2024-08-15
|
||||||
|
|
||||||
|
- CLI: enhance the `gpauth` command to support external browser authentication
|
||||||
|
- CLI: add the `--cookie-on-stdin` option to support read the cookie from stdin
|
||||||
|
- CLI: support usage: `gpauth <portal> --browser <browser> 2>/dev/null | sudo gpclient connect <portal> --cookie-on-stdin`
|
||||||
|
- CLI: fix the `--browser <browser>` option not working
|
||||||
|
|
||||||
## 2.3.5 - 2024-08-14
|
## 2.3.5 - 2024-08-14
|
||||||
|
|
||||||
- Support configure `no-dtls` option
|
- Support configure `no-dtls` option
|
||||||
|
@@ -85,7 +85,6 @@ impl SamlAuthData {
|
|||||||
return Ok(auth_data);
|
return Ok(auth_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
info!("Parsing SAML auth data...");
|
|
||||||
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: {}", e);
|
warn!("Failed to decode SAML auth data: {}", e);
|
||||||
AuthDataParseError::Invalid
|
AuthDataParseError::Invalid
|
||||||
|
@@ -1,9 +1,10 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use anyhow::bail;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use specta::Type;
|
use specta::Type;
|
||||||
|
|
||||||
use crate::auth::SamlAuthData;
|
use crate::auth::{SamlAuthData, SamlAuthResult};
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Type, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Type, Clone)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
@@ -230,6 +231,17 @@ impl From<SamlAuthData> for Credential {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TryFrom<SamlAuthResult> for Credential {
|
||||||
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
|
fn try_from(value: SamlAuthResult) -> anyhow::Result<Self> {
|
||||||
|
match value {
|
||||||
|
SamlAuthResult::Success(auth_data) => Ok(Self::from(auth_data)),
|
||||||
|
SamlAuthResult::Failure(err) => bail!(err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<PasswordCredential> for Credential {
|
impl From<PasswordCredential> for Credential {
|
||||||
fn from(value: PasswordCredential) -> Self {
|
fn from(value: PasswordCredential) -> Self {
|
||||||
Self::Password(value)
|
Self::Password(value)
|
||||||
|
@@ -29,7 +29,7 @@ pub async fn gateway_login(gateway: &str, cred: &Credential, gp_params: &GpParam
|
|||||||
params.extend(extra_params);
|
params.extend(extra_params);
|
||||||
params.insert("server", &gateway);
|
params.insert("server", &gateway);
|
||||||
|
|
||||||
info!("Gateway login, user_agent: {}", gp_params.user_agent());
|
info!("Perform gateway login, user_agent: {}", gp_params.user_agent());
|
||||||
|
|
||||||
let res = client
|
let res = client
|
||||||
.post(&login_url)
|
.post(&login_url)
|
||||||
|
@@ -109,7 +109,7 @@ pub async fn retrieve_config(portal: &str, cred: &Credential, gp_params: &GpPara
|
|||||||
params.insert("server", &server);
|
params.insert("server", &server);
|
||||||
params.insert("host", &server);
|
params.insert("host", &server);
|
||||||
|
|
||||||
info!("Portal config, user_agent: {}", gp_params.user_agent());
|
info!("Retrieve the portal config, user_agent: {}", gp_params.user_agent());
|
||||||
|
|
||||||
let res = client
|
let res = client
|
||||||
.post(&url)
|
.post(&url)
|
||||||
|
@@ -116,6 +116,8 @@ pub async fn prelogin(portal: &str, gp_params: &GpParams) -> anyhow::Result<Prel
|
|||||||
|
|
||||||
let client = Client::try_from(gp_params)?;
|
let client = Client::try_from(gp_params)?;
|
||||||
|
|
||||||
|
info!("Perform prelogin, user_agent: {}", gp_params.user_agent());
|
||||||
|
|
||||||
let res = client
|
let res = client
|
||||||
.post(&prelogin_url)
|
.post(&prelogin_url)
|
||||||
.form(¶ms)
|
.form(¶ms)
|
||||||
|
@@ -96,7 +96,7 @@ impl<'a> SamlAuthLauncher<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Launch the authenticator binary as the current user or SUDO_USER if available.
|
/// Launch the authenticator binary as the current user or SUDO_USER if available.
|
||||||
pub async fn launch(self) -> anyhow::Result<Option<Credential>> {
|
pub async fn launch(self) -> anyhow::Result<Credential> {
|
||||||
let mut auth_cmd = Command::new(GP_AUTH_BINARY);
|
let mut auth_cmd = Command::new(GP_AUTH_BINARY);
|
||||||
auth_cmd.arg(self.server);
|
auth_cmd.arg(self.server);
|
||||||
|
|
||||||
@@ -152,17 +152,10 @@ impl<'a> SamlAuthLauncher<'a> {
|
|||||||
.wait_with_output()
|
.wait_with_output()
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
if self.default_browser {
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
|
|
||||||
let Ok(auth_result) = serde_json::from_slice::<SamlAuthResult>(&output.stdout) else {
|
let Ok(auth_result) = serde_json::from_slice::<SamlAuthResult>(&output.stdout) else {
|
||||||
bail!("Failed to parse auth data")
|
bail!("Failed to parse auth data")
|
||||||
};
|
};
|
||||||
|
|
||||||
match auth_result {
|
Credential::try_from(auth_result)
|
||||||
SamlAuthResult::Success(auth_data) => Ok(Some(Credential::from(auth_data))),
|
|
||||||
SamlAuthResult::Failure(msg) => bail!(msg),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -19,7 +19,7 @@ impl BrowserAuthenticator<'_> {
|
|||||||
pub fn new_with_browser<'a>(auth_request: &'a str, browser: &'a str) -> BrowserAuthenticator<'a> {
|
pub fn new_with_browser<'a>(auth_request: &'a str, browser: &'a str) -> BrowserAuthenticator<'a> {
|
||||||
BrowserAuthenticator {
|
BrowserAuthenticator {
|
||||||
auth_request,
|
auth_request,
|
||||||
browser: Some(browser),
|
browser: if browser == "default" { None } else { Some(browser) },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user