mirror of
				https://github.com/yuezk/GlobalProtect-openconnect.git
				synced 2025-05-20 07:26:58 -04:00 
			
		
		
		
	Support HIP report (#309)
This commit is contained in:
		
							
								
								
									
										178
									
								
								crates/gpapi/src/gateway/hip.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										178
									
								
								crates/gpapi/src/gateway/hip.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,178 @@ | ||||
| use std::collections::HashMap; | ||||
|  | ||||
| use log::{info, warn}; | ||||
| use reqwest::Client; | ||||
| use roxmltree::Document; | ||||
|  | ||||
| use crate::{gp_params::GpParams, process::hip_launcher::HipLauncher, utils::normalize_server}; | ||||
|  | ||||
| struct HipReporter<'a> { | ||||
|   server: String, | ||||
|   cookie: &'a str, | ||||
|   md5: &'a str, | ||||
|   csd_wrapper: &'a str, | ||||
|   gp_params: &'a GpParams, | ||||
|   client: Client, | ||||
| } | ||||
|  | ||||
| impl HipReporter<'_> { | ||||
|   async fn report(&self) -> anyhow::Result<()> { | ||||
|     let client_ip = self.retrieve_client_ip().await?; | ||||
|  | ||||
|     let hip_needed = match self.check_hip(&client_ip).await { | ||||
|       Ok(hip_needed) => hip_needed, | ||||
|       Err(err) => { | ||||
|         warn!("Failed to check HIP: {}", err); | ||||
|         return Ok(()); | ||||
|       } | ||||
|     }; | ||||
|  | ||||
|     if !hip_needed { | ||||
|       info!("HIP report not needed"); | ||||
|       return Ok(()); | ||||
|     } | ||||
|  | ||||
|     info!("HIP report needed, generating report..."); | ||||
|     let report = self.generate_report(&client_ip).await?; | ||||
|  | ||||
|     if let Err(err) = self.submit_hip(&client_ip, &report).await { | ||||
|       warn!("Failed to submit HIP report: {}", err); | ||||
|     } | ||||
|  | ||||
|     Ok(()) | ||||
|   } | ||||
|  | ||||
|   async fn retrieve_client_ip(&self) -> anyhow::Result<String> { | ||||
|     let config_url = format!("{}/ssl-vpn/getconfig.esp", self.server); | ||||
|     let mut params: HashMap<&str, &str> = HashMap::new(); | ||||
|  | ||||
|     params.insert("client-type", "1"); | ||||
|     params.insert("protocol-version", "p1"); | ||||
|     params.insert("internal", "no"); | ||||
|     params.insert("ipv6-support", "yes"); | ||||
|     params.insert("clientos", self.gp_params.client_os()); | ||||
|     params.insert("hmac-algo", "sha1,md5,sha256"); | ||||
|     params.insert("enc-algo", "aes-128-cbc,aes-256-cbc"); | ||||
|  | ||||
|     if let Some(os_version) = self.gp_params.os_version() { | ||||
|       params.insert("os-version", os_version); | ||||
|     } | ||||
|     if let Some(client_version) = self.gp_params.client_version() { | ||||
|       params.insert("app-version", client_version); | ||||
|     } | ||||
|  | ||||
|     let params = merge_cookie_params(self.cookie, ¶ms)?; | ||||
|  | ||||
|     let res = self.client.post(&config_url).form(¶ms).send().await?; | ||||
|     let res_xml = res.error_for_status()?.text().await?; | ||||
|     let doc = Document::parse(&res_xml)?; | ||||
|  | ||||
|     // Get <ip-address> | ||||
|     let ip = doc | ||||
|       .descendants() | ||||
|       .find(|n| n.has_tag_name("ip-address")) | ||||
|       .and_then(|n| n.text()) | ||||
|       .ok_or_else(|| anyhow::anyhow!("ip-address not found"))?; | ||||
|  | ||||
|     Ok(ip.to_string()) | ||||
|   } | ||||
|  | ||||
|   async fn check_hip(&self, client_ip: &str) -> anyhow::Result<bool> { | ||||
|     let url = format!("{}/ssl-vpn/hipreportcheck.esp", self.server); | ||||
|     let mut params = HashMap::new(); | ||||
|  | ||||
|     params.insert("client-role", "global-protect-full"); | ||||
|     params.insert("client-ip", client_ip); | ||||
|     params.insert("md5", self.md5); | ||||
|  | ||||
|     let params = merge_cookie_params(self.cookie, ¶ms)?; | ||||
|     let res = self.client.post(&url).form(¶ms).send().await?; | ||||
|     let res_xml = res.error_for_status()?.text().await?; | ||||
|  | ||||
|     is_hip_needed(&res_xml) | ||||
|   } | ||||
|  | ||||
|   async fn generate_report(&self, client_ip: &str) -> anyhow::Result<String> { | ||||
|     let launcher = HipLauncher::new(self.csd_wrapper) | ||||
|       .cookie(self.cookie) | ||||
|       .md5(self.md5) | ||||
|       .client_ip(client_ip) | ||||
|       .client_os(self.gp_params.client_os()) | ||||
|       .client_version(self.gp_params.client_version()); | ||||
|  | ||||
|     launcher.launch().await | ||||
|   } | ||||
|  | ||||
|   async fn submit_hip(&self, client_ip: &str, report: &str) -> anyhow::Result<()> { | ||||
|     let url = format!("{}/ssl-vpn/hipreport.esp", self.server); | ||||
|  | ||||
|     let mut params = HashMap::new(); | ||||
|     params.insert("client-role", "global-protect-full"); | ||||
|     params.insert("client-ip", client_ip); | ||||
|     params.insert("report", report); | ||||
|  | ||||
|     let params = merge_cookie_params(self.cookie, ¶ms)?; | ||||
|     let res = self.client.post(&url).form(¶ms).send().await?; | ||||
|     let res_xml = res.error_for_status()?.text().await?; | ||||
|  | ||||
|     info!("HIP check response: {}", res_xml); | ||||
|  | ||||
|     Ok(()) | ||||
|   } | ||||
| } | ||||
|  | ||||
| fn is_hip_needed(res_xml: &str) -> anyhow::Result<bool> { | ||||
|   let doc = Document::parse(res_xml)?; | ||||
|  | ||||
|   let hip_needed = doc | ||||
|     .descendants() | ||||
|     .find(|n| n.has_tag_name("hip-report-needed")) | ||||
|     .and_then(|n| n.text()) | ||||
|     .ok_or_else(|| anyhow::anyhow!("hip-report-needed not found"))?; | ||||
|  | ||||
|   Ok(hip_needed == "yes") | ||||
| } | ||||
|  | ||||
| fn merge_cookie_params(cookie: &str, params: &HashMap<&str, &str>) -> anyhow::Result<HashMap<String, String>> { | ||||
|   let cookie_params = serde_urlencoded::from_str::<HashMap<String, String>>(cookie)?; | ||||
|   let params = params | ||||
|     .iter() | ||||
|     .map(|(k, v)| (k.to_string(), v.to_string())) | ||||
|     .chain(cookie_params) | ||||
|     .collect::<HashMap<String, String>>(); | ||||
|  | ||||
|   Ok(params) | ||||
| } | ||||
|  | ||||
| // Compute md5 for fields except authcookie,preferred-ip,preferred-ipv6 | ||||
| fn build_csd_token(cookie: &str) -> anyhow::Result<String> { | ||||
|   let mut cookie_params = serde_urlencoded::from_str::<Vec<(String, String)>>(cookie)?; | ||||
|   cookie_params.retain(|(k, _)| k != "authcookie" && k != "preferred-ip" && k != "preferred-ipv6"); | ||||
|  | ||||
|   let token = serde_urlencoded::to_string(cookie_params)?; | ||||
|   let md5 = format!("{:x}", md5::compute(token)); | ||||
|  | ||||
|   Ok(md5) | ||||
| } | ||||
|  | ||||
| pub async fn hip_report(gateway: &str, cookie: &str, csd_wrapper: &str, gp_params: &GpParams) -> anyhow::Result<()> { | ||||
|   let client = Client::builder() | ||||
|     .danger_accept_invalid_certs(gp_params.ignore_tls_errors()) | ||||
|     .user_agent(gp_params.user_agent()) | ||||
|     .build()?; | ||||
|  | ||||
|   let md5 = build_csd_token(cookie)?; | ||||
|  | ||||
|   info!("Submit HIP report md5: {}", md5); | ||||
|  | ||||
|   let reporter = HipReporter { | ||||
|     server: normalize_server(gateway)?, | ||||
|     cookie, | ||||
|     md5: &md5, | ||||
|     csd_wrapper, | ||||
|     gp_params, | ||||
|     client, | ||||
|   }; | ||||
|  | ||||
|   reporter.report().await | ||||
| } | ||||
| @@ -1,5 +1,6 @@ | ||||
| mod login; | ||||
| mod parse_gateways; | ||||
| pub mod hip; | ||||
|  | ||||
| pub use login::*; | ||||
| pub(crate) use parse_gateways::*; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user