Compare commits

..

4 Commits

Author SHA1 Message Date
Kevin Yue
187ca778f2 Release 2.1.1 2024-03-25 21:42:16 +08:00
Kevin Yue
2d1aa3ba8c Handle the gateway endpoint error
Related: #338
2024-03-25 21:03:54 +08:00
Kevin Yue
08bd4efefa Improve the error message
Related #327
2024-03-23 20:05:54 +08:00
Kevin Yue
558485f5a9 Add the --hip option 2024-03-17 18:41:42 +08:00
13 changed files with 109 additions and 38 deletions

15
Cargo.lock generated
View File

@@ -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",

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.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"

View File

@@ -22,8 +22,8 @@
"all": true, "all": true,
"request": true, "request": true,
"scope": [ "scope": [
"http://**", "http://*",
"https://**" "https://*"
] ]
} }
}, },

View File

@@ -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

View File

@@ -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()?;

View File

@@ -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
View 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),
}

View File

@@ -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(&params).send().await?; let res = client
.post(&login_url)
.form(&params)
.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?;

View File

@@ -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;

View File

@@ -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()))?;

View File

@@ -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),
}

View File

@@ -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)
} }

View File

@@ -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)
}