mirror of
https://github.com/yuezk/GlobalProtect-openconnect.git
synced 2025-05-20 07:26:58 -04:00
195 lines
5.1 KiB
Rust
195 lines
5.1 KiB
Rust
use anyhow::bail;
|
|
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},
|
|
};
|
|
|
|
const REQUIRED_PARAMS: [&str; 8] = [
|
|
"tmp",
|
|
"clientVer",
|
|
"clientos",
|
|
"os-version",
|
|
"host-id",
|
|
"ipv6-support",
|
|
"default-browser",
|
|
"cas-support",
|
|
];
|
|
|
|
#[derive(Debug, Serialize, Type, Clone)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct SamlPrelogin {
|
|
region: String,
|
|
is_gateway: bool,
|
|
saml_request: String,
|
|
support_default_browser: bool,
|
|
}
|
|
|
|
impl SamlPrelogin {
|
|
pub fn region(&self) -> &str {
|
|
&self.region
|
|
}
|
|
|
|
pub fn saml_request(&self) -> &str {
|
|
&self.saml_request
|
|
}
|
|
|
|
pub fn support_default_browser(&self) -> bool {
|
|
self.support_default_browser
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Type, Clone)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct StandardPrelogin {
|
|
region: String,
|
|
is_gateway: bool,
|
|
auth_message: String,
|
|
label_username: String,
|
|
label_password: String,
|
|
}
|
|
|
|
impl StandardPrelogin {
|
|
pub fn region(&self) -> &str {
|
|
&self.region
|
|
}
|
|
|
|
pub fn auth_message(&self) -> &str {
|
|
&self.auth_message
|
|
}
|
|
|
|
pub fn label_username(&self) -> &str {
|
|
&self.label_username
|
|
}
|
|
|
|
pub fn label_password(&self) -> &str {
|
|
&self.label_password
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Type, Clone)]
|
|
#[serde(tag = "type", rename_all = "camelCase")]
|
|
pub enum Prelogin {
|
|
Saml(SamlPrelogin),
|
|
Standard(StandardPrelogin),
|
|
}
|
|
|
|
impl Prelogin {
|
|
pub fn region(&self) -> &str {
|
|
match self {
|
|
Prelogin::Saml(saml) => saml.region(),
|
|
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!("Prelogin with user_agent: {}", user_agent);
|
|
|
|
let portal = normalize_server(portal)?;
|
|
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");
|
|
if gp_params.prefer_default_browser() {
|
|
params.insert("default-browser", "1");
|
|
}
|
|
|
|
params.retain(|k, _| REQUIRED_PARAMS.iter().any(|required_param| required_param == k));
|
|
|
|
let client = Client::builder()
|
|
.danger_accept_invalid_certs(gp_params.ignore_tls_errors())
|
|
.user_agent(user_agent)
|
|
.build()?;
|
|
|
|
let res = client.post(&prelogin_url).form(¶ms).send().await?;
|
|
let status = res.status();
|
|
|
|
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")
|
|
.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 = xml::get_child_text(&doc, "msg").unwrap_or(String::from("Unknown error"));
|
|
bail!("Prelogin failed: {}", msg)
|
|
}
|
|
|
|
let region = xml::get_child_text(&doc, "region").unwrap_or_else(|| {
|
|
info!("Prelogin response does not contain region element");
|
|
String::from("Unknown")
|
|
});
|
|
|
|
let saml_method = xml::get_child_text(&doc, "saml-auth-method");
|
|
let saml_request = xml::get_child_text(&doc, "saml-request");
|
|
let saml_default_browser = xml::get_child_text(&doc, "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())?;
|
|
let support_default_browser = saml_default_browser.map(|s| s.to_lowercase() == "yes").unwrap_or(false);
|
|
|
|
let saml_prelogin = SamlPrelogin {
|
|
region,
|
|
is_gateway,
|
|
saml_request,
|
|
support_default_browser,
|
|
};
|
|
|
|
return Ok(Prelogin::Saml(saml_prelogin));
|
|
}
|
|
|
|
let label_username = xml::get_child_text(&doc, "username-label");
|
|
let label_password = xml::get_child_text(&doc, "password-label");
|
|
// Check if the prelogin response is standard login
|
|
if label_username.is_some() && label_password.is_some() {
|
|
let auth_message =
|
|
xml::get_child_text(&doc, "authentication-message").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(),
|
|
};
|
|
|
|
return Ok(Prelogin::Standard(standard_prelogin));
|
|
}
|
|
|
|
bail!("Invalid prelogin response");
|
|
}
|