mirror of
https://github.com/yuezk/GlobalProtect-openconnect.git
synced 2025-05-20 07:26:58 -04:00
upgrade gpauth
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
[package]
|
||||
name = "gpapi"
|
||||
rust-version.workspace = true
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license = "MIT"
|
||||
|
@@ -1,11 +1,14 @@
|
||||
use std::borrow::{Borrow, Cow};
|
||||
|
||||
use anyhow::bail;
|
||||
use log::{info, warn};
|
||||
use regex::Regex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{error::AuthDataParseError, utils::base64::decode_to_string};
|
||||
|
||||
pub type AuthDataParseResult = anyhow::Result<SamlAuthData, AuthDataParseError>;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SamlAuthData {
|
||||
@@ -33,33 +36,51 @@ impl SamlAuthResult {
|
||||
}
|
||||
|
||||
impl SamlAuthData {
|
||||
pub fn new(username: String, prelogin_cookie: Option<String>, portal_userauthcookie: Option<String>) -> Self {
|
||||
Self {
|
||||
username,
|
||||
prelogin_cookie,
|
||||
portal_userauthcookie,
|
||||
token: None,
|
||||
pub fn new(
|
||||
username: Option<String>,
|
||||
prelogin_cookie: Option<String>,
|
||||
portal_userauthcookie: Option<String>,
|
||||
) -> anyhow::Result<Self> {
|
||||
let username = username.unwrap_or_default();
|
||||
if username.is_empty() {
|
||||
bail!("Invalid username: <empty>");
|
||||
}
|
||||
|
||||
let prelogin_cookie = prelogin_cookie.unwrap_or_default();
|
||||
let portal_userauthcookie = portal_userauthcookie.unwrap_or_default();
|
||||
|
||||
if prelogin_cookie.len() <= 5 && portal_userauthcookie.len() <= 5 {
|
||||
bail!(
|
||||
"Invalid prelogin-cookie: {}, portal-userauthcookie: {}",
|
||||
prelogin_cookie,
|
||||
portal_userauthcookie
|
||||
);
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
username,
|
||||
prelogin_cookie: Some(prelogin_cookie),
|
||||
portal_userauthcookie: Some(portal_userauthcookie),
|
||||
token: None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn from_html(html: &str) -> anyhow::Result<SamlAuthData, AuthDataParseError> {
|
||||
pub fn from_html(html: &str) -> AuthDataParseResult {
|
||||
match parse_xml_tag(html, "saml-auth-status") {
|
||||
Some(saml_status) if saml_status == "1" => {
|
||||
Some(status) if status == "1" => {
|
||||
let username = parse_xml_tag(html, "saml-username");
|
||||
let prelogin_cookie = parse_xml_tag(html, "prelogin-cookie");
|
||||
let portal_userauthcookie = parse_xml_tag(html, "portal-userauthcookie");
|
||||
|
||||
if SamlAuthData::check(&username, &prelogin_cookie, &portal_userauthcookie) {
|
||||
Ok(SamlAuthData::new(
|
||||
username.unwrap(),
|
||||
prelogin_cookie,
|
||||
portal_userauthcookie,
|
||||
))
|
||||
} else {
|
||||
Err(AuthDataParseError::Invalid)
|
||||
}
|
||||
SamlAuthData::new(username, prelogin_cookie, portal_userauthcookie).map_err(|e| {
|
||||
warn!("Failed to parse auth data: {}", e);
|
||||
AuthDataParseError::Invalid
|
||||
})
|
||||
}
|
||||
Some(status) => {
|
||||
warn!("Found invalid auth status: {}", status);
|
||||
Err(AuthDataParseError::Invalid)
|
||||
}
|
||||
Some(_) => Err(AuthDataParseError::Invalid),
|
||||
None => Err(AuthDataParseError::NotFound),
|
||||
}
|
||||
}
|
||||
@@ -105,27 +126,6 @@ impl SamlAuthData {
|
||||
pub fn token(&self) -> Option<&str> {
|
||||
self.token.as_deref()
|
||||
}
|
||||
|
||||
pub fn check(
|
||||
username: &Option<String>,
|
||||
prelogin_cookie: &Option<String>,
|
||||
portal_userauthcookie: &Option<String>,
|
||||
) -> bool {
|
||||
let username_valid = username.as_ref().is_some_and(|username| !username.is_empty());
|
||||
let prelogin_cookie_valid = prelogin_cookie.as_ref().is_some_and(|val| val.len() > 5);
|
||||
let portal_userauthcookie_valid = portal_userauthcookie.as_ref().is_some_and(|val| val.len() > 5);
|
||||
|
||||
let is_valid = username_valid && (prelogin_cookie_valid || portal_userauthcookie_valid);
|
||||
|
||||
if !is_valid {
|
||||
warn!(
|
||||
"Invalid SAML auth data: username: {:?}, prelogin-cookie: {:?}, portal-userauthcookie: {:?}",
|
||||
username, prelogin_cookie, portal_userauthcookie
|
||||
);
|
||||
}
|
||||
|
||||
is_valid
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_xml_tag(html: &str, tag: &str) -> Option<String> {
|
||||
|
@@ -1 +1,28 @@
|
||||
use crate::error::PortalError;
|
||||
|
||||
pub mod args;
|
||||
|
||||
pub trait Args {
|
||||
fn fix_openssl(&self) -> bool;
|
||||
fn ignore_tls_errors(&self) -> bool;
|
||||
}
|
||||
|
||||
pub fn handle_error(err: anyhow::Error, args: &impl Args) {
|
||||
eprintln!("\nError: {}", err);
|
||||
|
||||
let Some(err) = err.downcast_ref::<PortalError>() else {
|
||||
return;
|
||||
};
|
||||
|
||||
if err.is_legacy_openssl_error() && !args.fix_openssl() {
|
||||
eprintln!("\nRe-run it with the `--fix-openssl` option to work around this issue, e.g.:\n");
|
||||
let args = std::env::args().collect::<Vec<_>>();
|
||||
eprintln!("{} --fix-openssl {}\n", args[0], args[1..].join(" "));
|
||||
}
|
||||
|
||||
if err.is_tls_error() && !args.ignore_tls_errors() {
|
||||
eprintln!("\nRe-run it with the `--ignore-tls-errors` option to ignore the certificate error, e.g.:\n");
|
||||
let args = std::env::args().collect::<Vec<_>>();
|
||||
eprintln!("{} --ignore-tls-errors {}\n", args[0], args[1..].join(" "));
|
||||
}
|
||||
}
|
||||
|
@@ -7,7 +7,19 @@ pub enum PortalError {
|
||||
#[error("Portal config error: {0}")]
|
||||
ConfigError(String),
|
||||
#[error("Network error: {0}")]
|
||||
NetworkError(String),
|
||||
NetworkError(#[from] reqwest::Error),
|
||||
#[error("TLS error")]
|
||||
TlsError,
|
||||
}
|
||||
|
||||
impl PortalError {
|
||||
pub fn is_legacy_openssl_error(&self) -> bool {
|
||||
format!("{:?}", self).contains("unsafe legacy renegotiation")
|
||||
}
|
||||
|
||||
pub fn is_tls_error(&self) -> bool {
|
||||
matches!(self, PortalError::TlsError) || format!("{:?}", self).contains("certificate verify failed")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
@@ -17,3 +29,9 @@ pub enum AuthDataParseError {
|
||||
#[error("Invalid auth data")]
|
||||
Invalid,
|
||||
}
|
||||
|
||||
impl AuthDataParseError {
|
||||
pub fn is_invalid(&self) -> bool {
|
||||
matches!(self, AuthDataParseError::Invalid)
|
||||
}
|
||||
}
|
||||
|
@@ -36,7 +36,7 @@ pub async fn gateway_login(gateway: &str, cred: &Credential, gp_params: &GpParam
|
||||
.form(¶ms)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| anyhow::anyhow!(PortalError::NetworkError(e.to_string())))?;
|
||||
.map_err(|e| anyhow::anyhow!(PortalError::NetworkError(e)))?;
|
||||
|
||||
let res = parse_gp_response(res).await.map_err(|err| {
|
||||
warn!("{err}");
|
||||
|
@@ -16,6 +16,7 @@ pub const GP_API_KEY: &[u8; 32] = &[0; 32];
|
||||
|
||||
pub const GP_USER_AGENT: &str = "PAN GlobalProtect";
|
||||
pub const GP_SERVICE_LOCK_FILE: &str = "/var/run/gpservice.lock";
|
||||
pub const GP_CALLBACK_PORT_FILENAME: &str = "gpcallback.port";
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
pub const GP_CLIENT_BINARY: &str = "/usr/bin/gpclient";
|
||||
|
@@ -116,7 +116,7 @@ pub async fn retrieve_config(portal: &str, cred: &Credential, gp_params: &GpPara
|
||||
.form(¶ms)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| anyhow::anyhow!(PortalError::NetworkError(e.to_string())))?;
|
||||
.map_err(|e| anyhow::anyhow!(PortalError::NetworkError(e)))?;
|
||||
|
||||
let res_xml = parse_gp_response(res).await.or_else(|err| {
|
||||
if err.status == StatusCode::NOT_FOUND {
|
||||
|
@@ -116,14 +116,12 @@ pub async fn prelogin(portal: &str, gp_params: &GpParams) -> anyhow::Result<Prel
|
||||
|
||||
let client = Client::try_from(gp_params)?;
|
||||
|
||||
info!("Perform prelogin, user_agent: {}", gp_params.user_agent());
|
||||
|
||||
let res = client
|
||||
.post(&prelogin_url)
|
||||
.form(¶ms)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| anyhow::anyhow!(PortalError::NetworkError(e.to_string())))?;
|
||||
.map_err(|e| anyhow::anyhow!(PortalError::NetworkError(e)))?;
|
||||
|
||||
let res_xml = parse_gp_response(res).await.or_else(|err| {
|
||||
if err.status == StatusCode::NOT_FOUND {
|
||||
|
@@ -7,17 +7,12 @@ use tokio::process::Command;
|
||||
|
||||
pub trait WindowExt {
|
||||
fn raise(&self) -> anyhow::Result<()>;
|
||||
fn hide_menu(&self);
|
||||
}
|
||||
|
||||
impl WindowExt for WebviewWindow {
|
||||
fn raise(&self) -> anyhow::Result<()> {
|
||||
raise_window(self)
|
||||
}
|
||||
|
||||
fn hide_menu(&self) {
|
||||
hide_menu(self);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn raise_window(win: &WebviewWindow) -> anyhow::Result<()> {
|
||||
@@ -40,7 +35,7 @@ pub fn raise_window(win: &WebviewWindow) -> anyhow::Result<()> {
|
||||
|
||||
// Calling window.show() on Windows will cause the menu to be shown.
|
||||
// We need to hide it again.
|
||||
hide_menu(win);
|
||||
win.hide_menu()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -76,22 +71,3 @@ async fn wmctrl_try_raise_window(title: &str) -> anyhow::Result<ExitStatus> {
|
||||
|
||||
Ok(exit_status)
|
||||
}
|
||||
|
||||
fn hide_menu(win: &WebviewWindow) {
|
||||
// let menu_handle = win.menu_handle();
|
||||
|
||||
// tokio::spawn(async move {
|
||||
// loop {
|
||||
// let menu_visible = menu_handle.is_visible().unwrap_or(false);
|
||||
|
||||
// if !menu_visible {
|
||||
// break;
|
||||
// }
|
||||
|
||||
// if menu_visible {
|
||||
// let _ = menu_handle.hide();
|
||||
// tokio::time::sleep(Duration::from_millis(10)).await;
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
}
|
||||
|
Reference in New Issue
Block a user