diff --git a/README.md b/README.md index 67ac99a..c703086 100644 --- a/README.md +++ b/README.md @@ -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 [gpauth](./apps/gpauth): [GPL-3.0](./apps/gpauth/LICENSE) - app [gpgui-helper](./apps/gpgui-helper): [GPL-3.0](./apps/gpgui-helper/LICENSE) - -GPLv3 diff --git a/crates/gpapi/src/gateway/parse_gateways.rs b/crates/gpapi/src/gateway/parse_gateways.rs index 324cc95..9ed8d41 100644 --- a/crates/gpapi/src/gateway/parse_gateways.rs +++ b/crates/gpapi/src/gateway/parse_gateways.rs @@ -5,18 +5,18 @@ use crate::utils::xml::NodeExt; use super::{Gateway, PriorityRule}; -pub(crate) fn parse_gateways(node: &Node, use_internal: bool) -> Option> { - let node_gateways = node.find_child("gateways")?; - let internal_gateway_list = if use_internal { - info!("Using internal gateways"); - node_gateways.find_child("internal").and_then(|n| n.find_child("list")) +pub(crate) fn parse_gateways(node: &Node, prefer_internal: bool) -> Option> { + let node_gateways = node.find_descendant("gateways")?; + let internal_gateway_list = if prefer_internal { + info!("Try to parse the internal gateways..."); + node_gateways.find_descendant("internal").and_then(|n| n.find_child("list")) } else { None }; let gateway_list = internal_gateway_list.or_else(|| { - info!("Using external gateways"); - node_gateways.find_child("external").and_then(|n| n.find_child("list")) + info!("Try to parse the external gateways..."); + node_gateways.find_descendant("external").and_then(|n| n.find_child("list")) })?; let gateways = gateway_list diff --git a/crates/gpapi/src/portal/config.rs b/crates/gpapi/src/portal/config.rs index 4855f51..709fd56 100644 --- a/crates/gpapi/src/portal/config.rs +++ b/crates/gpapi/src/portal/config.rs @@ -1,6 +1,6 @@ use anyhow::bail; use dns_lookup::lookup_addr; -use log::{info, warn}; +use log::{debug, info, warn}; use reqwest::{Client, StatusCode}; use roxmltree::{Document, Node}; use serde::Serialize; @@ -22,6 +22,13 @@ pub struct PortalConfig { config_cred: Credential, gateways: Vec, config_digest: Option, + /** + * 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, } impl PortalConfig { @@ -41,6 +48,10 @@ impl PortalConfig { &self.config_cred } + pub fn internal_host_detection(&self) -> Option { + self.internal_host_detection + } + /// In-place sort the gateways by region pub fn sort_gateways(&mut self, region: &str) { 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())) } + debug!("Portal config response: {}", res_xml); + let doc = Document::parse(&res_xml).map_err(|e| PortalError::ConfigError(e.to_string()))?; let root = doc.root(); - let mut use_internal_gateways = false; - // Perform DNS lookup, set flag to internal or external, and pass it to parse_gateways - if let Some(ihd_node) = root.find_child("internal-host-detection") { - use_internal_gateways = internal_host_detect(&ihd_node) + let mut ihd_enabled = false; + let mut prefer_internal = false; + if let Some(ihd_node) = root.find_descendant("internal-host-detection") { + 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"); vec![] }); - let user_auth_cookie = root.child_text("portal-userauthcookie").unwrap_or_default(); - let prelogon_user_auth_cookie = root.child_text("portal-prelogonuserauthcookie").unwrap_or_default(); - let config_digest = root.child_text("config-digest"); + let user_auth_cookie = root.descendant_text("portal-userauthcookie").unwrap_or_default(); + let prelogon_user_auth_cookie = root.descendant_text("portal-prelogonuserauthcookie").unwrap_or_default(); + let config_digest = root.descendant_text("config-digest"); if gateways.is_empty() { 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(), gateways, 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 { let ip_info = [ (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() { match ip_address.parse::() { 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) => { info!( "rDNS lookup for {} returned {}, expected {}", 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), } diff --git a/crates/gpapi/src/portal/prelogin.rs b/crates/gpapi/src/portal/prelogin.rs index 9748e98..3952a73 100644 --- a/crates/gpapi/src/portal/prelogin.rs +++ b/crates/gpapi/src/portal/prelogin.rs @@ -149,25 +149,25 @@ fn parse_res_xml(res_xml: &str, is_gateway: bool) -> anyhow::Result { let root = doc.root(); let status = root - .child_text("status") + .descendant_text("status") .ok_or_else(|| anyhow::anyhow!("Prelogin response does not contain status element"))?; // Check the status of the prelogin response 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) } let region = root - .child_text("region") + .descendant_text("region") .unwrap_or_else(|| { info!("Prelogin response does not contain region element"); "Unknown" }) .to_string(); - let saml_method = root.child_text("saml-auth-method"); - let saml_request = root.child_text("saml-request"); - let saml_default_browser = root.child_text("saml-default-browser"); + let saml_method = root.descendant_text("saml-auth-method"); + let saml_request = root.descendant_text("saml-request"); + let saml_default_browser = root.descendant_text("saml-default-browser"); // Check if the prelogin response is SAML if saml_method.is_some() && saml_request.is_some() { 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 { } let label_username = root - .child_text("username-label") + .descendant_text("username-label") .unwrap_or_else(|| { info!("Username label has no value, using default"); "Username" }) .to_string(); let label_password = root - .child_text("password-label") + .descendant_text("password-label") .unwrap_or_else(|| { info!("Password label has no value, using default"); "Password" @@ -199,7 +199,7 @@ fn parse_res_xml(res_xml: &str, is_gateway: bool) -> anyhow::Result { .to_string(); let auth_message = root - .child_text("authentication-message") + .descendant_text("authentication-message") .unwrap_or("Please enter the login credentials") .to_string(); let standard_prelogin = StandardPrelogin { diff --git a/crates/gpapi/src/utils/window.rs b/crates/gpapi/src/utils/window.rs index 5e0b0a6..f74b16a 100644 --- a/crates/gpapi/src/utils/window.rs +++ b/crates/gpapi/src/utils/window.rs @@ -1,7 +1,7 @@ use std::{process::ExitStatus, time::Duration}; use anyhow::bail; -use log::{info, warn}; +use log::info; use tauri::Window; use tokio::process::Command; @@ -33,7 +33,7 @@ pub fn raise_window(win: &Window) -> anyhow::Result<()> { let title = win.title()?; tokio::spawn(async move { if let Err(err) = wmctrl_raise_window(&title).await { - warn!("Failed to raise window: {}", err); + info!("Window not raised: {}", err); } }); } diff --git a/crates/gpapi/src/utils/xml.rs b/crates/gpapi/src/utils/xml.rs index 76bf703..2885130 100644 --- a/crates/gpapi/src/utils/xml.rs +++ b/crates/gpapi/src/utils/xml.rs @@ -1,17 +1,27 @@ use roxmltree::Node; pub(crate) trait NodeExt<'a> { + fn find_descendant(&self, name: &str) -> Option>; + fn descendant_text(&self, name: &str) -> Option<&'a str>; + fn find_child(&self, name: &str) -> Option>; fn child_text(&self, name: &str) -> Option<&'a str>; } impl<'a> NodeExt<'a> for Node<'a, 'a> { - fn find_child(&self, name: &str) -> Option> { + fn find_descendant(&self, name: &str) -> Option> { 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> { + self.children().find(|n| n.has_tag_name(name)) + } + fn child_text(&self, name: &str) -> Option<&'a str> { - let node = self.find_child(name)?; - node.text() + self.find_child(name).and_then(|node| node.text()) } }