mirror of
				https://github.com/yuezk/GlobalProtect-openconnect.git
				synced 2025-05-20 07:26:58 -04:00 
			
		
		
		
	Compare commits
	
		
			4 Commits
		
	
	
		
			v2.4.3
			...
			fix_multi_
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | c70c7ee5b9 | ||
|  | fe3d3df662 | ||
|  | 2f90b73683 | ||
|  | 5186e80c6f | 
							
								
								
									
										63
									
								
								.github/workflows/build.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										63
									
								
								.github/workflows/build.yaml
									
									
									
									
										vendored
									
									
								
							| @@ -44,7 +44,8 @@ jobs: | ||||
|       with: | ||||
|         version: 9 | ||||
|     - name: Prepare workspace | ||||
|       run: rm -rf source && mkdir source | ||||
|       run: rm -rf source && mkdir -p source/artifacts | ||||
|  | ||||
|     - name: Checkout GlobalProtect-openconnect | ||||
|       uses: actions/checkout@v4 | ||||
|       with: | ||||
| @@ -52,6 +53,7 @@ jobs: | ||||
|         repository: yuezk/GlobalProtect-openconnect | ||||
|         ref: ${{ github.ref }} | ||||
|         path: source/gp | ||||
|  | ||||
|     - name: Create tarball | ||||
|       run: | | ||||
|         cd source/gp | ||||
| @@ -60,13 +62,69 @@ jobs: | ||||
|           touch SNAPSHOT | ||||
|         fi | ||||
|         make tarball | ||||
|  | ||||
|         mv -v .build/tarball/*.tar.gz ../artifacts/ | ||||
|  | ||||
|     - name: Generate RPM spec file | ||||
|       env: | ||||
|         RELEASE_TAG: ${{ github.ref == 'refs/heads/dev' && 'snapshot' || github.ref_name }} | ||||
|       run: | | ||||
|         cd source/gp | ||||
|  | ||||
|         make init-rpm \ | ||||
|           REVISION='1%{?dist}' \ | ||||
|           RPM_SOURCE=https://github.com/yuezk/GlobalProtect-openconnect/releases/download/${RELEASE_TAG}/%{name}-%{version}.tar.gz | ||||
|  | ||||
|         mv -v .build/rpm/*.spec ../artifacts/ | ||||
|  | ||||
|     - name: Upload tarball | ||||
|       uses: actions/upload-artifact@v4 | ||||
|       with: | ||||
|         name: artifact-source | ||||
|         if-no-files-found: error | ||||
|         path: | | ||||
|           source/gp/.build/tarball/*.tar.gz | ||||
|           source/artifacts/* | ||||
|  | ||||
|   tarball-offline: | ||||
|     if: ${{ github.ref == 'refs/heads/dev' || startsWith(github.ref, 'refs/tags/') }} | ||||
|     runs-on: ubuntu-latest | ||||
|     needs: | ||||
|     - tarball | ||||
|     steps: | ||||
|     - uses: pnpm/action-setup@v4 | ||||
|       with: | ||||
|         version: 9 | ||||
|  | ||||
|     - name: Prepare workspace | ||||
|       run: rm -rf source-offline && mkdir source-offline | ||||
|  | ||||
|     - name: Download tarball | ||||
|       uses: actions/download-artifact@v4 | ||||
|       with: | ||||
|         name: artifact-source | ||||
|         path: source-offline | ||||
|  | ||||
|     - name: Create offline tarball | ||||
|       run: | | ||||
|         cd source-offline | ||||
|  | ||||
|         offline_tarball=$(basename *.tar.gz .tar.gz).offline.tar.gz | ||||
|  | ||||
|         # Extract the tarball | ||||
|         tar -xzf *.tar.gz | ||||
|  | ||||
|         cd */ | ||||
|         make tarball OFFLINE=1 | ||||
|  | ||||
|         # Rename the tarball to .offline.tar.gz | ||||
|         mv -v .build/tarball/*.tar.gz ../$offline_tarball | ||||
|  | ||||
|     - name: Upload offline tarball | ||||
|       uses: actions/upload-artifact@v4 | ||||
|       with: | ||||
|         path: source-offline/*.offline.tar.gz | ||||
|         name: artifact-source-offline | ||||
|         if-no-files-found: error | ||||
|  | ||||
|   build-gp: | ||||
|     needs: | ||||
| @@ -168,6 +226,7 @@ jobs: | ||||
|     runs-on: ubuntu-latest | ||||
|     needs: | ||||
|       - tarball | ||||
|       - tarball-offline | ||||
|       - build-gp | ||||
|       - build-gpgui | ||||
|  | ||||
|   | ||||
							
								
								
									
										33
									
								
								.github/workflows/publish.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										33
									
								
								.github/workflows/publish.yaml
									
									
									
									
										vendored
									
									
								
							| @@ -52,22 +52,26 @@ jobs: | ||||
|         version: 9 | ||||
|     - name: Prepare workspace | ||||
|       run: rm -rf publish-ppa && mkdir publish-ppa | ||||
|     - name: Download ${{ inputs.tag }} source code | ||||
|       uses: robinraju/release-downloader@v1.9 | ||||
|       with: | ||||
|         token: ${{ secrets.GH_PAT }} | ||||
|         tag: ${{ inputs.tag }} | ||||
|         fileName: globalprotect-openconnect-*.tar.gz | ||||
|         tarBall: false | ||||
|         zipBall: false | ||||
|         out-file-path: publish-ppa | ||||
|     - name: Make the offline tarball | ||||
|     - name: Download ${{ inputs.tag }} offline source code | ||||
|       env: | ||||
|         GH_TOKEN: ${{ secrets.GH_PAT }} | ||||
|       run: | | ||||
|         gh -R yuezk/GlobalProtect-openconnect \ | ||||
|           release download ${{ inputs.tag }} \ | ||||
|           --pattern '*.offline.tar.gz' \ | ||||
|           --dir publish-ppa | ||||
|     - name: Patch the source code | ||||
|       run: | | ||||
|         cd publish-ppa | ||||
|         tar -xf globalprotect-openconnect-*.tar.gz | ||||
|         cd globalprotect-openconnect-*/ | ||||
|  | ||||
|         make tarball OFFLINE=1 | ||||
|         # Rename the source tarball without the offline suffix | ||||
|         mv -v *.tar.gz $(basename *.tar.gz .offline.tar.gz).tar.gz | ||||
|  | ||||
|         # Extract the source tarball | ||||
|         tar -xzf *.tar.gz | ||||
|  | ||||
|         # Prepare the debian directory with custom files | ||||
|         cd globalprotect-openconnect-*/ | ||||
|  | ||||
|         # Prepare the debian directory with custom files | ||||
|         mkdir -p .build/debian | ||||
| @@ -78,7 +82,6 @@ jobs: | ||||
|         cp -v packaging/deb/postrm .build/debian/postrm | ||||
|  | ||||
|         sed -i "s/@RUST@/cargo-1.80/g" .build/debian/control | ||||
|  | ||||
|         sed -i "s/@OFFLINE@/1/g" .build/debian/rules | ||||
|         sed -i "s/@BUILD_GUI@/1/g" .build/debian/rules | ||||
|         sed -i "s/@RUST_VERSION@/1.80/g" .build/debian/rules | ||||
| @@ -89,7 +92,7 @@ jobs: | ||||
|         repository: "yuezk/globalprotect-openconnect" | ||||
|         gpg_private_key: ${{ secrets.PPA_GPG_PRIVATE_KEY }} | ||||
|         gpg_passphrase: ${{ secrets.PPA_GPG_PASSPHRASE }} | ||||
|         tarball: publish-ppa/globalprotect-openconnect-*/.build/tarball/*.tar.gz | ||||
|         tarball: publish-ppa/globalprotect-openconnect-*.tar.gz | ||||
|         debian_dir: publish-ppa/globalprotect-openconnect-*/.build/debian | ||||
|         deb_email: "k3vinyue@gmail.com" | ||||
|         deb_fullname: "Kevin Yue" | ||||
|   | ||||
							
								
								
									
										17
									
								
								.github/workflows/release.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										17
									
								
								.github/workflows/release.yaml
									
									
									
									
										vendored
									
									
								
							| @@ -96,15 +96,16 @@ jobs: | ||||
|     steps: | ||||
|     - name: Prepare workspace | ||||
|       run: rm -rf build-${{ matrix.package }} && mkdir -p build-${{ matrix.package }} | ||||
|  | ||||
|     - name: Download ${{ inputs.tag }} source code | ||||
|       uses: robinraju/release-downloader@v1.9 | ||||
|       with: | ||||
|         token: ${{ secrets.GH_PAT }} | ||||
|         tag: ${{ inputs.tag }} | ||||
|         fileName: globalprotect-openconnect-*.tar.gz | ||||
|         tarBall: false | ||||
|         zipBall: false | ||||
|         out-file-path: build-${{ matrix.package }} | ||||
|       env: | ||||
|         GH_TOKEN: ${{ secrets.GH_PAT }} | ||||
|       run: | | ||||
|         gh -R yuezk/GlobalProtect-openconnect \ | ||||
|           release download ${{ inputs.tag }} \ | ||||
|           --pattern '*[^offline].tar.gz' \ | ||||
|           --dir build-${{ matrix.package }} | ||||
|  | ||||
|     - name: Docker Login | ||||
|       run: echo ${{ secrets.DOCKER_HUB_TOKEN }} | docker login -u ${{ secrets.DOCKER_HUB_USERNAME }} --password-stdin | ||||
|     - name: Build ${{ matrix.package }} package in Docker | ||||
|   | ||||
							
								
								
									
										1
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| @@ -1599,6 +1599,7 @@ dependencies = [ | ||||
|  "clap-verbosity-flag", | ||||
|  "dns-lookup", | ||||
|  "env_logger", | ||||
|  "gtk", | ||||
|  "log", | ||||
|  "log-reload", | ||||
|  "md5", | ||||
|   | ||||
							
								
								
									
										3
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										3
									
								
								Makefile
									
									
									
									
									
								
							| @@ -8,6 +8,8 @@ RUST_VERSION = 1.80 | ||||
|  | ||||
| VERSION = $(shell $(CARGO) metadata --no-deps --format-version 1 | jq -r '.packages[0].version') | ||||
| REVISION ?= 1 | ||||
| RPM_SOURCE ?= %{name}.tar.gz | ||||
|  | ||||
| PPA_REVISION ?= 1 | ||||
| PKG_NAME = globalprotect-openconnect | ||||
| PKG = $(PKG_NAME)-$(VERSION) | ||||
| @@ -234,6 +236,7 @@ init-rpm: clean-rpm | ||||
|  | ||||
| 	sed -i "s/@VERSION@/$(VERSION)/g" .build/rpm/globalprotect-openconnect.spec | ||||
| 	sed -i "s/@REVISION@/$(REVISION)/g" .build/rpm/globalprotect-openconnect.spec | ||||
| 	sed -i "s|@SOURCE@|$(RPM_SOURCE)|g" .build/rpm/globalprotect-openconnect.spec | ||||
| 	sed -i "s/@OFFLINE@/$(OFFLINE)/g" .build/rpm/globalprotect-openconnect.spec | ||||
| 	sed -i "s/@DATE@/$(shell LC_ALL=en.US date "+%a %b %d %Y")/g" .build/rpm/globalprotect-openconnect.spec | ||||
|  | ||||
|   | ||||
| @@ -70,7 +70,7 @@ The GUI version is also available after you installed it. You can launch it from | ||||
|  | ||||
| ### Debian/Ubuntu based distributions | ||||
|  | ||||
| #### Install from PPA (Ubuntu > 18.04) | ||||
| #### Install from PPA | ||||
|  | ||||
| ``` | ||||
| sudo add-apt-repository ppa:yuezk/globalprotect-openconnect | ||||
| @@ -81,10 +81,6 @@ sudo apt-get install globalprotect-openconnect | ||||
| > | ||||
| > 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`. | ||||
|  | ||||
| #### **Ubuntu 18.04** | ||||
|  | ||||
| The latest package is not available in the PPA, but you still needs to add the `ppa:yuezk/globalprotect-openconnect` repo beforehand to use the required `openconnect` package. Then you can follow the [Install from deb package](#install-from-deb-package) section to install the latest package. | ||||
|  | ||||
| #### Install from deb package | ||||
|  | ||||
| Download the latest deb package from [releases](https://github.com/yuezk/GlobalProtect-openconnect/releases) page. Then install it with `apt`: | ||||
|   | ||||
| @@ -1,17 +1,21 @@ | ||||
| use std::{env::temp_dir, fs::File}; | ||||
| use std::{env::temp_dir, fs::File, str::FromStr}; | ||||
|  | ||||
| use anyhow::bail; | ||||
| use clap::{Parser, Subcommand}; | ||||
| use gpapi::{ | ||||
|   clap::{handle_error, Args, InfoLevelVerbosity}, | ||||
|   utils::openssl, | ||||
| }; | ||||
| use log::info; | ||||
| use sysinfo::{Pid, System}; | ||||
| use tempfile::NamedTempFile; | ||||
| use tokio::fs; | ||||
|  | ||||
| use crate::{ | ||||
|   connect::{ConnectArgs, ConnectHandler}, | ||||
|   disconnect::{DisconnectArgs, DisconnectHandler}, | ||||
|   launch_gui::{LaunchGuiArgs, LaunchGuiHandler}, | ||||
|   GP_CLIENT_LOCK_FILE, | ||||
| }; | ||||
|  | ||||
| const VERSION: &str = concat!(env!("CARGO_PKG_VERSION"), " (", compile_time::date_str!(), ")"); | ||||
| @@ -77,6 +81,25 @@ impl Args for Cli { | ||||
| } | ||||
|  | ||||
| impl Cli { | ||||
|   async fn is_running(&self) -> bool { | ||||
|     let Ok(c) = fs::read_to_string(GP_CLIENT_LOCK_FILE).await else { | ||||
|       return false; | ||||
|     }; | ||||
|  | ||||
|     let Ok(pid) = Pid::from_str(c.trim()) else { | ||||
|       return false; | ||||
|     }; | ||||
|  | ||||
|     let s = System::new_all(); | ||||
|     let Some(p) = s.process(pid) else { | ||||
|       return false; | ||||
|     }; | ||||
|  | ||||
|     p.exe() | ||||
|       .map(|exe| exe.to_string_lossy().contains("gpclient")) | ||||
|       .unwrap_or(false) | ||||
|   } | ||||
|  | ||||
|   fn fix_openssl(&self) -> anyhow::Result<Option<NamedTempFile>> { | ||||
|     if self.fix_openssl { | ||||
|       let file = openssl::fix_openssl_env()?; | ||||
| @@ -87,6 +110,11 @@ impl Cli { | ||||
|   } | ||||
|  | ||||
|   async fn run(&self) -> anyhow::Result<()> { | ||||
|     // check if an instance is running | ||||
|     if self.is_running().await { | ||||
|       bail!("Another instance of the client is already running"); | ||||
|     } | ||||
|  | ||||
|     // 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()?; | ||||
|   | ||||
| @@ -39,6 +39,9 @@ clap-verbosity-flag = { workspace = true, optional = true } | ||||
| env_logger = { workspace = true, optional = true } | ||||
| log-reload = { version = "0.1", optional = true } | ||||
|  | ||||
| [target.'cfg(not(any(target_os="macos", target_os="windows")))'.dependencies] | ||||
| gtk = "0.18" | ||||
|  | ||||
| [features] | ||||
| tauri = ["dep:tauri"] | ||||
| clap = ["dep:clap", "dep:clap-verbosity-flag"] | ||||
|   | ||||
| @@ -41,12 +41,6 @@ pub fn patch_gui_runtime_env(hidpi: bool) { | ||||
|   // This is to avoid blank screen on some systems | ||||
|   std::env::set_var("WEBKIT_DISABLE_COMPOSITING_MODE", "1"); | ||||
|  | ||||
|   // Workaround for https://github.com/tauri-apps/tao/issues/929 | ||||
|   let is_wayland = std::env::var("XDG_SESSION_TYPE").unwrap_or_default() == "wayland"; | ||||
|   if is_wayland { | ||||
|     env::set_var("GDK_BACKEND", "x11"); | ||||
|   } | ||||
|  | ||||
|   if hidpi { | ||||
|     info!("Setting GDK_SCALE=2 and GDK_DPI_SCALE=0.5"); | ||||
|     std::env::set_var("GDK_SCALE", "2"); | ||||
|   | ||||
| @@ -1,73 +1,97 @@ | ||||
| use std::{process::ExitStatus, time::Duration}; | ||||
|  | ||||
| use anyhow::bail; | ||||
| use log::info; | ||||
| use tauri::WebviewWindow; | ||||
| use tokio::process::Command; | ||||
|  | ||||
| pub trait WindowExt { | ||||
|   fn raise(&self) -> anyhow::Result<()>; | ||||
| } | ||||
|  | ||||
| impl WindowExt for WebviewWindow { | ||||
|   #[cfg(any(target_os = "macos", target_os = "windows"))] | ||||
|   fn raise(&self) -> anyhow::Result<()> { | ||||
|     raise_window(self) | ||||
|     self.show()?; | ||||
|     Ok(()) | ||||
|   } | ||||
|  | ||||
|   #[cfg(not(any(target_os = "macos", target_os = "windows")))] | ||||
|   fn raise(&self) -> anyhow::Result<()> { | ||||
|     unix::raise_window(self) | ||||
|   } | ||||
| } | ||||
|  | ||||
| pub fn raise_window(win: &WebviewWindow) -> anyhow::Result<()> { | ||||
|   let is_wayland = std::env::var("XDG_SESSION_TYPE").unwrap_or_default() == "wayland"; | ||||
| #[cfg(not(any(target_os = "macos", target_os = "windows")))] | ||||
| mod unix { | ||||
|   use std::{process::ExitStatus, time::Duration}; | ||||
|  | ||||
|   if is_wayland { | ||||
|     win.hide()?; | ||||
|     win.show()?; | ||||
|   } else { | ||||
|     if !win.is_visible()? { | ||||
|       win.show()?; | ||||
|     } | ||||
|     let title = win.title()?; | ||||
|     tokio::spawn(async move { | ||||
|       if let Err(err) = wmctrl_raise_window(&title).await { | ||||
|         info!("Window not raised: {}", err); | ||||
|   use anyhow::bail; | ||||
|   use gtk::{ | ||||
|     glib::Cast, | ||||
|     traits::{EventBoxExt, GtkWindowExt, WidgetExt}, | ||||
|     EventBox, | ||||
|   }; | ||||
|   use log::info; | ||||
|   use tauri::WebviewWindow; | ||||
|   use tokio::process::Command; | ||||
|  | ||||
|   pub fn raise_window(win: &WebviewWindow) -> anyhow::Result<()> { | ||||
|     let is_wayland = std::env::var("XDG_SESSION_TYPE").unwrap_or_default() == "wayland"; | ||||
|  | ||||
|     if is_wayland { | ||||
|       let gtk_win = win.gtk_window()?; | ||||
|       if let Some(header) = gtk_win.titlebar() { | ||||
|         let _ = header.downcast::<EventBox>().map(|event_box| { | ||||
|           event_box.set_above_child(false); | ||||
|         }); | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   // Calling window.show() on Windows will cause the menu to be shown. | ||||
|   // We need to hide it again. | ||||
|   win.hide_menu()?; | ||||
|  | ||||
|   Ok(()) | ||||
| } | ||||
|  | ||||
| async fn wmctrl_raise_window(title: &str) -> anyhow::Result<()> { | ||||
|   let mut counter = 0; | ||||
|  | ||||
|   loop { | ||||
|     if let Ok(exit_status) = wmctrl_try_raise_window(title).await { | ||||
|       if exit_status.success() { | ||||
|         info!("Window raised after {} attempts", counter + 1); | ||||
|         return Ok(()); | ||||
|       gtk_win.hide(); | ||||
|       gtk_win.show_all(); | ||||
|     } else { | ||||
|       if !win.is_visible()? { | ||||
|         win.show()?; | ||||
|       } | ||||
|       let title = win.title()?; | ||||
|       tokio::spawn(async move { | ||||
|         if let Err(err) = wmctrl_raise_window(&title).await { | ||||
|           info!("Window not raised: {}", err); | ||||
|         } | ||||
|       }); | ||||
|     } | ||||
|  | ||||
|     if counter >= 10 { | ||||
|       bail!("Failed to raise window: {}", title) | ||||
|     } | ||||
|     // Calling window.show() on window object will cause the menu to be shown. | ||||
|     // We need to hide it again. | ||||
|     win.hide_menu()?; | ||||
|  | ||||
|     counter += 1; | ||||
|     tokio::time::sleep(Duration::from_millis(100)).await; | ||||
|     Ok(()) | ||||
|   } | ||||
|  | ||||
|   async fn wmctrl_raise_window(title: &str) -> anyhow::Result<()> { | ||||
|     let mut counter = 0; | ||||
|  | ||||
|     loop { | ||||
|       if let Ok(exit_status) = wmctrl_try_raise_window(title).await { | ||||
|         if exit_status.success() { | ||||
|           info!("Window raised after {} attempts", counter + 1); | ||||
|           return Ok(()); | ||||
|         } | ||||
|       } | ||||
|  | ||||
|       if counter >= 10 { | ||||
|         bail!("Failed to raise window: {}", title) | ||||
|       } | ||||
|  | ||||
|       counter += 1; | ||||
|       tokio::time::sleep(Duration::from_millis(100)).await; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   async fn wmctrl_try_raise_window(title: &str) -> anyhow::Result<ExitStatus> { | ||||
|     let exit_status = Command::new("wmctrl") | ||||
|       .arg("-F") | ||||
|       .arg("-a") | ||||
|       .arg(title) | ||||
|       .spawn()? | ||||
|       .wait() | ||||
|       .await?; | ||||
|  | ||||
|     Ok(exit_status) | ||||
|   } | ||||
| } | ||||
|  | ||||
| async fn wmctrl_try_raise_window(title: &str) -> anyhow::Result<ExitStatus> { | ||||
|   let exit_status = Command::new("wmctrl") | ||||
|     .arg("-F") | ||||
|     .arg("-a") | ||||
|     .arg(title) | ||||
|     .spawn()? | ||||
|     .wait() | ||||
|     .await?; | ||||
|  | ||||
|   Ok(exit_status) | ||||
| } | ||||
|   | ||||
| @@ -6,7 +6,7 @@ Group:          Productivity/Networking/PPP | ||||
|  | ||||
| License:        GPL-3.0 | ||||
| URL:            https://github.com/yuezk/GlobalProtect-openconnect | ||||
| Source:         %{name}.tar.gz | ||||
| Source:         @SOURCE@ | ||||
|  | ||||
| BuildRequires:  make | ||||
| BuildRequires:  rust | ||||
|   | ||||
| @@ -28,7 +28,7 @@ release_snapshot() { | ||||
|  | ||||
|   echo "Uploading new assets..." | ||||
|   gh -R "$REPO" release upload "$TAG" \ | ||||
|     "$PROJECT_DIR"/.build/artifacts/artifact-source/* \ | ||||
|     "$PROJECT_DIR"/.build/artifacts/artifact-source*/* \ | ||||
|     "$PROJECT_DIR"/.build/artifacts/artifact-gpgui-*/* | ||||
| } | ||||
|  | ||||
| @@ -40,7 +40,7 @@ release_tag() { | ||||
|   gh -R "$REPO" release create $TAG \ | ||||
|     --title "$TAG" \ | ||||
|     --notes "$RELEASE_NOTES" \ | ||||
|     "$PROJECT_DIR"/.build/artifacts/artifact-source/* \ | ||||
|     "$PROJECT_DIR"/.build/artifacts/artifact-source*/* \ | ||||
|     "$PROJECT_DIR"/.build/artifacts/artifact-gpgui-*/* | ||||
| } | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user