mirror of
				https://github.com/yuezk/GlobalProtect-openconnect.git
				synced 2025-05-20 07:26:58 -04:00 
			
		
		
		
	Compare commits
	
		
			5 Commits
		
	
	
		
			v2.0.0-bet
			...
			v2.0.0-bet
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 87b965f80c | ||
|  | b09b21ae0f | ||
|  | 7e372cd113 | ||
|  | 1e211e8912 | ||
|  | 8bc4049a0f | 
							
								
								
									
										138
									
								
								.github/workflows/build.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										138
									
								
								.github/workflows/build.yaml
									
									
									
									
										vendored
									
									
								
							| @@ -6,8 +6,8 @@ on: | ||||
|       - "*.md" | ||||
|       - .vscode | ||||
|       - .devcontainer | ||||
|     # branches: | ||||
|       # - main | ||||
|     branches: | ||||
|       - main | ||||
|     # tags: | ||||
|     #   - v*.*.* | ||||
| jobs: | ||||
| @@ -114,137 +114,3 @@ jobs: | ||||
|           name: artifact-${{ matrix.arch }}-tauri | ||||
|           path: | | ||||
|             gpgui/.tmp/artifact | ||||
|  | ||||
|   package-rpm: | ||||
|     needs: [setup-matrix, build-tauri] | ||||
|     runs-on: ubuntu-latest | ||||
|     strategy: | ||||
|       matrix: | ||||
|         arch: ${{ fromJson(needs.setup-matrix.outputs.matrix) }} | ||||
|     steps: | ||||
|       - name: Checkout gpgui repo | ||||
|         uses: actions/checkout@v4 | ||||
|         with: | ||||
|           token: ${{ secrets.GH_PAT }} | ||||
|           repository: yuezk/gpgui | ||||
|           path: gpgui | ||||
|  | ||||
|       - name: Download artifact-${{ matrix.arch }} | ||||
|         uses: actions/download-artifact@v4 | ||||
|         with: | ||||
|           name: artifact-${{ matrix.arch }}-tauri | ||||
|           path: gpgui/.tmp/artifact | ||||
|  | ||||
|       - name: Set up QEMU | ||||
|         uses: docker/setup-qemu-action@v3 | ||||
|         with: | ||||
|           platforms: ${{ matrix.arch }} | ||||
|  | ||||
|       - name: Login to Docker Hub | ||||
|         uses: docker/login-action@v3 | ||||
|         with: | ||||
|           username: ${{ secrets.DOCKER_HUB_USERNAME }} | ||||
|           password: ${{ secrets.DOCKER_HUB_TOKEN }} | ||||
|  | ||||
|       - name: Create RPM package | ||||
|         run: | | ||||
|           docker run \ | ||||
|             --rm \ | ||||
|             -v $(pwd):/${{ github.workspace }} \ | ||||
|             -w ${{ github.workspace }} \ | ||||
|             --platform linux/${{ matrix.arch }} \ | ||||
|             yuezk/gpdev:rpm-builder \ | ||||
|             "./gpgui/scripts/build-rpm.sh" | ||||
|  | ||||
|       - name: Upload rpm artifacts | ||||
|         uses: actions/upload-artifact@v4 | ||||
|         with: | ||||
|           name: artifact-${{ matrix.arch }}-rpm | ||||
|           path: | | ||||
|             gpgui/.tmp/artifact/*.rpm | ||||
|  | ||||
|   package-pkgbuild: | ||||
|     needs: [setup-matrix, build-tauri] | ||||
|     runs-on: ubuntu-latest | ||||
|     strategy: | ||||
|       matrix: | ||||
|         arch: ${{ fromJson(needs.setup-matrix.outputs.matrix) }} | ||||
|     steps: | ||||
|       - name: Checkout gpgui repo | ||||
|         uses: actions/checkout@v4 | ||||
|         with: | ||||
|           token: ${{ secrets.GH_PAT }} | ||||
|           repository: yuezk/gpgui | ||||
|           path: gpgui | ||||
|  | ||||
|       - name: Download artifact-${{ matrix.arch }} | ||||
|         uses: actions/download-artifact@v4 | ||||
|         with: | ||||
|           name: artifact-${{ matrix.arch }}-tauri | ||||
|           path: gpgui/.tmp/artifact | ||||
|  | ||||
|       - name: Set up QEMU | ||||
|         uses: docker/setup-qemu-action@v3 | ||||
|         with: | ||||
|           platforms: ${{ matrix.arch }} | ||||
|  | ||||
|       - name: Login to Docker Hub | ||||
|         uses: docker/login-action@v3 | ||||
|         with: | ||||
|           username: ${{ secrets.DOCKER_HUB_USERNAME }} | ||||
|           password: ${{ secrets.DOCKER_HUB_TOKEN }} | ||||
|  | ||||
|       - name: Generate PKGBUILD | ||||
|         run: | | ||||
|           ./gpgui/scripts/generate-pkgbuild.sh | ||||
|  | ||||
|       - name: Build PKGBUILD package | ||||
|         run: | | ||||
|           # Generate PKGBUILD to .tmp/pkgbuild | ||||
|           ./gpgui/scripts/generate-pkgbuild.sh | ||||
|  | ||||
|           # Build package | ||||
|           docker run \ | ||||
|             --rm \ | ||||
|             -v $(pwd)/gpgui/.tmp/pkgbuild:/pkgbuild \ | ||||
|             --platform linux/${{ matrix.arch }} \ | ||||
|             yuezk/gpdev:pkgbuild | ||||
|  | ||||
|       - name: Upload pkgbuild artifacts | ||||
|         uses: actions/upload-artifact@v4 | ||||
|         with: | ||||
|           name: artifact-${{ matrix.arch }}-pkgbuild | ||||
|           path: | | ||||
|             gpgui/.tmp/pkgbuild/*.pkg.tar.zst | ||||
|  | ||||
|   gh-release: | ||||
|     if: startsWith(github.ref, 'refs/tags/') | ||||
|     runs-on: ubuntu-latest | ||||
|     needs: | ||||
|       - package-rpm | ||||
|       - package-pkgbuild | ||||
|  | ||||
|     steps: | ||||
|       - name: Download artifact | ||||
|         uses: actions/download-artifact@v4 | ||||
|         with: | ||||
|           path: artifact | ||||
|           pattern: artifact-* | ||||
|           merge-multiple: true | ||||
|  | ||||
|       - name: Generate checksum | ||||
|         uses: jmgilman/actions-generate-checksum@v1 | ||||
|         with: | ||||
|           output: checksums.txt | ||||
|           patterns: | | ||||
|             artifact/* | ||||
|  | ||||
|       - name: Create GH release | ||||
|         uses: softprops/action-gh-release@v1 | ||||
|         with: | ||||
|           token: ${{ secrets.GH_PAT }} | ||||
|           prerelease: contains(github.ref, 'latest') | ||||
|           fail_on_unmatched_files: true | ||||
|           files: | | ||||
|             checksums.txt | ||||
|             artifact/* | ||||
|   | ||||
							
								
								
									
										3
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							| @@ -48,6 +48,7 @@ | ||||
|         "vpnc", | ||||
|         "vpninfo", | ||||
|         "wmctrl", | ||||
|         "XAUTHORITY" | ||||
|         "XAUTHORITY", | ||||
|         "yuezk" | ||||
|     ] | ||||
| } | ||||
|   | ||||
							
								
								
									
										25
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										25
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| @@ -1423,11 +1423,12 @@ dependencies = [ | ||||
|  | ||||
| [[package]] | ||||
| name = "gpapi" | ||||
| version = "2.0.0-beta2" | ||||
| version = "2.0.0-beta3" | ||||
| dependencies = [ | ||||
|  "anyhow", | ||||
|  "base64 0.21.5", | ||||
|  "chacha20poly1305", | ||||
|  "clap", | ||||
|  "dotenvy_macro", | ||||
|  "log", | ||||
|  "redact-engine", | ||||
| @@ -1450,7 +1451,7 @@ dependencies = [ | ||||
|  | ||||
| [[package]] | ||||
| name = "gpauth" | ||||
| version = "2.0.0-beta2" | ||||
| version = "2.0.0-beta3" | ||||
| dependencies = [ | ||||
|  "anyhow", | ||||
|  "clap", | ||||
| @@ -1470,7 +1471,7 @@ dependencies = [ | ||||
|  | ||||
| [[package]] | ||||
| name = "gpclient" | ||||
| version = "2.0.0-beta2" | ||||
| version = "2.0.0-beta3" | ||||
| dependencies = [ | ||||
|  "anyhow", | ||||
|  "clap", | ||||
| @@ -1491,7 +1492,7 @@ dependencies = [ | ||||
|  | ||||
| [[package]] | ||||
| name = "gpservice" | ||||
| version = "2.0.0-beta2" | ||||
| version = "2.0.0-beta3" | ||||
| dependencies = [ | ||||
|  "anyhow", | ||||
|  "axum", | ||||
| @@ -1564,9 +1565,9 @@ dependencies = [ | ||||
|  | ||||
| [[package]] | ||||
| name = "h2" | ||||
| version = "0.3.22" | ||||
| version = "0.3.24" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "4d6250322ef6e60f93f9a2162799302cd6f68f79f6e5d85c8c16f14d1d958178" | ||||
| checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" | ||||
| dependencies = [ | ||||
|  "bytes", | ||||
|  "fnv", | ||||
| @@ -1583,9 +1584,9 @@ dependencies = [ | ||||
|  | ||||
| [[package]] | ||||
| name = "h2" | ||||
| version = "0.4.0" | ||||
| version = "0.4.2" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "e1d308f63daf4181410c242d34c11f928dcb3aa105852019e043c9d1f4e4368a" | ||||
| checksum = "31d030e59af851932b72ceebadf4a2b5986dba4c3b99dd2493f8273a0f151943" | ||||
| dependencies = [ | ||||
|  "bytes", | ||||
|  "fnv", | ||||
| @@ -1743,7 +1744,7 @@ dependencies = [ | ||||
|  "futures-channel", | ||||
|  "futures-core", | ||||
|  "futures-util", | ||||
|  "h2 0.3.22", | ||||
|  "h2 0.3.24", | ||||
|  "http 0.2.11", | ||||
|  "http-body 0.4.6", | ||||
|  "httparse", | ||||
| @@ -1766,7 +1767,7 @@ dependencies = [ | ||||
|  "bytes", | ||||
|  "futures-channel", | ||||
|  "futures-util", | ||||
|  "h2 0.4.0", | ||||
|  "h2 0.4.2", | ||||
|  "http 1.0.0", | ||||
|  "http-body 1.0.0", | ||||
|  "httparse", | ||||
| @@ -2446,7 +2447,7 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" | ||||
|  | ||||
| [[package]] | ||||
| name = "openconnect" | ||||
| version = "2.0.0-beta2" | ||||
| version = "2.0.0-beta3" | ||||
| dependencies = [ | ||||
|  "cc", | ||||
|  "is_executable", | ||||
| @@ -3070,7 +3071,7 @@ dependencies = [ | ||||
|  "encoding_rs", | ||||
|  "futures-core", | ||||
|  "futures-util", | ||||
|  "h2 0.3.22", | ||||
|  "h2 0.3.24", | ||||
|  "http 0.2.11", | ||||
|  "http-body 0.4.6", | ||||
|  "hyper 0.14.28", | ||||
|   | ||||
| @@ -4,7 +4,7 @@ resolver = "2" | ||||
| members = ["crates/*", "apps/gpclient", "apps/gpservice", "apps/gpauth"] | ||||
|  | ||||
| [workspace.package] | ||||
| version = "2.0.0-beta2" | ||||
| version = "2.0.0-beta3" | ||||
| authors = ["Kevin Yue <k3vinyue@gmail.com>"] | ||||
| homepage = "https://github.com/yuezk/GlobalProtect-openconnect" | ||||
| edition = "2021" | ||||
|   | ||||
							
								
								
									
										28
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										28
									
								
								README.md
									
									
									
									
									
								
							| @@ -33,11 +33,12 @@ Commands: | ||||
|  | ||||
| Options: | ||||
|       --fix-openssl        Get around the OpenSSL `unsafe legacy renegotiation` error | ||||
|       --ignore-tls-errors  Ignore the TLS errors | ||||
|   -h, --help               Print help | ||||
|   -V, --version            Print version | ||||
| ``` | ||||
|  | ||||
| See `gpclient -h` for help. | ||||
| See 'gpclient help <command>' for more information on a specific command. | ||||
| ``` | ||||
|  | ||||
| ### GUI | ||||
|  | ||||
| @@ -49,6 +50,15 @@ The GUI version is also available after you installed it. You can launch it from | ||||
|  | ||||
| ## Installation | ||||
|  | ||||
| > [!Note] | ||||
| > | ||||
| > This instruction is for the 2.x version. The 1.x version is still available on the [1.x](https://github.com/yuezk/GlobalProtect-openconnect/tree/1.x) branch, you can build it from the source code by following the instructions in the `README.md` file. | ||||
|  | ||||
| > [!Warning] | ||||
| > | ||||
| > The client requires `openconnect >= 8.20`, please make sure you have it installed, you can check it with `openconnect --version`. | ||||
| > Installing the client from PPA will automatically install the required version of `openconnect`. | ||||
|  | ||||
| ### Debian/Ubuntu based distributions | ||||
|  | ||||
| #### Install from PPA | ||||
| @@ -59,6 +69,10 @@ sudo apt-get update | ||||
| sudo apt-get install globalprotect-openconnect | ||||
| ``` | ||||
|  | ||||
| > [!Note] | ||||
| > | ||||
| > For Linux Mint, you might need to import the GPG key with: `sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 7937C393082992E5D6E4A60453FC26B43838D761` if you encountered an error `gpg: keyserver receive failed: General error`. | ||||
|  | ||||
| #### Install from deb package | ||||
|  | ||||
| Download the latest deb package from [releases](https://github.com/yuezk/GlobalProtect-openconnect/releases) page. Then install it with `dpkg`: | ||||
| @@ -73,6 +87,10 @@ sudo dpkg -i globalprotect-openconnect_*.deb | ||||
|  | ||||
| Install from AUR: [globalprotect-openconnect-git](https://aur.archlinux.org/packages/globalprotect-openconnect-git/) | ||||
|  | ||||
| ``` | ||||
| yay -S globalprotect-openconnect-git | ||||
| ``` | ||||
|  | ||||
| #### Install from package | ||||
|  | ||||
| Download the latest package from [releases](https://github.com/yuezk/GlobalProtect-openconnect/releases) page. Then install it with `pacman`: | ||||
| @@ -102,11 +120,7 @@ Download the latest RPM package from [releases](https://github.com/yuezk/GlobalP | ||||
|  | ||||
| ### Other distributions | ||||
|  | ||||
| The project depends on `openconnect`, `webkit2gtk`, `libsecret`, `libayatana-appindicator` or `libappindicator-gtk3`. You can install them first and then download the latest binary release (i.e., `*.bin.tar.gz`) from [releases](https://github.com/yuezk/GlobalProtect-openconnect/releases) page. | ||||
|  | ||||
| ### Install the Old Version (v1.4.9) | ||||
|  | ||||
| The 1.x version is still available on the [1.x](https://github.com/yuezk/GlobalProtect-openconnect/tree/1.x) branch, you can build it from the source code by following the instructions in the `README.md` file. | ||||
| The project depends on `openconnect >= 8.20`, `webkit2gtk`, `libsecret`, `libayatana-appindicator` or `libappindicator-gtk3`. You can install them first and then download the latest binary release (i.e., `*.bin.tar.gz`) from [releases](https://github.com/yuezk/GlobalProtect-openconnect/releases) page. | ||||
|  | ||||
| ## [License](./LICENSE) | ||||
|  | ||||
|   | ||||
| @@ -8,7 +8,7 @@ license.workspace = true | ||||
| tauri-build = { version = "1.5", features = [] } | ||||
|  | ||||
| [dependencies] | ||||
| gpapi = { path = "../../crates/gpapi", features = ["tauri"] } | ||||
| gpapi = { path = "../../crates/gpapi", features = ["tauri", "clap"] } | ||||
| anyhow.workspace = true | ||||
| clap.workspace = true | ||||
| env_logger.workspace = true | ||||
|   | ||||
| @@ -7,6 +7,7 @@ use std::{ | ||||
| use anyhow::bail; | ||||
| use gpapi::{ | ||||
|   auth::SamlAuthData, | ||||
|   gp_params::GpParams, | ||||
|   portal::{prelogin, Prelogin}, | ||||
|   utils::{redact::redact_uri, window::WindowExt}, | ||||
| }; | ||||
| @@ -18,11 +19,13 @@ use tokio_util::sync::CancellationToken; | ||||
| use webkit2gtk::{ | ||||
|   gio::Cancellable, | ||||
|   glib::{GString, TimeSpan}, | ||||
|   LoadEvent, SettingsExt, URIResponse, URIResponseExt, WebContextExt, WebResource, WebResourceExt, | ||||
|   WebView, WebViewExt, WebsiteDataManagerExtManual, WebsiteDataTypes, | ||||
|   LoadEvent, SettingsExt, TLSErrorsPolicy, URIResponse, URIResponseExt, WebContextExt, WebResource, | ||||
|   WebResourceExt, WebView, WebViewExt, WebsiteDataManagerExtManual, WebsiteDataTypes, | ||||
| }; | ||||
|  | ||||
| enum AuthDataError { | ||||
|   /// Failed to load page due to TLS error | ||||
|   TlsError, | ||||
|   /// 1. Found auth data in headers/body but it's invalid | ||||
|   /// 2. Loaded an empty page, failed to load page. etc. | ||||
|   Invalid, | ||||
| @@ -37,6 +40,7 @@ pub(crate) struct AuthWindow<'a> { | ||||
|   server: &'a str, | ||||
|   saml_request: &'a str, | ||||
|   user_agent: &'a str, | ||||
|   gp_params: Option<GpParams>, | ||||
|   clean: bool, | ||||
| } | ||||
|  | ||||
| @@ -47,6 +51,7 @@ impl<'a> AuthWindow<'a> { | ||||
|       server: "", | ||||
|       saml_request: "", | ||||
|       user_agent: "", | ||||
|       gp_params: None, | ||||
|       clean: false, | ||||
|     } | ||||
|   } | ||||
| @@ -66,6 +71,11 @@ impl<'a> AuthWindow<'a> { | ||||
|     self | ||||
|   } | ||||
|  | ||||
|   pub fn gp_params(mut self, gp_params: GpParams) -> Self { | ||||
|     self.gp_params.replace(gp_params); | ||||
|     self | ||||
|   } | ||||
|  | ||||
|   pub fn clean(mut self, clean: bool) -> Self { | ||||
|     self.clean = clean; | ||||
|     self | ||||
| @@ -119,6 +129,12 @@ impl<'a> AuthWindow<'a> { | ||||
|     let saml_request = self.saml_request.to_string(); | ||||
|     let (auth_result_tx, mut auth_result_rx) = mpsc::unbounded_channel::<AuthResult>(); | ||||
|     let raise_window_cancel_token: Arc<RwLock<Option<CancellationToken>>> = Default::default(); | ||||
|     let gp_params = self.gp_params.as_ref().unwrap(); | ||||
|     let tls_err_policy = if gp_params.ignore_tls_errors() { | ||||
|       TLSErrorsPolicy::Ignore | ||||
|     } else { | ||||
|       TLSErrorsPolicy::Fail | ||||
|     }; | ||||
|  | ||||
|     if self.clean { | ||||
|       clear_webview_cookies(window).await?; | ||||
| @@ -128,6 +144,10 @@ impl<'a> AuthWindow<'a> { | ||||
|     window.with_webview(move |wv| { | ||||
|       let wv = wv.inner(); | ||||
|  | ||||
|       if let Some(context) = wv.context() { | ||||
|         context.set_tls_errors_policy(tls_err_policy); | ||||
|       } | ||||
|  | ||||
|       if let Some(settings) = wv.settings() { | ||||
|         let ua = settings.user_agent().unwrap_or("".into()); | ||||
|         info!("Auth window user agent: {}", ua); | ||||
| @@ -168,12 +188,15 @@ impl<'a> AuthWindow<'a> { | ||||
|         } | ||||
|       }); | ||||
|  | ||||
|       wv.connect_load_failed_with_tls_errors(|_wv, uri, cert, err| { | ||||
|       let auth_result_tx_clone = auth_result_tx.clone(); | ||||
|       wv.connect_load_failed_with_tls_errors(move |_wv, uri, cert, err| { | ||||
|         let redacted_uri = redact_uri(uri); | ||||
|         warn!( | ||||
|           "Failed to load uri: {} with error: {}, cert: {}", | ||||
|           redacted_uri, err, cert | ||||
|         ); | ||||
|  | ||||
|         send_auth_result(&auth_result_tx_clone, Err(AuthDataError::TlsError)); | ||||
|         true | ||||
|       }); | ||||
|  | ||||
| @@ -187,12 +210,14 @@ impl<'a> AuthWindow<'a> { | ||||
|     })?; | ||||
|  | ||||
|     let portal = self.server.to_string(); | ||||
|     let user_agent = self.user_agent.to_string(); | ||||
|  | ||||
|     loop { | ||||
|       if let Some(auth_result) = auth_result_rx.recv().await { | ||||
|         match auth_result { | ||||
|           Ok(auth_data) => return Ok(auth_data), | ||||
|           Err(AuthDataError::TlsError) => { | ||||
|             return Err(anyhow::anyhow!("TLS error: certificate verify failed")) | ||||
|           } | ||||
|           Err(AuthDataError::NotFound) => { | ||||
|             info!("No auth data found, it may not be the /SAML20/SP/ACS endpoint"); | ||||
|  | ||||
| @@ -237,7 +262,7 @@ impl<'a> AuthWindow<'a> { | ||||
|               ); | ||||
|             })?; | ||||
|  | ||||
|             let saml_request = portal_prelogin(&portal, &user_agent).await?; | ||||
|             let saml_request = portal_prelogin(&portal, gp_params).await?; | ||||
|             window.with_webview(move |wv| { | ||||
|               let wv = wv.inner(); | ||||
|               load_saml_request(&wv, &saml_request); | ||||
| @@ -258,9 +283,10 @@ fn raise_window(window: &Arc<Window>) { | ||||
|   } | ||||
| } | ||||
|  | ||||
| pub(crate) async fn portal_prelogin(portal: &str, user_agent: &str) -> anyhow::Result<String> { | ||||
| pub(crate) async fn portal_prelogin(portal: &str, gp_params: &GpParams) -> anyhow::Result<String> { | ||||
|   info!("Portal prelogin..."); | ||||
|   match prelogin(portal, user_agent).await? { | ||||
|  | ||||
|   match prelogin(portal, gp_params).await? { | ||||
|     Prelogin::Saml(prelogin) => Ok(prelogin.saml_request().to_string()), | ||||
|     Prelogin::Standard(_) => Err(anyhow::anyhow!("Received non-SAML prelogin response")), | ||||
|   } | ||||
| @@ -397,6 +423,11 @@ fn read_auth_data(main_resource: &WebResource, auth_result_tx: mpsc::UnboundedSe | ||||
|         send_auth_result(&auth_result_tx, auth_result) | ||||
|       }); | ||||
|     } | ||||
|     Err(AuthDataError::TlsError) => { | ||||
|       // NOTE: This is unreachable | ||||
|       info!("TLS error found in headers, trying to read from body..."); | ||||
|       send_auth_result(&auth_result_tx, Err(AuthDataError::TlsError)); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,6 +1,8 @@ | ||||
| use clap::Parser; | ||||
| use gpapi::{ | ||||
|   auth::{SamlAuthData, SamlAuthResult}, | ||||
|   clap::args::Os, | ||||
|   gp_params::{ClientOs, GpParams}, | ||||
|   utils::{normalize_server, openssl}, | ||||
|   GP_USER_AGENT, | ||||
| }; | ||||
| @@ -26,23 +28,35 @@ struct Cli { | ||||
|   saml_request: Option<String>, | ||||
|   #[arg(long, default_value = GP_USER_AGENT)] | ||||
|   user_agent: String, | ||||
|   #[arg(long, default_value = "Linux")] | ||||
|   os: Os, | ||||
|   #[arg(long)] | ||||
|   os_version: Option<String>, | ||||
|   #[arg(long)] | ||||
|   hidpi: bool, | ||||
|   #[arg(long)] | ||||
|   fix_openssl: bool, | ||||
|   #[arg(long)] | ||||
|   ignore_tls_errors: bool, | ||||
|   #[arg(long)] | ||||
|   clean: bool, | ||||
| } | ||||
|  | ||||
| impl Cli { | ||||
|   async fn run(&mut self) -> anyhow::Result<()> { | ||||
|     if self.ignore_tls_errors { | ||||
|       info!("TLS errors will be ignored"); | ||||
|     } | ||||
|  | ||||
|     let mut openssl_conf = self.prepare_env()?; | ||||
|  | ||||
|     self.server = normalize_server(&self.server)?; | ||||
|     let gp_params = self.build_gp_params(); | ||||
|  | ||||
|     // Get the initial SAML request | ||||
|     let saml_request = match self.saml_request { | ||||
|       Some(ref saml_request) => saml_request.clone(), | ||||
|       None => portal_prelogin(&self.server, &self.user_agent).await?, | ||||
|       None => portal_prelogin(&self.server, &gp_params).await?, | ||||
|     }; | ||||
|  | ||||
|     self.saml_request.replace(saml_request); | ||||
| @@ -82,10 +96,22 @@ impl Cli { | ||||
|     Ok(None) | ||||
|   } | ||||
|  | ||||
|   fn build_gp_params(&self) -> GpParams { | ||||
|     let gp_params = GpParams::builder() | ||||
|       .user_agent(&self.user_agent) | ||||
|       .client_os(ClientOs::from(&self.os)) | ||||
|       .os_version(self.os_version.clone()) | ||||
|       .ignore_tls_errors(self.ignore_tls_errors) | ||||
|       .build(); | ||||
|  | ||||
|     gp_params | ||||
|   } | ||||
|  | ||||
|   async fn saml_auth(&self, app_handle: AppHandle) -> anyhow::Result<SamlAuthData> { | ||||
|     let auth_window = AuthWindow::new(app_handle) | ||||
|       .server(&self.server) | ||||
|       .user_agent(&self.user_agent) | ||||
|       .gp_params(self.build_gp_params()) | ||||
|       .saml_request(self.saml_request.as_ref().unwrap()) | ||||
|       .clean(self.clean); | ||||
|  | ||||
|   | ||||
| @@ -6,7 +6,7 @@ edition.workspace = true | ||||
| license.workspace = true | ||||
|  | ||||
| [dependencies] | ||||
| gpapi = { path = "../../crates/gpapi" } | ||||
| gpapi = { path = "../../crates/gpapi", features = ["clap"] } | ||||
| openconnect = { path = "../../crates/openconnect" } | ||||
| anyhow.workspace = true | ||||
| clap.workspace = true | ||||
|   | ||||
| @@ -16,6 +16,11 @@ const VERSION: &str = concat!( | ||||
|   ")" | ||||
| ); | ||||
|  | ||||
| pub(crate) struct SharedArgs { | ||||
|   pub(crate) fix_openssl: bool, | ||||
|   pub(crate) ignore_tls_errors: bool, | ||||
| } | ||||
|  | ||||
| #[derive(Subcommand)] | ||||
| enum CliCommand { | ||||
|   #[command(about = "Connect to a portal server")] | ||||
| @@ -40,6 +45,8 @@ enum CliCommand { | ||||
| {usage-heading} {usage} | ||||
|  | ||||
| {all-args}{after-help} | ||||
|  | ||||
| See 'gpclient help <command>' for more information on a specific command. | ||||
| " | ||||
| )] | ||||
| struct Cli { | ||||
| @@ -51,6 +58,8 @@ struct Cli { | ||||
|     help = "Get around the OpenSSL `unsafe legacy renegotiation` error" | ||||
|   )] | ||||
|   fix_openssl: bool, | ||||
|   #[arg(long, help = "Ignore the TLS errors")] | ||||
|   ignore_tls_errors: bool, | ||||
| } | ||||
|  | ||||
| impl Cli { | ||||
| @@ -67,9 +76,17 @@ impl Cli { | ||||
|     // The temp file will be dropped automatically when the file handle is dropped | ||||
|     // So, declare it here to ensure it's not dropped | ||||
|     let _file = self.fix_openssl()?; | ||||
|     let shared_args = SharedArgs { | ||||
|       fix_openssl: self.fix_openssl, | ||||
|       ignore_tls_errors: self.ignore_tls_errors, | ||||
|     }; | ||||
|  | ||||
|     if self.ignore_tls_errors { | ||||
|       info!("TLS errors will be ignored"); | ||||
|     } | ||||
|  | ||||
|     match &self.command { | ||||
|       CliCommand::Connect(args) => ConnectHandler::new(args, self.fix_openssl).handle().await, | ||||
|       CliCommand::Connect(args) => ConnectHandler::new(args, &shared_args).handle().await, | ||||
|       CliCommand::Disconnect => DisconnectHandler::new().handle(), | ||||
|       CliCommand::LaunchGui(args) => LaunchGuiHandler::new(args).handle().await, | ||||
|     } | ||||
| @@ -89,13 +106,24 @@ pub(crate) async fn run() { | ||||
|   if let Err(err) = cli.run().await { | ||||
|     eprintln!("\nError: {}", err); | ||||
|  | ||||
|     if err.to_string().contains("unsafe legacy renegotiation") && !cli.fix_openssl { | ||||
|     let err = err.to_string(); | ||||
|  | ||||
|     if err.contains("unsafe legacy renegotiation") && !cli.fix_openssl { | ||||
|       eprintln!("\nRe-run it with the `--fix-openssl` option to work around this issue, e.g.:\n"); | ||||
|       // Print the command | ||||
|       let args = std::env::args().collect::<Vec<_>>(); | ||||
|       eprintln!("{} --fix-openssl {}\n", args[0], args[1..].join(" ")); | ||||
|     } | ||||
|  | ||||
|     if err.contains("certificate verify failed") { | ||||
|       eprintln!( | ||||
|         "\nRe-run it with the `--ignore-tls-errors` option to ignore the certificate error, e.g.:\n" | ||||
|       ); | ||||
|       // Print the command | ||||
|       let args = std::env::args().collect::<Vec<_>>(); | ||||
|       eprintln!("{} --ignore-tls-errors {}\n", args[0], args[1..].join(" ")); | ||||
|     } | ||||
|  | ||||
|     std::process::exit(1); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -2,9 +2,10 @@ use std::{fs, sync::Arc}; | ||||
|  | ||||
| use clap::Args; | ||||
| use gpapi::{ | ||||
|   clap::args::Os, | ||||
|   credential::{Credential, PasswordCredential}, | ||||
|   gateway::gateway_login, | ||||
|   gp_params::GpParams, | ||||
|   gp_params::{ClientOs, GpParams}, | ||||
|   portal::{prelogin, retrieve_config, Prelogin}, | ||||
|   process::auth_launcher::SamlAuthLauncher, | ||||
|   utils::{self, shutdown_signal}, | ||||
| @@ -14,7 +15,7 @@ use inquire::{Password, PasswordDisplayMode, Select, Text}; | ||||
| use log::info; | ||||
| use openconnect::Vpn; | ||||
|  | ||||
| use crate::GP_CLIENT_LOCK_FILE; | ||||
| use crate::{cli::SharedArgs, GP_CLIENT_LOCK_FILE}; | ||||
|  | ||||
| #[derive(Args)] | ||||
| pub(crate) struct ConnectArgs { | ||||
| @@ -36,20 +37,38 @@ pub(crate) struct ConnectArgs { | ||||
|   script: Option<String>, | ||||
|   #[arg(long, default_value = GP_USER_AGENT, help = "The user agent to use")] | ||||
|   user_agent: String, | ||||
|   #[arg(long, default_value = "Linux")] | ||||
|   os: Os, | ||||
|   #[arg(long)] | ||||
|   os_version: Option<String>, | ||||
|   #[arg(long, help = "The HiDPI mode, useful for high resolution screens")] | ||||
|   hidpi: bool, | ||||
|   #[arg(long, help = "Do not reuse the remembered authentication cookie")] | ||||
|   clean: bool, | ||||
| } | ||||
|  | ||||
| impl ConnectArgs { | ||||
|   fn os_version(&self) -> String { | ||||
|     if let Some(os_version) = &self.os_version { | ||||
|       return os_version.to_owned(); | ||||
|     } | ||||
|  | ||||
|     match self.os { | ||||
|       Os::Linux => format!("Linux {}", whoami::distro()), | ||||
|       Os::Windows => String::from("Microsoft Windows 11 Pro , 64-bit"), | ||||
|       Os::Mac => String::from("Apple Mac OS X 13.4.0"), | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| pub(crate) struct ConnectHandler<'a> { | ||||
|   args: &'a ConnectArgs, | ||||
|   fix_openssl: bool, | ||||
|   shared_args: &'a SharedArgs, | ||||
| } | ||||
|  | ||||
| impl<'a> ConnectHandler<'a> { | ||||
|   pub(crate) fn new(args: &'a ConnectArgs, fix_openssl: bool) -> Self { | ||||
|     Self { args, fix_openssl } | ||||
|   pub(crate) fn new(args: &'a ConnectArgs, shared_args: &'a SharedArgs) -> Self { | ||||
|     Self { args, shared_args } | ||||
|   } | ||||
|  | ||||
|   pub(crate) async fn handle(&self) -> anyhow::Result<()> { | ||||
| @@ -57,9 +76,12 @@ impl<'a> ConnectHandler<'a> { | ||||
|  | ||||
|     let gp_params = GpParams::builder() | ||||
|       .user_agent(&self.args.user_agent) | ||||
|       .client_os(ClientOs::from(&self.args.os)) | ||||
|       .os_version(self.args.os_version()) | ||||
|       .ignore_tls_errors(self.shared_args.ignore_tls_errors) | ||||
|       .build(); | ||||
|  | ||||
|     let prelogin = prelogin(&portal, &self.args.user_agent).await?; | ||||
|     let prelogin = prelogin(&portal, &gp_params).await?; | ||||
|     let portal_credential = self.obtain_portal_credential(&prelogin).await?; | ||||
|     let mut portal_config = retrieve_config(&portal, &portal_credential, &gp_params).await?; | ||||
|  | ||||
| @@ -114,10 +136,13 @@ impl<'a> ConnectHandler<'a> { | ||||
|     match prelogin { | ||||
|       Prelogin::Saml(prelogin) => { | ||||
|         SamlAuthLauncher::new(&self.args.server) | ||||
|           .user_agent(&self.args.user_agent) | ||||
|           .saml_request(prelogin.saml_request()) | ||||
|           .user_agent(&self.args.user_agent) | ||||
|           .os(self.args.os.as_str()) | ||||
|           .os_version(Some(&self.args.os_version())) | ||||
|           .hidpi(self.args.hidpi) | ||||
|           .fix_openssl(self.fix_openssl) | ||||
|           .fix_openssl(self.shared_args.fix_openssl) | ||||
|           .ignore_tls_errors(self.shared_args.ignore_tls_errors) | ||||
|           .clean(self.args.clean) | ||||
|           .launch() | ||||
|           .await | ||||
|   | ||||
| @@ -27,6 +27,8 @@ dotenvy_macro.workspace = true | ||||
| uzers.workspace = true | ||||
|  | ||||
| tauri = { workspace = true, optional = true } | ||||
| clap = { workspace = true, optional = true } | ||||
|  | ||||
| [features] | ||||
| tauri = ["dep:tauri"] | ||||
| clap = ["dep:clap"] | ||||
|   | ||||
							
								
								
									
										64
									
								
								crates/gpapi/src/clap/args.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								crates/gpapi/src/clap/args.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,64 @@ | ||||
| use clap::{builder::PossibleValue, ValueEnum}; | ||||
|  | ||||
| use crate::gp_params::ClientOs; | ||||
|  | ||||
| #[derive(Debug, Clone)] | ||||
| pub enum Os { | ||||
|   Linux, | ||||
|   Windows, | ||||
|   Mac, | ||||
| } | ||||
|  | ||||
| impl Os { | ||||
|   pub fn as_str(&self) -> &'static str { | ||||
|     match self { | ||||
|       Os::Linux => "Linux", | ||||
|       Os::Windows => "Windows", | ||||
|       Os::Mac => "Mac", | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| impl From<&str> for Os { | ||||
|   fn from(os: &str) -> Self { | ||||
|     match os.to_lowercase().as_str() { | ||||
|       "linux" => Os::Linux, | ||||
|       "windows" => Os::Windows, | ||||
|       "mac" => Os::Mac, | ||||
|       _ => Os::Linux, | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| impl From<&Os> for ClientOs { | ||||
|   fn from(value: &Os) -> Self { | ||||
|     match value { | ||||
|       Os::Linux => ClientOs::Linux, | ||||
|       Os::Windows => ClientOs::Windows, | ||||
|       Os::Mac => ClientOs::Mac, | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| impl ValueEnum for Os { | ||||
|   fn value_variants<'a>() -> &'a [Self] { | ||||
|     &[Os::Linux, Os::Windows, Os::Mac] | ||||
|   } | ||||
|  | ||||
|   fn to_possible_value(&self) -> Option<clap::builder::PossibleValue> { | ||||
|     match self { | ||||
|       Os::Linux => Some(PossibleValue::new("Linux")), | ||||
|       Os::Windows => Some(PossibleValue::new("Windows")), | ||||
|       Os::Mac => Some(PossibleValue::new("Mac")), | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   fn from_str(input: &str, _: bool) -> Result<Self, String> { | ||||
|     match input.to_lowercase().as_str() { | ||||
|       "linux" => Ok(Os::Linux), | ||||
|       "windows" => Ok(Os::Windows), | ||||
|       "mac" => Ok(Os::Mac), | ||||
|       _ => Err(format!("Invalid OS: {}", input)), | ||||
|     } | ||||
|   } | ||||
| } | ||||
							
								
								
									
										1
									
								
								crates/gpapi/src/clap/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								crates/gpapi/src/clap/mod.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| pub mod args; | ||||
| @@ -164,31 +164,34 @@ impl Credential { | ||||
|     let mut params = HashMap::new(); | ||||
|     params.insert("user", self.username()); | ||||
|  | ||||
|     match self { | ||||
|       Credential::Password(cred) => { | ||||
|         params.insert("passwd", cred.password()); | ||||
|       } | ||||
|       Credential::PreloginCookie(cred) => { | ||||
|         params.insert("prelogin-cookie", cred.prelogin_cookie()); | ||||
|       } | ||||
|       Credential::AuthCookie(cred) => { | ||||
|         params.insert("portal-userauthcookie", cred.user_auth_cookie()); | ||||
|     let (passwd, prelogin_cookie, portal_userauthcookie, portal_prelogonuserauthcookie) = match self | ||||
|     { | ||||
|       Credential::Password(cred) => (Some(cred.password()), None, None, None), | ||||
|       Credential::PreloginCookie(cred) => (None, Some(cred.prelogin_cookie()), None, None), | ||||
|       Credential::AuthCookie(cred) => ( | ||||
|         None, | ||||
|         None, | ||||
|         Some(cred.user_auth_cookie()), | ||||
|         Some(cred.prelogon_user_auth_cookie()), | ||||
|       ), | ||||
|       Credential::CachedCredential(cred) => ( | ||||
|         cred.password(), | ||||
|         None, | ||||
|         Some(cred.auth_cookie.user_auth_cookie()), | ||||
|         Some(cred.auth_cookie.prelogon_user_auth_cookie()), | ||||
|       ), | ||||
|     }; | ||||
|  | ||||
|     params.insert("passwd", passwd.unwrap_or_default()); | ||||
|     params.insert("prelogin-cookie", prelogin_cookie.unwrap_or_default()); | ||||
|     params.insert( | ||||
|       "portal-userauthcookie", | ||||
|       portal_userauthcookie.unwrap_or_default(), | ||||
|     ); | ||||
|     params.insert( | ||||
|       "portal-prelogonuserauthcookie", | ||||
|           cred.prelogon_user_auth_cookie(), | ||||
|       portal_prelogonuserauthcookie.unwrap_or_default(), | ||||
|     ); | ||||
|       } | ||||
|       Credential::CachedCredential(cred) => { | ||||
|         if let Some(password) = cred.password() { | ||||
|           params.insert("passwd", password); | ||||
|         } | ||||
|         params.insert("portal-userauthcookie", cred.auth_cookie.user_auth_cookie()); | ||||
|         params.insert( | ||||
|           "portal-prelogonuserauthcookie", | ||||
|           cred.auth_cookie.prelogon_user_auth_cookie(), | ||||
|         ); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     params | ||||
|   } | ||||
|   | ||||
| @@ -23,14 +23,8 @@ pub async fn gateway_login( | ||||
|  | ||||
|   info!("Gateway login, user_agent: {}", gp_params.user_agent()); | ||||
|  | ||||
|   let res_xml = client | ||||
|     .post(&login_url) | ||||
|     .form(¶ms) | ||||
|     .send() | ||||
|     .await? | ||||
|     .error_for_status()? | ||||
|     .text() | ||||
|     .await?; | ||||
|   let res = client.post(&login_url).form(¶ms).send().await?; | ||||
|   let res_xml = res.error_for_status()?.text().await?; | ||||
|  | ||||
|   let doc = Document::parse(&res_xml)?; | ||||
|  | ||||
|   | ||||
| @@ -7,23 +7,32 @@ use crate::GP_USER_AGENT; | ||||
|  | ||||
| #[derive(Debug, Serialize, Deserialize, Clone, Type, Default)] | ||||
| pub enum ClientOs { | ||||
|   Linux, | ||||
|   #[default] | ||||
|   Linux, | ||||
|   Windows, | ||||
|   Mac, | ||||
| } | ||||
|  | ||||
| impl From<&ClientOs> for &str { | ||||
|   fn from(os: &ClientOs) -> Self { | ||||
| impl From<&str> for ClientOs { | ||||
|   fn from(os: &str) -> Self { | ||||
|     match os { | ||||
|       ClientOs::Linux => "Linux", | ||||
|       ClientOs::Windows => "Windows", | ||||
|       ClientOs::Mac => "Mac", | ||||
|       "Linux" => ClientOs::Linux, | ||||
|       "Windows" => ClientOs::Windows, | ||||
|       "Mac" => ClientOs::Mac, | ||||
|       _ => ClientOs::Linux, | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| impl ClientOs { | ||||
|   pub fn as_str(&self) -> &str { | ||||
|     match self { | ||||
|       ClientOs::Linux => "Linux", | ||||
|       ClientOs::Windows => "Windows", | ||||
|       ClientOs::Mac => "Mac", | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   pub fn to_openconnect_os(&self) -> &str { | ||||
|     match self { | ||||
|       ClientOs::Linux => "linux", | ||||
| @@ -39,7 +48,8 @@ pub struct GpParams { | ||||
|   client_os: ClientOs, | ||||
|   os_version: Option<String>, | ||||
|   client_version: Option<String>, | ||||
|   computer: Option<String>, | ||||
|   computer: String, | ||||
|   ignore_tls_errors: bool, | ||||
| } | ||||
|  | ||||
| impl GpParams { | ||||
| @@ -52,15 +62,16 @@ impl GpParams { | ||||
|   } | ||||
|  | ||||
|   pub(crate) fn computer(&self) -> &str { | ||||
|     match self.computer { | ||||
|       Some(ref computer) => computer, | ||||
|       None => (&self.client_os).into() | ||||
|     &self.computer | ||||
|   } | ||||
|  | ||||
|   pub fn ignore_tls_errors(&self) -> bool { | ||||
|     self.ignore_tls_errors | ||||
|   } | ||||
|  | ||||
|   pub(crate) fn to_params(&self) -> HashMap<&str, &str> { | ||||
|     let mut params: HashMap<&str, &str> = HashMap::new(); | ||||
|     let client_os: &str = (&self.client_os).into(); | ||||
|     let client_os = self.client_os.as_str(); | ||||
|  | ||||
|     // Common params | ||||
|     params.insert("prot", "https:"); | ||||
| @@ -70,14 +81,8 @@ impl GpParams { | ||||
|     params.insert("ipv6-support", "yes"); | ||||
|     params.insert("inputStr", ""); | ||||
|     params.insert("clientVer", "4100"); | ||||
|  | ||||
|     params.insert("clientos", client_os); | ||||
|  | ||||
|     if let Some(computer) = &self.computer { | ||||
|       params.insert("computer", computer); | ||||
|     } else { | ||||
|       params.insert("computer", client_os); | ||||
|     } | ||||
|     params.insert("computer", &self.computer); | ||||
|  | ||||
|     if let Some(os_version) = &self.os_version { | ||||
|       params.insert("os-version", os_version); | ||||
| @@ -96,7 +101,8 @@ pub struct GpParamsBuilder { | ||||
|   client_os: ClientOs, | ||||
|   os_version: Option<String>, | ||||
|   client_version: Option<String>, | ||||
|   computer: Option<String>, | ||||
|   computer: String, | ||||
|   ignore_tls_errors: bool, | ||||
| } | ||||
|  | ||||
| impl GpParamsBuilder { | ||||
| @@ -106,7 +112,8 @@ impl GpParamsBuilder { | ||||
|       client_os: ClientOs::Linux, | ||||
|       os_version: Default::default(), | ||||
|       client_version: Default::default(), | ||||
|       computer: Default::default(), | ||||
|       computer: whoami::hostname(), | ||||
|       ignore_tls_errors: false, | ||||
|     } | ||||
|   } | ||||
|  | ||||
| @@ -120,18 +127,23 @@ impl GpParamsBuilder { | ||||
|     self | ||||
|   } | ||||
|  | ||||
|   pub fn os_version(&mut self, os_version: &str) -> &mut Self { | ||||
|     self.os_version = Some(os_version.to_string()); | ||||
|   pub fn os_version<T: Into<Option<String>>>(&mut self, os_version: T) -> &mut Self { | ||||
|     self.os_version = os_version.into(); | ||||
|     self | ||||
|   } | ||||
|  | ||||
|   pub fn client_version(&mut self, client_version: &str) -> &mut Self { | ||||
|     self.client_version = Some(client_version.to_string()); | ||||
|   pub fn client_version<T: Into<Option<String>>>(&mut self, client_version: T) -> &mut Self { | ||||
|     self.client_version = client_version.into(); | ||||
|     self | ||||
|   } | ||||
|  | ||||
|   pub fn computer(&mut self, computer: &str) -> &mut Self { | ||||
|     self.computer = Some(computer.to_string()); | ||||
|     self.computer = computer.to_string(); | ||||
|     self | ||||
|   } | ||||
|  | ||||
|   pub fn ignore_tls_errors(&mut self, ignore_tls_errors: bool) -> &mut Self { | ||||
|     self.ignore_tls_errors = ignore_tls_errors; | ||||
|     self | ||||
|   } | ||||
|  | ||||
| @@ -142,6 +154,7 @@ impl GpParamsBuilder { | ||||
|       os_version: self.os_version.clone(), | ||||
|       client_version: self.client_version.clone(), | ||||
|       computer: self.computer.clone(), | ||||
|       ignore_tls_errors: self.ignore_tls_errors, | ||||
|     } | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -7,12 +7,17 @@ pub mod process; | ||||
| pub mod service; | ||||
| pub mod utils; | ||||
|  | ||||
| #[cfg(feature = "clap")] | ||||
| pub mod clap; | ||||
|  | ||||
| #[cfg(debug_assertions)] | ||||
| 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"; | ||||
|  | ||||
| #[cfg(not(debug_assertions))] | ||||
| pub const GP_CLIENT_BINARY: &str = "/usr/bin/gpclient"; | ||||
| #[cfg(not(debug_assertions))] | ||||
| pub const GP_SERVICE_BINARY: &str = "/usr/bin/gpservice"; | ||||
| #[cfg(not(debug_assertions))] | ||||
| @@ -20,6 +25,8 @@ pub const GP_GUI_BINARY: &str = "/usr/bin/gpgui"; | ||||
| #[cfg(not(debug_assertions))] | ||||
| pub(crate) const GP_AUTH_BINARY: &str = "/usr/bin/gpauth"; | ||||
|  | ||||
| #[cfg(debug_assertions)] | ||||
| pub const GP_CLIENT_BINARY: &str = dotenvy_macro::dotenv!("GP_CLIENT_BINARY"); | ||||
| #[cfg(debug_assertions)] | ||||
| pub const GP_SERVICE_BINARY: &str = dotenvy_macro::dotenv!("GP_SERVICE_BINARY"); | ||||
| #[cfg(debug_assertions)] | ||||
|   | ||||
| @@ -132,14 +132,8 @@ pub async fn retrieve_config( | ||||
|  | ||||
|   info!("Portal config, user_agent: {}", gp_params.user_agent()); | ||||
|  | ||||
|   let res_xml = client | ||||
|     .post(&url) | ||||
|     .form(¶ms) | ||||
|     .send() | ||||
|     .await? | ||||
|     .error_for_status()? | ||||
|     .text() | ||||
|     .await?; | ||||
|   let res = client.post(&url).form(¶ms).send().await?; | ||||
|   let res_xml = res.error_for_status()?.text().await?; | ||||
|  | ||||
|   ensure!(!res_xml.is_empty(), PortalConfigError::EmptyResponse); | ||||
|  | ||||
|   | ||||
| @@ -5,7 +5,21 @@ use roxmltree::Document; | ||||
| use serde::Serialize; | ||||
| use specta::Type; | ||||
|  | ||||
| use crate::utils::{base64, normalize_server, xml}; | ||||
| use crate::{ | ||||
|   gp_params::GpParams, | ||||
|   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")] | ||||
| @@ -67,20 +81,33 @@ impl Prelogin { | ||||
|   } | ||||
| } | ||||
|  | ||||
| pub async fn prelogin(portal: &str, user_agent: &str) -> anyhow::Result<Prelogin> { | ||||
| 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); | ||||
|  | ||||
|   let portal = normalize_server(portal)?; | ||||
|   let prelogin_url = format!("{}/global-protect/prelogin.esp", portal); | ||||
|   let client = Client::builder().user_agent(user_agent).build()?; | ||||
|   let prelogin_url = format!( | ||||
|     "{}/global-protect/prelogin.esp?kerberos-support=yes", | ||||
|     portal | ||||
|   ); | ||||
|   let mut params = gp_params.to_params(); | ||||
|   params.insert("tmp", "tmp"); | ||||
|   params.insert("default-browser", "0"); | ||||
|   params.insert("cas-support", "yes"); | ||||
|  | ||||
|   let res_xml = client | ||||
|     .get(&prelogin_url) | ||||
|     .send() | ||||
|     .await? | ||||
|     .error_for_status()? | ||||
|     .text() | ||||
|     .await?; | ||||
|   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 res_xml = res.error_for_status()?.text().await?; | ||||
|  | ||||
|   trace!("Prelogin response: {}", res_xml); | ||||
|   let doc = Document::parse(&res_xml)?; | ||||
|   | ||||
| @@ -8,10 +8,13 @@ use super::command_traits::CommandExt; | ||||
|  | ||||
| pub struct SamlAuthLauncher<'a> { | ||||
|   server: &'a str, | ||||
|   user_agent: Option<&'a str>, | ||||
|   saml_request: Option<&'a str>, | ||||
|   user_agent: Option<&'a str>, | ||||
|   os: Option<&'a str>, | ||||
|   os_version: Option<&'a str>, | ||||
|   hidpi: bool, | ||||
|   fix_openssl: bool, | ||||
|   ignore_tls_errors: bool, | ||||
|   clean: bool, | ||||
| } | ||||
|  | ||||
| @@ -19,21 +22,34 @@ impl<'a> SamlAuthLauncher<'a> { | ||||
|   pub fn new(server: &'a str) -> Self { | ||||
|     Self { | ||||
|       server, | ||||
|       user_agent: None, | ||||
|       saml_request: None, | ||||
|       user_agent: None, | ||||
|       os: None, | ||||
|       os_version: None, | ||||
|       hidpi: false, | ||||
|       fix_openssl: false, | ||||
|       ignore_tls_errors: false, | ||||
|       clean: false, | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   pub fn saml_request(mut self, saml_request: &'a str) -> Self { | ||||
|     self.saml_request = Some(saml_request); | ||||
|     self | ||||
|   } | ||||
|  | ||||
|   pub fn user_agent(mut self, user_agent: &'a str) -> Self { | ||||
|     self.user_agent = Some(user_agent); | ||||
|     self | ||||
|   } | ||||
|  | ||||
|   pub fn saml_request(mut self, saml_request: &'a str) -> Self { | ||||
|     self.saml_request = Some(saml_request); | ||||
|   pub fn os(mut self, os: &'a str) -> Self { | ||||
|     self.os = Some(os); | ||||
|     self | ||||
|   } | ||||
|  | ||||
|   pub fn os_version(mut self, os_version: Option<&'a str>) -> Self { | ||||
|     self.os_version = os_version; | ||||
|     self | ||||
|   } | ||||
|  | ||||
| @@ -47,6 +63,11 @@ impl<'a> SamlAuthLauncher<'a> { | ||||
|     self | ||||
|   } | ||||
|  | ||||
|   pub fn ignore_tls_errors(mut self, ignore_tls_errors: bool) -> Self { | ||||
|     self.ignore_tls_errors = ignore_tls_errors; | ||||
|     self | ||||
|   } | ||||
|  | ||||
|   pub fn clean(mut self, clean: bool) -> Self { | ||||
|     self.clean = clean; | ||||
|     self | ||||
| @@ -57,20 +78,32 @@ impl<'a> SamlAuthLauncher<'a> { | ||||
|     let mut auth_cmd = Command::new(GP_AUTH_BINARY); | ||||
|     auth_cmd.arg(self.server); | ||||
|  | ||||
|     if let Some(saml_request) = self.saml_request { | ||||
|       auth_cmd.arg("--saml-request").arg(saml_request); | ||||
|     } | ||||
|  | ||||
|     if let Some(user_agent) = self.user_agent { | ||||
|       auth_cmd.arg("--user-agent").arg(user_agent); | ||||
|     } | ||||
|  | ||||
|     if let Some(saml_request) = self.saml_request { | ||||
|       auth_cmd.arg("--saml-request").arg(saml_request); | ||||
|     if let Some(os) = self.os { | ||||
|       auth_cmd.arg("--os").arg(os); | ||||
|     } | ||||
|  | ||||
|     if let Some(os_version) = self.os_version { | ||||
|       auth_cmd.arg("--os-version").arg(os_version); | ||||
|     } | ||||
|  | ||||
|     if self.hidpi { | ||||
|       auth_cmd.arg("--hidpi"); | ||||
|     } | ||||
|  | ||||
|     if self.fix_openssl { | ||||
|       auth_cmd.arg("--fix-openssl"); | ||||
|     } | ||||
|  | ||||
|     if self.hidpi { | ||||
|       auth_cmd.arg("--hidpi"); | ||||
|     if self.ignore_tls_errors { | ||||
|       auth_cmd.arg("--ignore-tls-errors"); | ||||
|     } | ||||
|  | ||||
|     if self.clean { | ||||
|   | ||||
| @@ -27,7 +27,6 @@ pub fn raise_window(win: &Window) -> anyhow::Result<()> { | ||||
|     } | ||||
|     let title = win.title()?; | ||||
|     tokio::spawn(async move { | ||||
|       info!("Raising window: {}", title); | ||||
|       if let Err(err) = wmctrl_raise_window(&title).await { | ||||
|         warn!("Failed to raise window: {}", err); | ||||
|       } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user