mirror of
https://github.com/yuezk/GlobalProtect-openconnect.git
synced 2025-05-20 07:26:58 -04:00
refactor: improve the XML parsing
This commit is contained in:
parent
fb8fb21450
commit
5cb9432f21
@ -223,5 +223,3 @@ The CLI version is always free, while the GUI version is paid. There are two tri
|
|||||||
- app [gpclient](./apps/gpclient): [GPL-3.0](./apps/gpclient/LICENSE)
|
- app [gpclient](./apps/gpclient): [GPL-3.0](./apps/gpclient/LICENSE)
|
||||||
- app [gpauth](./apps/gpauth): [GPL-3.0](./apps/gpauth/LICENSE)
|
- app [gpauth](./apps/gpauth): [GPL-3.0](./apps/gpauth/LICENSE)
|
||||||
- app [gpgui-helper](./apps/gpgui-helper): [GPL-3.0](./apps/gpgui-helper/LICENSE)
|
- app [gpgui-helper](./apps/gpgui-helper): [GPL-3.0](./apps/gpgui-helper/LICENSE)
|
||||||
|
|
||||||
GPLv3
|
|
||||||
|
@ -5,18 +5,18 @@ use crate::utils::xml::NodeExt;
|
|||||||
|
|
||||||
use super::{Gateway, PriorityRule};
|
use super::{Gateway, PriorityRule};
|
||||||
|
|
||||||
pub(crate) fn parse_gateways(node: &Node, use_internal: bool) -> Option<Vec<Gateway>> {
|
pub(crate) fn parse_gateways(node: &Node, prefer_internal: bool) -> Option<Vec<Gateway>> {
|
||||||
let node_gateways = node.find_child("gateways")?;
|
let node_gateways = node.find_descendant("gateways")?;
|
||||||
let internal_gateway_list = if use_internal {
|
let internal_gateway_list = if prefer_internal {
|
||||||
info!("Using internal gateways");
|
info!("Try to parse the internal gateways...");
|
||||||
node_gateways.find_child("internal").and_then(|n| n.find_child("list"))
|
node_gateways.find_descendant("internal").and_then(|n| n.find_child("list"))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let gateway_list = internal_gateway_list.or_else(|| {
|
let gateway_list = internal_gateway_list.or_else(|| {
|
||||||
info!("Using external gateways");
|
info!("Try to parse the external gateways...");
|
||||||
node_gateways.find_child("external").and_then(|n| n.find_child("list"))
|
node_gateways.find_descendant("external").and_then(|n| n.find_child("list"))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let gateways = gateway_list
|
let gateways = gateway_list
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use anyhow::bail;
|
use anyhow::bail;
|
||||||
use dns_lookup::lookup_addr;
|
use dns_lookup::lookup_addr;
|
||||||
use log::{info, warn};
|
use log::{debug, info, warn};
|
||||||
use reqwest::{Client, StatusCode};
|
use reqwest::{Client, StatusCode};
|
||||||
use roxmltree::{Document, Node};
|
use roxmltree::{Document, Node};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
@ -22,6 +22,13 @@ pub struct PortalConfig {
|
|||||||
config_cred: Credential,
|
config_cred: Credential,
|
||||||
gateways: Vec<Gateway>,
|
gateways: Vec<Gateway>,
|
||||||
config_digest: Option<String>,
|
config_digest: Option<String>,
|
||||||
|
/**
|
||||||
|
* Variants:
|
||||||
|
* - None: Internal host detection is not supported
|
||||||
|
* - Some(false): Internal host detection is supported but the user is not connected to the internal network
|
||||||
|
* - Some(true): Internal host detection is supported and the user is connected to the internal network
|
||||||
|
*/
|
||||||
|
internal_host_detection: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PortalConfig {
|
impl PortalConfig {
|
||||||
@ -41,6 +48,10 @@ impl PortalConfig {
|
|||||||
&self.config_cred
|
&self.config_cred
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn internal_host_detection(&self) -> Option<bool> {
|
||||||
|
self.internal_host_detection
|
||||||
|
}
|
||||||
|
|
||||||
/// In-place sort the gateways by region
|
/// In-place sort the gateways by region
|
||||||
pub fn sort_gateways(&mut self, region: &str) {
|
pub fn sort_gateways(&mut self, region: &str) {
|
||||||
let preferred_gateway = self.find_preferred_gateway(region);
|
let preferred_gateway = self.find_preferred_gateway(region);
|
||||||
@ -124,23 +135,26 @@ pub async fn retrieve_config(portal: &str, cred: &Credential, gp_params: &GpPara
|
|||||||
bail!(PortalError::ConfigError("Empty portal config response".to_string()))
|
bail!(PortalError::ConfigError("Empty portal config response".to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
debug!("Portal config response: {}", res_xml);
|
||||||
|
|
||||||
let doc = Document::parse(&res_xml).map_err(|e| PortalError::ConfigError(e.to_string()))?;
|
let doc = Document::parse(&res_xml).map_err(|e| PortalError::ConfigError(e.to_string()))?;
|
||||||
let root = doc.root();
|
let root = doc.root();
|
||||||
|
|
||||||
let mut use_internal_gateways = false;
|
let mut ihd_enabled = false;
|
||||||
// Perform DNS lookup, set flag to internal or external, and pass it to parse_gateways
|
let mut prefer_internal = false;
|
||||||
if let Some(ihd_node) = root.find_child("internal-host-detection") {
|
if let Some(ihd_node) = root.find_descendant("internal-host-detection") {
|
||||||
use_internal_gateways = internal_host_detect(&ihd_node)
|
ihd_enabled = true;
|
||||||
|
prefer_internal = internal_host_detect(&ihd_node)
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut gateways = parse_gateways(&root, use_internal_gateways).unwrap_or_else(|| {
|
let mut gateways = parse_gateways(&root, prefer_internal).unwrap_or_else(|| {
|
||||||
info!("No gateways found in portal config");
|
info!("No gateways found in portal config");
|
||||||
vec![]
|
vec![]
|
||||||
});
|
});
|
||||||
|
|
||||||
let user_auth_cookie = root.child_text("portal-userauthcookie").unwrap_or_default();
|
let user_auth_cookie = root.descendant_text("portal-userauthcookie").unwrap_or_default();
|
||||||
let prelogon_user_auth_cookie = root.child_text("portal-prelogonuserauthcookie").unwrap_or_default();
|
let prelogon_user_auth_cookie = root.descendant_text("portal-prelogonuserauthcookie").unwrap_or_default();
|
||||||
let config_digest = root.child_text("config-digest");
|
let config_digest = root.descendant_text("config-digest");
|
||||||
|
|
||||||
if gateways.is_empty() {
|
if gateways.is_empty() {
|
||||||
gateways.push(Gateway::new(server.to_string(), server.to_string()));
|
gateways.push(Gateway::new(server.to_string(), server.to_string()));
|
||||||
@ -152,9 +166,11 @@ pub async fn retrieve_config(portal: &str, cred: &Credential, gp_params: &GpPara
|
|||||||
config_cred: cred.clone(),
|
config_cred: cred.clone(),
|
||||||
gateways,
|
gateways,
|
||||||
config_digest: config_digest.map(|s| s.to_string()),
|
config_digest: config_digest.map(|s| s.to_string()),
|
||||||
|
internal_host_detection: if ihd_enabled { Some(prefer_internal) } else { None },
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Perform DNS lookup and compare the result with the expected hostname
|
||||||
fn internal_host_detect(node: &Node) -> bool {
|
fn internal_host_detect(node: &Node) -> bool {
|
||||||
let ip_info = [
|
let ip_info = [
|
||||||
(node.child_text("ip-address"), node.child_text("host")),
|
(node.child_text("ip-address"), node.child_text("host")),
|
||||||
@ -168,14 +184,16 @@ fn internal_host_detect(node: &Node) -> bool {
|
|||||||
if !ip_address.is_empty() && !host.is_empty() {
|
if !ip_address.is_empty() && !host.is_empty() {
|
||||||
match ip_address.parse::<std::net::IpAddr>() {
|
match ip_address.parse::<std::net::IpAddr>() {
|
||||||
Ok(ip) => match lookup_addr(&ip) {
|
Ok(ip) => match lookup_addr(&ip) {
|
||||||
Ok(host_lookup) if host_lookup == *host => return true,
|
Ok(host_lookup) if host_lookup.to_lowercase() == host.to_lowercase() => {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
Ok(host_lookup) => {
|
Ok(host_lookup) => {
|
||||||
info!(
|
info!(
|
||||||
"rDNS lookup for {} returned {}, expected {}",
|
"rDNS lookup for {} returned {}, expected {}",
|
||||||
ip_address, host_lookup, host
|
ip_address, host_lookup, host
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Err(err) => warn!("DNS lookup failed for {}: {}", ip_address, err),
|
Err(err) => warn!("rDNS lookup failed for {}: {}", ip_address, err),
|
||||||
},
|
},
|
||||||
Err(err) => warn!("Invalid IP address {}: {}", ip_address, err),
|
Err(err) => warn!("Invalid IP address {}: {}", ip_address, err),
|
||||||
}
|
}
|
||||||
|
@ -149,25 +149,25 @@ fn parse_res_xml(res_xml: &str, is_gateway: bool) -> anyhow::Result<Prelogin> {
|
|||||||
let root = doc.root();
|
let root = doc.root();
|
||||||
|
|
||||||
let status = root
|
let status = root
|
||||||
.child_text("status")
|
.descendant_text("status")
|
||||||
.ok_or_else(|| anyhow::anyhow!("Prelogin response does not contain status element"))?;
|
.ok_or_else(|| anyhow::anyhow!("Prelogin response does not contain status element"))?;
|
||||||
// Check the status of the prelogin response
|
// Check the status of the prelogin response
|
||||||
if status.to_uppercase() != "SUCCESS" {
|
if status.to_uppercase() != "SUCCESS" {
|
||||||
let msg = root.child_text("msg").unwrap_or("Unknown error");
|
let msg = root.descendant_text("msg").unwrap_or("Unknown error");
|
||||||
bail!("{}", msg)
|
bail!("{}", msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
let region = root
|
let region = root
|
||||||
.child_text("region")
|
.descendant_text("region")
|
||||||
.unwrap_or_else(|| {
|
.unwrap_or_else(|| {
|
||||||
info!("Prelogin response does not contain region element");
|
info!("Prelogin response does not contain region element");
|
||||||
"Unknown"
|
"Unknown"
|
||||||
})
|
})
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
let saml_method = root.child_text("saml-auth-method");
|
let saml_method = root.descendant_text("saml-auth-method");
|
||||||
let saml_request = root.child_text("saml-request");
|
let saml_request = root.descendant_text("saml-request");
|
||||||
let saml_default_browser = root.child_text("saml-default-browser");
|
let saml_default_browser = root.descendant_text("saml-default-browser");
|
||||||
// Check if the prelogin response is SAML
|
// Check if the prelogin response is SAML
|
||||||
if saml_method.is_some() && saml_request.is_some() {
|
if saml_method.is_some() && saml_request.is_some() {
|
||||||
let saml_request = base64::decode_to_string(saml_request.unwrap())?;
|
let saml_request = base64::decode_to_string(saml_request.unwrap())?;
|
||||||
@ -184,14 +184,14 @@ fn parse_res_xml(res_xml: &str, is_gateway: bool) -> anyhow::Result<Prelogin> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let label_username = root
|
let label_username = root
|
||||||
.child_text("username-label")
|
.descendant_text("username-label")
|
||||||
.unwrap_or_else(|| {
|
.unwrap_or_else(|| {
|
||||||
info!("Username label has no value, using default");
|
info!("Username label has no value, using default");
|
||||||
"Username"
|
"Username"
|
||||||
})
|
})
|
||||||
.to_string();
|
.to_string();
|
||||||
let label_password = root
|
let label_password = root
|
||||||
.child_text("password-label")
|
.descendant_text("password-label")
|
||||||
.unwrap_or_else(|| {
|
.unwrap_or_else(|| {
|
||||||
info!("Password label has no value, using default");
|
info!("Password label has no value, using default");
|
||||||
"Password"
|
"Password"
|
||||||
@ -199,7 +199,7 @@ fn parse_res_xml(res_xml: &str, is_gateway: bool) -> anyhow::Result<Prelogin> {
|
|||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
let auth_message = root
|
let auth_message = root
|
||||||
.child_text("authentication-message")
|
.descendant_text("authentication-message")
|
||||||
.unwrap_or("Please enter the login credentials")
|
.unwrap_or("Please enter the login credentials")
|
||||||
.to_string();
|
.to_string();
|
||||||
let standard_prelogin = StandardPrelogin {
|
let standard_prelogin = StandardPrelogin {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use std::{process::ExitStatus, time::Duration};
|
use std::{process::ExitStatus, time::Duration};
|
||||||
|
|
||||||
use anyhow::bail;
|
use anyhow::bail;
|
||||||
use log::{info, warn};
|
use log::info;
|
||||||
use tauri::Window;
|
use tauri::Window;
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
|
|
||||||
@ -33,7 +33,7 @@ pub fn raise_window(win: &Window) -> anyhow::Result<()> {
|
|||||||
let title = win.title()?;
|
let title = win.title()?;
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
if let Err(err) = wmctrl_raise_window(&title).await {
|
if let Err(err) = wmctrl_raise_window(&title).await {
|
||||||
warn!("Failed to raise window: {}", err);
|
info!("Window not raised: {}", err);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,27 @@
|
|||||||
use roxmltree::Node;
|
use roxmltree::Node;
|
||||||
|
|
||||||
pub(crate) trait NodeExt<'a> {
|
pub(crate) trait NodeExt<'a> {
|
||||||
|
fn find_descendant(&self, name: &str) -> Option<Node<'a, 'a>>;
|
||||||
|
fn descendant_text(&self, name: &str) -> Option<&'a str>;
|
||||||
|
|
||||||
fn find_child(&self, name: &str) -> Option<Node<'a, 'a>>;
|
fn find_child(&self, name: &str) -> Option<Node<'a, 'a>>;
|
||||||
fn child_text(&self, name: &str) -> Option<&'a str>;
|
fn child_text(&self, name: &str) -> Option<&'a str>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> NodeExt<'a> for Node<'a, 'a> {
|
impl<'a> NodeExt<'a> for Node<'a, 'a> {
|
||||||
fn find_child(&self, name: &str) -> Option<Node<'a, 'a>> {
|
fn find_descendant(&self, name: &str) -> Option<Node<'a, 'a>> {
|
||||||
self.descendants().find(|n| n.has_tag_name(name))
|
self.descendants().find(|n| n.has_tag_name(name))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn descendant_text(&self, name: &str) -> Option<&'a str> {
|
||||||
|
self.find_descendant(name).and_then(|node| node.text())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_child(&self, name: &str) -> Option<Node<'a, 'a>> {
|
||||||
|
self.children().find(|n| n.has_tag_name(name))
|
||||||
|
}
|
||||||
|
|
||||||
fn child_text(&self, name: &str) -> Option<&'a str> {
|
fn child_text(&self, name: &str) -> Option<&'a str> {
|
||||||
let node = self.find_child(name)?;
|
self.find_child(name).and_then(|node| node.text())
|
||||||
node.text()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user