mirror of
				https://github.com/yuezk/GlobalProtect-openconnect.git
				synced 2025-05-20 07:26:58 -04:00 
			
		
		
		
	Support connect gateway (#306)
This commit is contained in:
		| @@ -1,16 +1,16 @@ | ||||
| use anyhow::ensure; | ||||
| use anyhow::bail; | ||||
| use log::info; | ||||
| use reqwest::Client; | ||||
| use reqwest::{Client, StatusCode}; | ||||
| use roxmltree::Document; | ||||
| use serde::Serialize; | ||||
| use specta::Type; | ||||
| use thiserror::Error; | ||||
|  | ||||
| use crate::{ | ||||
|   credential::{AuthCookieCredential, Credential}, | ||||
|   gateway::{parse_gateways, Gateway}, | ||||
|   gp_params::GpParams, | ||||
|   utils::{normalize_server, xml}, | ||||
|   portal::PortalError, | ||||
|   utils::{normalize_server, remove_url_scheme, xml}, | ||||
| }; | ||||
|  | ||||
| #[derive(Debug, Serialize, Type)] | ||||
| @@ -18,25 +18,12 @@ use crate::{ | ||||
| pub struct PortalConfig { | ||||
|   portal: String, | ||||
|   auth_cookie: AuthCookieCredential, | ||||
|   config_cred: Credential, | ||||
|   gateways: Vec<Gateway>, | ||||
|   config_digest: Option<String>, | ||||
| } | ||||
|  | ||||
| impl PortalConfig { | ||||
|   pub fn new( | ||||
|     portal: String, | ||||
|     auth_cookie: AuthCookieCredential, | ||||
|     gateways: Vec<Gateway>, | ||||
|     config_digest: Option<String>, | ||||
|   ) -> Self { | ||||
|     Self { | ||||
|       portal, | ||||
|       auth_cookie, | ||||
|       gateways, | ||||
|       config_digest, | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   pub fn portal(&self) -> &str { | ||||
|     &self.portal | ||||
|   } | ||||
| @@ -49,6 +36,10 @@ impl PortalConfig { | ||||
|     &self.auth_cookie | ||||
|   } | ||||
|  | ||||
|   pub fn config_cred(&self) -> &Credential { | ||||
|     &self.config_cred | ||||
|   } | ||||
|  | ||||
|   /// In-place sort the gateways by region | ||||
|   pub fn sort_gateways(&mut self, region: &str) { | ||||
|     let preferred_gateway = self.find_preferred_gateway(region); | ||||
| @@ -98,12 +89,6 @@ impl PortalConfig { | ||||
|   } | ||||
| } | ||||
|  | ||||
| #[derive(Error, Debug)] | ||||
| pub enum PortalConfigError { | ||||
|   #[error("Empty response, retrying can help")] | ||||
|   EmptyResponse, | ||||
| } | ||||
|  | ||||
| pub async fn retrieve_config( | ||||
|   portal: &str, | ||||
|   cred: &Credential, | ||||
| @@ -128,13 +113,35 @@ pub async fn retrieve_config( | ||||
|   info!("Portal config, user_agent: {}", gp_params.user_agent()); | ||||
|  | ||||
|   let res = client.post(&url).form(¶ms).send().await?; | ||||
|   let res_xml = res.error_for_status()?.text().await?; | ||||
|   let status = res.status(); | ||||
|  | ||||
|   ensure!(!res_xml.is_empty(), PortalConfigError::EmptyResponse); | ||||
|   if status == StatusCode::NOT_FOUND { | ||||
|     bail!(PortalError::ConfigError( | ||||
|       "Config endpoint not found".to_string() | ||||
|     )) | ||||
|   } | ||||
|  | ||||
|   let doc = Document::parse(&res_xml)?; | ||||
|   let mut gateways = | ||||
|     parse_gateways(&doc).ok_or_else(|| anyhow::anyhow!("Failed to parse gateways"))?; | ||||
|   if status.is_client_error() || status.is_server_error() { | ||||
|     bail!("Portal config error: {}", status) | ||||
|   } | ||||
|  | ||||
|   let res_xml = res | ||||
|     .text() | ||||
|     .await | ||||
|     .map_err(|e| PortalError::ConfigError(e.to_string()))?; | ||||
|  | ||||
|   if res_xml.is_empty() { | ||||
|     bail!(PortalError::ConfigError( | ||||
|       "Empty portal config response".to_string() | ||||
|     )) | ||||
|   } | ||||
|  | ||||
|   let doc = Document::parse(&res_xml).map_err(|e| PortalError::ConfigError(e.to_string()))?; | ||||
|  | ||||
|   let mut gateways = parse_gateways(&doc).unwrap_or_else(|| { | ||||
|     info!("No gateways found in portal config"); | ||||
|     vec![] | ||||
|   }); | ||||
|  | ||||
|   let user_auth_cookie = xml::get_child_text(&doc, "portal-userauthcookie").unwrap_or_default(); | ||||
|   let prelogon_user_auth_cookie = | ||||
| @@ -142,26 +149,18 @@ pub async fn retrieve_config( | ||||
|   let config_digest = xml::get_child_text(&doc, "config-digest"); | ||||
|  | ||||
|   if gateways.is_empty() { | ||||
|     gateways.push(Gateway { | ||||
|       name: server.to_string(), | ||||
|       address: server.to_string(), | ||||
|       priority: 0, | ||||
|       priority_rules: vec![], | ||||
|     }); | ||||
|     gateways.push(Gateway::new(server.to_string(), server.to_string())); | ||||
|   } | ||||
|  | ||||
|   Ok(PortalConfig::new( | ||||
|     server.to_string(), | ||||
|     AuthCookieCredential::new( | ||||
|   Ok(PortalConfig { | ||||
|     portal: server.to_string(), | ||||
|     auth_cookie: AuthCookieCredential::new( | ||||
|       cred.username(), | ||||
|       &user_auth_cookie, | ||||
|       &prelogon_user_auth_cookie, | ||||
|     ), | ||||
|     config_cred: cred.clone(), | ||||
|     gateways, | ||||
|     config_digest, | ||||
|   )) | ||||
| } | ||||
|  | ||||
| fn remove_url_scheme(s: &str) -> String { | ||||
|   s.replace("http://", "").replace("https://", "") | ||||
|   }) | ||||
| } | ||||
|   | ||||
| @@ -3,3 +3,13 @@ mod prelogin; | ||||
|  | ||||
| pub use config::*; | ||||
| 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,12 +1,13 @@ | ||||
| use anyhow::bail; | ||||
| use log::{info, trace}; | ||||
| use reqwest::Client; | ||||
| use log::info; | ||||
| use reqwest::{Client, StatusCode}; | ||||
| use roxmltree::Document; | ||||
| use serde::Serialize; | ||||
| use specta::Type; | ||||
|  | ||||
| use crate::{ | ||||
|   gp_params::GpParams, | ||||
|   portal::PortalError, | ||||
|   utils::{base64, normalize_server, xml}, | ||||
| }; | ||||
|  | ||||
| @@ -25,6 +26,7 @@ const REQUIRED_PARAMS: [&str; 8] = [ | ||||
| #[serde(rename_all = "camelCase")] | ||||
| pub struct SamlPrelogin { | ||||
|   region: String, | ||||
|   is_gateway: bool, | ||||
|   saml_request: String, | ||||
|   support_default_browser: bool, | ||||
| } | ||||
| @@ -47,6 +49,7 @@ impl SamlPrelogin { | ||||
| #[serde(rename_all = "camelCase")] | ||||
| pub struct StandardPrelogin { | ||||
|   region: String, | ||||
|   is_gateway: bool, | ||||
|   auth_message: String, | ||||
|   label_username: String, | ||||
|   label_password: String, | ||||
| @@ -84,21 +87,27 @@ impl Prelogin { | ||||
|       Prelogin::Standard(standard) => standard.region(), | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   pub fn is_gateway(&self) -> bool { | ||||
|     match self { | ||||
|       Prelogin::Saml(saml) => saml.is_gateway, | ||||
|       Prelogin::Standard(standard) => standard.is_gateway, | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| pub async fn prelogin(portal: &str, gp_params: &GpParams) -> anyhow::Result<Prelogin> { | ||||
|   let user_agent = gp_params.user_agent(); | ||||
|   info!("Portal prelogin, user_agent: {}", user_agent); | ||||
|   info!("Prelogin with user_agent: {}", user_agent); | ||||
|  | ||||
|   let portal = normalize_server(portal)?; | ||||
|   let prelogin_url = format!( | ||||
|     "{portal}/{}/prelogin.esp", | ||||
|     if gp_params.is_gateway() { | ||||
|       "ssl-vpn" | ||||
|     } else { | ||||
|       "global-protect" | ||||
|     } | ||||
|   ); | ||||
|   let is_gateway = gp_params.is_gateway(); | ||||
|   let path = if is_gateway { | ||||
|     "ssl-vpn" | ||||
|   } else { | ||||
|     "global-protect" | ||||
|   }; | ||||
|   let prelogin_url = format!("{portal}/{}/prelogin.esp", path); | ||||
|   let mut params = gp_params.to_params(); | ||||
|  | ||||
|   params.insert("tmp", "tmp"); | ||||
| @@ -118,9 +127,30 @@ pub async fn prelogin(portal: &str, gp_params: &GpParams) -> anyhow::Result<Prel | ||||
|     .build()?; | ||||
|  | ||||
|   let res = client.post(&prelogin_url).form(¶ms).send().await?; | ||||
|   let res_xml = res.error_for_status()?.text().await?; | ||||
|   let status = res.status(); | ||||
|  | ||||
|   trace!("Prelogin response: {}", res_xml); | ||||
|   if status == StatusCode::NOT_FOUND { | ||||
|     bail!(PortalError::PreloginError( | ||||
|       "Prelogin endpoint not found".to_string() | ||||
|     )) | ||||
|   } | ||||
|  | ||||
|   if status.is_client_error() || status.is_server_error() { | ||||
|     bail!("Prelogin error: {}", status) | ||||
|   } | ||||
|  | ||||
|   let res_xml = res | ||||
|     .text() | ||||
|     .await | ||||
|     .map_err(|e| PortalError::PreloginError(e.to_string()))?; | ||||
|  | ||||
|   let prelogin = | ||||
|     parse_res_xml(res_xml, is_gateway).map_err(|e| PortalError::PreloginError(e.to_string()))?; | ||||
|  | ||||
|   Ok(prelogin) | ||||
| } | ||||
|  | ||||
| fn parse_res_xml(res_xml: String, is_gateway: bool) -> anyhow::Result<Prelogin> { | ||||
|   let doc = Document::parse(&res_xml)?; | ||||
|  | ||||
|   let status = xml::get_child_text(&doc, "status") | ||||
| @@ -146,6 +176,7 @@ pub async fn prelogin(portal: &str, gp_params: &GpParams) -> anyhow::Result<Prel | ||||
|  | ||||
|     let saml_prelogin = SamlPrelogin { | ||||
|       region, | ||||
|       is_gateway, | ||||
|       saml_request, | ||||
|       support_default_browser, | ||||
|     }; | ||||
| @@ -161,6 +192,7 @@ pub async fn prelogin(portal: &str, gp_params: &GpParams) -> anyhow::Result<Prel | ||||
|       .unwrap_or(String::from("Please enter the login credentials")); | ||||
|     let standard_prelogin = StandardPrelogin { | ||||
|       region, | ||||
|       is_gateway, | ||||
|       auth_message, | ||||
|       label_username: label_username.unwrap(), | ||||
|       label_password: label_password.unwrap(), | ||||
|   | ||||
		Reference in New Issue
	
	Block a user