mirror of
https://github.com/yuezk/GlobalProtect-openconnect.git
synced 2025-05-20 07:26:58 -04:00
Compare commits
4 Commits
cff2ff9dbe
...
v2.1.1
Author | SHA1 | Date | |
---|---|---|---|
|
187ca778f2 | ||
|
2d1aa3ba8c | ||
|
08bd4efefa | ||
|
558485f5a9 |
15
Cargo.lock
generated
15
Cargo.lock
generated
@@ -564,7 +564,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "common"
|
name = "common"
|
||||||
version = "2.1.0"
|
version = "2.1.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"is_executable",
|
"is_executable",
|
||||||
]
|
]
|
||||||
@@ -1430,7 +1430,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gpapi"
|
name = "gpapi"
|
||||||
version = "2.1.0"
|
version = "2.1.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64 0.21.5",
|
"base64 0.21.5",
|
||||||
@@ -1462,7 +1462,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gpauth"
|
name = "gpauth"
|
||||||
version = "2.1.0"
|
version = "2.1.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"clap",
|
"clap",
|
||||||
@@ -1482,10 +1482,11 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gpclient"
|
name = "gpclient"
|
||||||
version = "2.1.0"
|
version = "2.1.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"clap",
|
"clap",
|
||||||
|
"common",
|
||||||
"compile-time",
|
"compile-time",
|
||||||
"directories",
|
"directories",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
@@ -1503,7 +1504,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gpgui-helper"
|
name = "gpgui-helper"
|
||||||
version = "2.1.0"
|
version = "2.1.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"clap",
|
"clap",
|
||||||
@@ -1521,7 +1522,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gpservice"
|
name = "gpservice"
|
||||||
version = "2.1.0"
|
version = "2.1.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"axum",
|
"axum",
|
||||||
@@ -2526,7 +2527,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openconnect"
|
name = "openconnect"
|
||||||
version = "2.1.0"
|
version = "2.1.1"
|
||||||
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.1.0"
|
version = "2.1.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"
|
||||||
|
@@ -22,8 +22,8 @@
|
|||||||
"all": true,
|
"all": true,
|
||||||
"request": true,
|
"request": true,
|
||||||
"scope": [
|
"scope": [
|
||||||
"http://**",
|
"http://*",
|
||||||
"https://**"
|
"https://*"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@@ -6,6 +6,7 @@ edition.workspace = true
|
|||||||
license.workspace = true
|
license.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
common = { path = "../../crates/common" }
|
||||||
gpapi = { path = "../../crates/gpapi", features = ["clap"] }
|
gpapi = { path = "../../crates/gpapi", features = ["clap"] }
|
||||||
openconnect = { path = "../../crates/openconnect" }
|
openconnect = { path = "../../crates/openconnect" }
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
|
@@ -1,12 +1,14 @@
|
|||||||
use std::{fs, sync::Arc};
|
use std::{fs, sync::Arc};
|
||||||
|
|
||||||
use clap::Args;
|
use clap::Args;
|
||||||
|
use common::vpn_utils::find_csd_wrapper;
|
||||||
use gpapi::{
|
use gpapi::{
|
||||||
clap::args::Os,
|
clap::args::Os,
|
||||||
credential::{Credential, PasswordCredential},
|
credential::{Credential, PasswordCredential},
|
||||||
|
error::PortalError,
|
||||||
gateway::gateway_login,
|
gateway::gateway_login,
|
||||||
gp_params::{ClientOs, GpParams},
|
gp_params::{ClientOs, GpParams},
|
||||||
portal::{prelogin, retrieve_config, PortalError, Prelogin},
|
portal::{prelogin, retrieve_config, Prelogin},
|
||||||
process::{
|
process::{
|
||||||
auth_launcher::SamlAuthLauncher,
|
auth_launcher::SamlAuthLauncher,
|
||||||
users::{get_non_root_user, get_user_by_name},
|
users::{get_non_root_user, get_user_by_name},
|
||||||
@@ -31,6 +33,12 @@ pub(crate) struct ConnectArgs {
|
|||||||
#[arg(long, short, help = "The VPNC script to use")]
|
#[arg(long, short, help = "The VPNC script to use")]
|
||||||
script: Option<String>,
|
script: Option<String>,
|
||||||
|
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "Use the default CSD wrapper to generate the HIP report and send it to the server"
|
||||||
|
)]
|
||||||
|
hip: bool,
|
||||||
|
|
||||||
#[arg(long, help = "Same as the '--csd-user' option in the openconnect command")]
|
#[arg(long, help = "Same as the '--csd-user' option in the openconnect command")]
|
||||||
csd_user: Option<String>,
|
csd_user: Option<String>,
|
||||||
|
|
||||||
@@ -112,16 +120,19 @@ impl<'a> ConnectHandler<'a> {
|
|||||||
let selected_gateway = match &self.args.gateway {
|
let selected_gateway = match &self.args.gateway {
|
||||||
Some(gateway) => portal_config
|
Some(gateway) => portal_config
|
||||||
.find_gateway(gateway)
|
.find_gateway(gateway)
|
||||||
.ok_or_else(|| anyhow::anyhow!("Cannot find gateway {}", gateway))?,
|
.ok_or_else(|| anyhow::anyhow!("Cannot find gateway specified: {}", gateway))?,
|
||||||
None => {
|
None => {
|
||||||
portal_config.sort_gateways(prelogin.region());
|
portal_config.sort_gateways(prelogin.region());
|
||||||
let gateways = portal_config.gateways();
|
let gateways = portal_config.gateways();
|
||||||
|
|
||||||
if gateways.len() > 1 {
|
if gateways.len() > 1 {
|
||||||
Select::new("Which gateway do you want to connect to?", gateways)
|
let gateway = Select::new("Which gateway do you want to connect to?", gateways)
|
||||||
.with_vim_mode(true)
|
.with_vim_mode(true)
|
||||||
.prompt()?
|
.prompt()?;
|
||||||
|
info!("Connecting to the selected gateway: {}", gateway);
|
||||||
|
gateway
|
||||||
} else {
|
} else {
|
||||||
|
info!("Connecting to the only available gateway: {}", gateways[0]);
|
||||||
gateways[0]
|
gateways[0]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -142,6 +153,8 @@ impl<'a> ConnectHandler<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn connect_gateway_with_prelogin(&self, gateway: &str) -> anyhow::Result<()> {
|
async fn connect_gateway_with_prelogin(&self, gateway: &str) -> anyhow::Result<()> {
|
||||||
|
info!("Treat the portal as the gateway, connecting...");
|
||||||
|
|
||||||
let mut gp_params = self.build_gp_params();
|
let mut gp_params = self.build_gp_params();
|
||||||
gp_params.set_is_gateway(true);
|
gp_params.set_is_gateway(true);
|
||||||
|
|
||||||
@@ -154,14 +167,21 @@ impl<'a> ConnectHandler<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn connect_gateway(&self, gateway: &str, cookie: &str) -> anyhow::Result<()> {
|
async fn connect_gateway(&self, gateway: &str, cookie: &str) -> anyhow::Result<()> {
|
||||||
let csd_uid = get_csd_uid(&self.args.csd_user)?;
|
|
||||||
let mtu = self.args.mtu.unwrap_or(0);
|
let mtu = self.args.mtu.unwrap_or(0);
|
||||||
|
let csd_uid = get_csd_uid(&self.args.csd_user)?;
|
||||||
|
let csd_wrapper = if self.args.csd_wrapper.is_some() {
|
||||||
|
self.args.csd_wrapper.clone()
|
||||||
|
} else if self.args.hip {
|
||||||
|
find_csd_wrapper()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
let vpn = Vpn::builder(gateway, cookie)
|
let vpn = Vpn::builder(gateway, cookie)
|
||||||
.script(self.args.script.clone())
|
.script(self.args.script.clone())
|
||||||
.user_agent(self.args.user_agent.clone())
|
.user_agent(self.args.user_agent.clone())
|
||||||
.csd_uid(csd_uid)
|
.csd_uid(csd_uid)
|
||||||
.csd_wrapper(self.args.csd_wrapper.clone())
|
.csd_wrapper(csd_wrapper)
|
||||||
.mtu(mtu)
|
.mtu(mtu)
|
||||||
.build()?;
|
.build()?;
|
||||||
|
|
||||||
|
@@ -1,5 +1,12 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2.1.1 - 2024-03-25
|
||||||
|
|
||||||
|
- Add the `--hip` option to enable HIP report
|
||||||
|
- Fix not working in OpenSuse 15.5 (fix #336, #322)
|
||||||
|
- Treat portal as gateway when the gateway login is failed (fix #338)
|
||||||
|
- Improve the error message (fix #327)
|
||||||
|
|
||||||
## 2.1.0 - 2024-02-27
|
## 2.1.0 - 2024-02-27
|
||||||
|
|
||||||
- Update distribution channel for `gpgui` to complaint with the GPL-3 license.
|
- Update distribution channel for `gpgui` to complaint with the GPL-3 license.
|
||||||
|
11
crates/gpapi/src/error.rs
Normal file
11
crates/gpapi/src/error.rs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum PortalError {
|
||||||
|
#[error("Portal prelogin error: {0}")]
|
||||||
|
PreloginError(String),
|
||||||
|
#[error("Portal config error: {0}")]
|
||||||
|
ConfigError(String),
|
||||||
|
#[error("Gateway error: {0}")]
|
||||||
|
GatewayError(String),
|
||||||
|
}
|
@@ -1,13 +1,14 @@
|
|||||||
use anyhow::bail;
|
use anyhow::bail;
|
||||||
use log::info;
|
use log::{info, warn};
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
use roxmltree::Document;
|
use roxmltree::Document;
|
||||||
use urlencoding::encode;
|
use urlencoding::encode;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
credential::Credential,
|
credential::Credential,
|
||||||
|
error::PortalError,
|
||||||
gp_params::GpParams,
|
gp_params::GpParams,
|
||||||
utils::{normalize_server, remove_url_scheme},
|
utils::{normalize_server, parse_gp_error, remove_url_scheme},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub async fn gateway_login(gateway: &str, cred: &Credential, gp_params: &GpParams) -> anyhow::Result<String> {
|
pub async fn gateway_login(gateway: &str, cred: &Credential, gp_params: &GpParams) -> anyhow::Result<String> {
|
||||||
@@ -28,11 +29,24 @@ pub async fn gateway_login(gateway: &str, cred: &Credential, gp_params: &GpParam
|
|||||||
|
|
||||||
info!("Gateway login, user_agent: {}", gp_params.user_agent());
|
info!("Gateway login, user_agent: {}", gp_params.user_agent());
|
||||||
|
|
||||||
let res = client.post(&login_url).form(¶ms).send().await?;
|
let res = client
|
||||||
|
.post(&login_url)
|
||||||
|
.form(¶ms)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.map_err(|e| anyhow::anyhow!(PortalError::GatewayError(e.to_string())))?;
|
||||||
|
|
||||||
let status = res.status();
|
let status = res.status();
|
||||||
|
|
||||||
if status.is_client_error() || status.is_server_error() {
|
if status.is_client_error() || status.is_server_error() {
|
||||||
bail!("Gateway login error: {}", status)
|
let (reason, res) = parse_gp_error(res).await;
|
||||||
|
|
||||||
|
warn!(
|
||||||
|
"Gateway login error: reason={}, status={}, response={}",
|
||||||
|
reason, status, res
|
||||||
|
);
|
||||||
|
|
||||||
|
bail!("Gateway login error, reason: {}", reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
let res_xml = res.text().await?;
|
let res_xml = res.text().await?;
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
pub mod auth;
|
pub mod auth;
|
||||||
pub mod credential;
|
pub mod credential;
|
||||||
|
pub mod error;
|
||||||
pub mod gateway;
|
pub mod gateway;
|
||||||
pub mod gp_params;
|
pub mod gp_params;
|
||||||
pub mod portal;
|
pub mod portal;
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
use anyhow::bail;
|
use anyhow::bail;
|
||||||
use log::info;
|
use log::{info, warn};
|
||||||
use reqwest::{Client, StatusCode};
|
use reqwest::{Client, StatusCode};
|
||||||
use roxmltree::Document;
|
use roxmltree::Document;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
@@ -7,10 +7,10 @@ use specta::Type;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
credential::{AuthCookieCredential, Credential},
|
credential::{AuthCookieCredential, Credential},
|
||||||
|
error::PortalError,
|
||||||
gateway::{parse_gateways, Gateway},
|
gateway::{parse_gateways, Gateway},
|
||||||
gp_params::GpParams,
|
gp_params::GpParams,
|
||||||
portal::PortalError,
|
utils::{normalize_server, parse_gp_error, remove_url_scheme, xml},
|
||||||
utils::{normalize_server, remove_url_scheme, xml},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Type)]
|
#[derive(Debug, Serialize, Type)]
|
||||||
@@ -110,7 +110,14 @@ pub async fn retrieve_config(portal: &str, cred: &Credential, gp_params: &GpPara
|
|||||||
}
|
}
|
||||||
|
|
||||||
if status.is_client_error() || status.is_server_error() {
|
if status.is_client_error() || status.is_server_error() {
|
||||||
bail!("Portal config error: {}", status)
|
let (reason, res) = parse_gp_error(res).await;
|
||||||
|
|
||||||
|
warn!(
|
||||||
|
"Portal config error: reason={}, status={}, response={}",
|
||||||
|
reason, status, res
|
||||||
|
);
|
||||||
|
|
||||||
|
bail!("Portal config error, reason: {}", reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
let res_xml = res.text().await.map_err(|e| PortalError::ConfigError(e.to_string()))?;
|
let res_xml = res.text().await.map_err(|e| PortalError::ConfigError(e.to_string()))?;
|
||||||
|
@@ -3,13 +3,3 @@ mod prelogin;
|
|||||||
|
|
||||||
pub use config::*;
|
pub use config::*;
|
||||||
pub use prelogin::*;
|
pub use prelogin::*;
|
||||||
|
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
|
||||||
pub enum PortalError {
|
|
||||||
#[error("Portal prelogin error: {0}")]
|
|
||||||
PreloginError(String),
|
|
||||||
#[error("Portal config error: {0}")]
|
|
||||||
ConfigError(String),
|
|
||||||
}
|
|
||||||
|
@@ -1,14 +1,14 @@
|
|||||||
use anyhow::bail;
|
use anyhow::bail;
|
||||||
use log::info;
|
use log::{info, warn};
|
||||||
use reqwest::{Client, StatusCode};
|
use reqwest::{Client, StatusCode};
|
||||||
use roxmltree::Document;
|
use roxmltree::Document;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use specta::Type;
|
use specta::Type;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
error::PortalError,
|
||||||
gp_params::GpParams,
|
gp_params::GpParams,
|
||||||
portal::PortalError,
|
utils::{base64, normalize_server, parse_gp_error, xml},
|
||||||
utils::{base64, normalize_server, xml},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const REQUIRED_PARAMS: [&str; 8] = [
|
const REQUIRED_PARAMS: [&str; 8] = [
|
||||||
@@ -126,6 +126,10 @@ pub async fn prelogin(portal: &str, gp_params: &GpParams) -> anyhow::Result<Prel
|
|||||||
}
|
}
|
||||||
|
|
||||||
if status.is_client_error() || status.is_server_error() {
|
if status.is_client_error() || status.is_server_error() {
|
||||||
|
let (reason, res) = parse_gp_error(res).await;
|
||||||
|
|
||||||
|
warn!("Prelogin error: reason={}, status={}, response={}", reason, status, res);
|
||||||
|
|
||||||
bail!("Prelogin error: {}", status)
|
bail!("Prelogin error: {}", status)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
use reqwest::Url;
|
use reqwest::{Response, Url};
|
||||||
|
|
||||||
pub(crate) mod xml;
|
pub(crate) mod xml;
|
||||||
|
|
||||||
@@ -41,3 +41,18 @@ pub fn normalize_server(server: &str) -> anyhow::Result<String> {
|
|||||||
pub fn remove_url_scheme(s: &str) -> String {
|
pub fn remove_url_scheme(s: &str) -> String {
|
||||||
s.replace("http://", "").replace("https://", "")
|
s.replace("http://", "").replace("https://", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn parse_gp_error(res: Response) -> (String, String) {
|
||||||
|
let reason = res
|
||||||
|
.headers()
|
||||||
|
.get("x-private-pan-globalprotect")
|
||||||
|
.map_or_else(|| "<none>", |v| v.to_str().unwrap_or("<invalid header>"))
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
let res = res.text().await.map_or_else(
|
||||||
|
|_| "<failed to read response>".to_string(),
|
||||||
|
|v| if v.is_empty() { "<empty>".to_string() } else { v },
|
||||||
|
);
|
||||||
|
|
||||||
|
(reason, res)
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user