Compare commits
	
		
			1 Commits
		
	
	
		
			1.x
			...
			v2.0.0-bet
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 04a916a3e1 | 
							
								
								
									
										62
									
								
								.devcontainer/Dockerfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,62 @@ | ||||
| FROM ubuntu:18.04 | ||||
|  | ||||
| ARG USERNAME=vscode | ||||
| ARG USER_UID=1000 | ||||
| ARG USER_GID=$USER_UID | ||||
|  | ||||
| ENV RUSTUP_HOME=/usr/local/rustup \ | ||||
|     CARGO_HOME=/usr/local/cargo \ | ||||
|     PATH=/usr/local/cargo/bin:$PATH \ | ||||
|     RUST_VERSION=1.75.0 | ||||
|  | ||||
| RUN set -eux; \ | ||||
|   apt-get update; \ | ||||
|   apt-get install -y --no-install-recommends \ | ||||
|     sudo \ | ||||
|     ca-certificates \ | ||||
|     curl \ | ||||
|     gnupg \ | ||||
|     git \ | ||||
|     less \ | ||||
|     software-properties-common \ | ||||
|     # Tauri dependencies | ||||
|     libwebkit2gtk-4.0-dev build-essential wget libssl-dev libgtk-3-dev libayatana-appindicator3-dev librsvg2-dev; \ | ||||
|   # Install openconnect | ||||
|   add-apt-repository ppa:yuezk/globalprotect-openconnect; \ | ||||
|   apt-get update; \ | ||||
|   apt-get install -y openconnect libopenconnect-dev; \ | ||||
|   # Create a non-root user | ||||
|   groupadd --gid $USER_GID $USERNAME; \ | ||||
|   useradd --uid $USER_UID --gid $USER_GID -m $USERNAME; \ | ||||
|   echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME; \ | ||||
|   chmod 0440 /etc/sudoers.d/$USERNAME; \ | ||||
|   # Install Node.js | ||||
|   mkdir -p /etc/apt/keyrings; \ | ||||
|   curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg; \ | ||||
|   echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_16.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list; \ | ||||
|   apt-get update; \ | ||||
|   apt-get install -y nodejs; \ | ||||
|   corepack enable; \ | ||||
|   # Install diff-so-fancy | ||||
|   npm install -g diff-so-fancy; \ | ||||
|   # Install Rust | ||||
|   curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain $RUST_VERSION; \ | ||||
|   chown -R $USERNAME:$USERNAME $RUSTUP_HOME $CARGO_HOME; \ | ||||
|   rustup --version; \ | ||||
|   cargo --version; \ | ||||
|   rustc --version | ||||
|  | ||||
| USER $USERNAME | ||||
|  | ||||
| # Install Oh My Zsh | ||||
| RUN sh -c "$(wget -O- https://github.com/deluan/zsh-in-docker/releases/download/v1.1.5/zsh-in-docker.sh)" -- \ | ||||
|     -t https://github.com/denysdovhan/spaceship-prompt \ | ||||
|     -a 'SPACESHIP_PROMPT_ADD_NEWLINE="false"' \ | ||||
|     -a 'SPACESHIP_PROMPT_SEPARATE_LINE="false"' \ | ||||
|     -p git \ | ||||
|     -p https://github.com/zsh-users/zsh-autosuggestions \ | ||||
|     -p https://github.com/zsh-users/zsh-completions; \ | ||||
|     # Change the default shell | ||||
|     sudo chsh -s /bin/zsh $USERNAME; \ | ||||
|     # Change the XTERM to xterm-256color | ||||
|     sed -i 's/TERM=xterm/TERM=xterm-256color/g' $HOME/.zshrc; | ||||
							
								
								
									
										10
									
								
								.devcontainer/devcontainer.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,10 @@ | ||||
| { | ||||
|   "build": { | ||||
|     "dockerfile": "Dockerfile" | ||||
|   }, | ||||
|   "runArgs": [ | ||||
|     "--privileged", | ||||
|     "--cap-add=NET_ADMIN", | ||||
|     "--device=/dev/net/tun" | ||||
|   ] | ||||
| } | ||||
| @@ -1,13 +1,9 @@ | ||||
| # top-most EditorConfig file | ||||
| root = true | ||||
|  | ||||
| # Unix-style newlines with a newline ending every file | ||||
| [*] | ||||
| end_of_line = lf | ||||
| insert_final_newline = false | ||||
| trim_trailing_whitespace=true | ||||
| charset = utf-8 | ||||
| indent_style = space | ||||
| indent_size = 4 | ||||
|  | ||||
| [*.sh] | ||||
| indent_style = tab | ||||
| indent_size = 2 | ||||
| end_of_line = lf | ||||
| insert_final_newline = true | ||||
| trim_trailing_whitespace = true | ||||
|   | ||||
							
								
								
									
										250
									
								
								.github/workflows/build.yaml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,250 @@ | ||||
| name: Build GPGUI | ||||
| on: | ||||
|   push: | ||||
|     paths-ignore: | ||||
|       - LICENSE | ||||
|       - "*.md" | ||||
|       - .vscode | ||||
|       - .devcontainer | ||||
|     branches: | ||||
|       - main | ||||
|     # tags: | ||||
|     #   - v*.*.* | ||||
| jobs: | ||||
|   # Include arm64 if ref is a tag | ||||
|   setup-matrix: | ||||
|     runs-on: ubuntu-latest | ||||
|     outputs: | ||||
|       matrix: ${{ steps.set-matrix.outputs.matrix }} | ||||
|     steps: | ||||
|       - name: Set up matrix | ||||
|         id: set-matrix | ||||
|         run: | | ||||
|           if [[ "${{ github.ref }}" == "refs/tags/"* ]]; then | ||||
|             echo "matrix=[\"amd64\", \"arm64\"]" >> $GITHUB_OUTPUT | ||||
|           else | ||||
|             echo "matrix=[\"amd64\"]" >> $GITHUB_OUTPUT | ||||
|           fi | ||||
|  | ||||
|   build-fe: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - name: Checkout gpgui repo | ||||
|         uses: actions/checkout@v4 | ||||
|         with: | ||||
|           token: ${{ secrets.GH_PAT }} | ||||
|           repository: yuezk/gpgui | ||||
|  | ||||
|       - name: Install Node.js | ||||
|         uses: actions/setup-node@v4 | ||||
|         with: | ||||
|           node-version: 18 | ||||
|  | ||||
|       - uses: pnpm/action-setup@v2 | ||||
|         with: | ||||
|           version: 8 | ||||
|  | ||||
|       - name: Install dependencies | ||||
|         run: | | ||||
|           cd app | ||||
|           pnpm install | ||||
|       - name: Build | ||||
|         run: | | ||||
|           cd app | ||||
|           pnpm run build | ||||
|  | ||||
|       - name: Upload artifacts | ||||
|         uses: actions/upload-artifact@v4 | ||||
|         with: | ||||
|           name: gpgui-fe | ||||
|           path: app/dist | ||||
|  | ||||
|   build-tauri: | ||||
|     needs: [setup-matrix, build-fe] | ||||
|     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: Checkout gp repo | ||||
|         uses: actions/checkout@v4 | ||||
|         with: | ||||
|           token: ${{ secrets.GH_PAT }} | ||||
|           repository: yuezk/GlobalProtect-openconnect | ||||
|           path: gp | ||||
|  | ||||
|       - name: Download gpgui-fe artifact | ||||
|         uses: actions/download-artifact@v4 | ||||
|         with: | ||||
|           name: gpgui-fe | ||||
|           path: gpgui/app/dist | ||||
|  | ||||
|       - 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: Build Tauri in Docker | ||||
|         run: | | ||||
|           docker run \ | ||||
|             --rm \ | ||||
|             -v $(pwd):/${{ github.workspace }} \ | ||||
|             -w ${{ github.workspace }} \ | ||||
|             -e CI=true \ | ||||
|             --platform linux/${{ matrix.arch }} \ | ||||
|             yuezk/gpdev:main \ | ||||
|             "./gpgui/scripts/build.sh" | ||||
|  | ||||
|       - name: Upload artifacts | ||||
|         uses: actions/upload-artifact@v4 | ||||
|         with: | ||||
|           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/* | ||||
							
								
								
									
										275
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,275 +0,0 @@ | ||||
| name: Build | ||||
|  | ||||
| on: | ||||
|   push: | ||||
|     branches: | ||||
|       - master | ||||
|       - develop | ||||
|     tags: | ||||
|       - "v*.*.*" | ||||
|     paths-ignore: | ||||
|       - LICENSE | ||||
|       - "*.md" | ||||
|       - .vscode | ||||
|   workflow_dispatch: | ||||
|  | ||||
| # A workflow run is made up of one or more jobs that can run sequentially or in parallel | ||||
| jobs: | ||||
|   build: | ||||
|     strategy: | ||||
|       matrix: | ||||
|         os: [ubuntu-18.04, ubuntu-20.04, ubuntu-22.04] | ||||
|  | ||||
|     runs-on: ${{ matrix.os }} | ||||
|  | ||||
|     steps: | ||||
|       # Checkout repository and submodules | ||||
|       - uses: actions/checkout@v2 | ||||
|         with: | ||||
|           submodules: recursive | ||||
|  | ||||
|       - name: Build | ||||
|         run: | | ||||
|           ./scripts/install-ubuntu.sh | ||||
|           # assert no library missing | ||||
|           test $(ldd $(which gpclient) | grep 'not found' | wc -l) -eq 0 | ||||
|  | ||||
|   snapshot-archive-all: | ||||
|     if: ${{ github.event_name != 'pull_request' && github.ref == 'refs/heads/develop' }} | ||||
|     needs: build | ||||
|     runs-on: ubuntu-latest | ||||
|  | ||||
|     steps: | ||||
|       - uses: actions/checkout@v2 | ||||
|         with: | ||||
|           submodules: recursive | ||||
|           fetch-depth: 0 | ||||
|  | ||||
|       - name: Install dependencies | ||||
|         run: | | ||||
|           python -m pip install --upgrade pip | ||||
|           pip install git-archive-all | ||||
|  | ||||
|       - name: Archive all | ||||
|         run: | | ||||
|           ./scripts/snapshot-archive-all.sh | ||||
|  | ||||
|       - name: Verify debian package | ||||
|         run: | | ||||
|           ./scripts/verify-debian-package.sh | ||||
|  | ||||
|       - uses: actions/upload-artifact@v2 | ||||
|         with: | ||||
|           name: snapshot-source-code | ||||
|           path: ./artifacts/* | ||||
|  | ||||
|   snapshot-ppa: | ||||
|     if: ${{ github.event_name != 'pull_request' && github.ref == 'refs/heads/develop' }} | ||||
|     needs: snapshot-archive-all | ||||
|     runs-on: ubuntu-latest | ||||
|  | ||||
|     steps: | ||||
|       - uses: actions/download-artifact@v2 | ||||
|         with: | ||||
|           name: snapshot-source-code | ||||
|           path: artifacts | ||||
|  | ||||
|       - name: Extract source code | ||||
|         run: | | ||||
|           cd $GITHUB_WORKSPACE/artifacts | ||||
|           mkdir deb-build && cp *.tar.gz deb-build && cd deb-build | ||||
|           tar xf *.tar.gz | ||||
|  | ||||
|       - name: Publish PPA | ||||
|         uses: yuezk/publish-ppa-package@develop | ||||
|         with: | ||||
|           repository: 'ppa:yuezk/globalprotect-openconnect-snapshot' | ||||
|           gpg_private_key: ${{ secrets.PPA_GPG_PRIVATE_KEY }} | ||||
|           gpg_passphrase: ${{ secrets.PPA_GPG_PASSPHRASE }} | ||||
|           pkgdir: '${{ github.workspace }}/artifacts/deb-build/globalprotect-openconnect*/' | ||||
|  | ||||
|   snapshot-aur: | ||||
|     if: ${{ github.event_name != 'pull_request' && github.ref == 'refs/heads/develop' }} | ||||
|     needs: snapshot-archive-all | ||||
|     runs-on: ubuntu-latest | ||||
|  | ||||
|     steps: | ||||
|       - uses: actions/download-artifact@v2 | ||||
|         with: | ||||
|           name: snapshot-source-code | ||||
|           path: artifacts | ||||
|  | ||||
|       - name: Publish AUR package | ||||
|         uses: yuezk/github-actions-deploy-aur@update-pkgver | ||||
|         with: | ||||
|           pkgname: globalprotect-openconnect-git | ||||
|           pkgbuild: ./artifacts/aur/PKGBUILD | ||||
|           assets: ./artifacts/aur/gp.install | ||||
|           update_pkgver: true | ||||
|           commit_username: ${{ secrets.AUR_USERNAME }} | ||||
|           commit_email: ${{ secrets.AUR_EMAIL }} | ||||
|           ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }} | ||||
|           commit_message: 'Snapshot release: git#${{ github.sha }}' | ||||
|  | ||||
|   snapshot-obs: | ||||
|     if: ${{ github.event_name != 'pull_request' && github.ref == 'refs/heads/develop' }} | ||||
|     needs: snapshot-archive-all | ||||
|     runs-on: ubuntu-latest | ||||
|  | ||||
|     steps: | ||||
|       - uses: actions/download-artifact@v2 | ||||
|         with: | ||||
|           name: snapshot-source-code | ||||
|           path: artifacts | ||||
|  | ||||
|       - uses: yuezk/publish-obs-package@main | ||||
|         with: | ||||
|           project: home:yuezk | ||||
|           package: globalprotect-openconnect-snapshot | ||||
|           username: yuezk | ||||
|           password: ${{ secrets.OBS_PASSWORD }} | ||||
|           files: ./artifacts/obs/* | ||||
|  | ||||
|   snapshot-snap: | ||||
|     # if: ${{ github.event_name != 'pull_request' && github.ref == 'refs/heads/develop' }} | ||||
|     if: ${{ false }} | ||||
|     needs: snapshot-archive-all | ||||
|     runs-on: ubuntu-latest | ||||
|  | ||||
|     steps: | ||||
|       - uses: actions/download-artifact@v2 | ||||
|         with: | ||||
|           name: snapshot-source-code | ||||
|           path: artifacts | ||||
|  | ||||
|       - name: Extract source code | ||||
|         run: | | ||||
|           mkdir snap-source | ||||
|           tar xvf ./artifacts/globalprotect-openconnect-*tar.gz \ | ||||
|             --directory snap-source \ | ||||
|             --strip 1 | ||||
|  | ||||
|       - uses: snapcore/action-build@v1 | ||||
|         id: build | ||||
|         with: | ||||
|           path: ./snap-source | ||||
|  | ||||
|       - uses: snapcore/action-publish@v1 | ||||
|         with: | ||||
|           store_login: ${{ secrets.SNAPSTORE_LOGIN }} | ||||
|           snap: ${{ steps.build.outputs.snap }} | ||||
|           release: edge | ||||
|  | ||||
|   release-archive-all: | ||||
|     if: startsWith(github.ref, 'refs/tags/v') | ||||
|     needs: build | ||||
|     runs-on: ubuntu-latest | ||||
|  | ||||
|     steps: | ||||
|       - uses: actions/checkout@v2 | ||||
|         with: | ||||
|           submodules: recursive | ||||
|           fetch-depth: 0 | ||||
|  | ||||
|       - name: Install dependencies | ||||
|         run: | | ||||
|           python -m pip install --upgrade pip | ||||
|           pip install git-archive-all | ||||
|  | ||||
|       - name: Archive all | ||||
|         run: | | ||||
|           ./scripts/release-archive-all.sh | ||||
|  | ||||
|       - name: Verify debian package | ||||
|         run: | | ||||
|           ./scripts/verify-debian-package.sh | ||||
|  | ||||
|       - uses: actions/upload-artifact@v2 | ||||
|         with: | ||||
|           name: release-source-code | ||||
|           path: ./artifacts/* | ||||
|  | ||||
|   release-ppa: | ||||
|     if: startsWith(github.ref, 'refs/tags/v') | ||||
|     needs: release-archive-all | ||||
|     runs-on: ubuntu-latest | ||||
|  | ||||
|     steps: | ||||
|       - uses: actions/download-artifact@v2 | ||||
|         with: | ||||
|           name: release-source-code | ||||
|           path: artifacts | ||||
|  | ||||
|       - name: Extract source code | ||||
|         run: | | ||||
|           cd $GITHUB_WORKSPACE/artifacts | ||||
|           mkdir deb-build && cp *.tar.gz deb-build && cd deb-build | ||||
|           tar xf *.tar.gz | ||||
|  | ||||
|       - name: Publish PPA | ||||
|         uses: yuezk/publish-ppa-package@develop | ||||
|         with: | ||||
|           repository: 'ppa:yuezk/globalprotect-openconnect' | ||||
|           gpg_private_key: ${{ secrets.PPA_GPG_PRIVATE_KEY }} | ||||
|           gpg_passphrase: ${{ secrets.PPA_GPG_PASSPHRASE }} | ||||
|           pkgdir: '${{ github.workspace }}/artifacts/deb-build/globalprotect-openconnect*/' | ||||
|  | ||||
|   release-aur: | ||||
|     if: startsWith(github.ref, 'refs/tags/v') | ||||
|     needs: release-archive-all | ||||
|     runs-on: ubuntu-latest | ||||
|  | ||||
|     steps: | ||||
|       - uses: actions/download-artifact@v2 | ||||
|         with: | ||||
|           name: release-source-code | ||||
|           path: artifacts | ||||
|  | ||||
|       - name: Publish AUR package | ||||
|         uses: yuezk/github-actions-deploy-aur@update-pkgver | ||||
|         with: | ||||
|           pkgname: globalprotect-openconnect-git | ||||
|           pkgbuild: ./artifacts/aur/PKGBUILD | ||||
|           assets: ./artifacts/aur/gp.install | ||||
|           update_pkgver: true | ||||
|           commit_username: ${{ secrets.AUR_USERNAME }} | ||||
|           commit_email: ${{ secrets.AUR_EMAIL }} | ||||
|           ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }} | ||||
|           commit_message: 'Release ${{ github.ref }}' | ||||
|  | ||||
|   release-obs: | ||||
|     if: startsWith(github.ref, 'refs/tags/v') | ||||
|     needs: release-archive-all | ||||
|     runs-on: ubuntu-latest | ||||
|  | ||||
|     steps: | ||||
|       - uses: actions/download-artifact@v2 | ||||
|         with: | ||||
|           name: release-source-code | ||||
|           path: artifacts | ||||
|  | ||||
|       - uses: yuezk/publish-obs-package@main | ||||
|         with: | ||||
|           project: home:yuezk | ||||
|           package: globalprotect-openconnect | ||||
|           username: yuezk | ||||
|           password: ${{ secrets.OBS_PASSWORD }} | ||||
|           files: ./artifacts/obs/* | ||||
|  | ||||
|   release-github: | ||||
|     if: startsWith(github.ref, 'refs/tags/v') | ||||
|     needs: | ||||
|       - release-ppa | ||||
|       - release-aur | ||||
|       - release-obs | ||||
|     runs-on: ubuntu-latest | ||||
|  | ||||
|     steps: | ||||
|       - uses: actions/download-artifact@v2 | ||||
|         with: | ||||
|           name: release-source-code | ||||
|           path: artifacts | ||||
|       - uses: softprops/action-gh-release@v1 | ||||
|         with: | ||||
|           files: | | ||||
|             ./artifacts/*.tar.gz | ||||
							
								
								
									
										33
									
								
								.github/workflows/pr.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,33 +0,0 @@ | ||||
| name: PR Build | ||||
|  | ||||
| on: | ||||
|   pull_request: | ||||
|     branches: | ||||
|       - master | ||||
|       - develop | ||||
|     paths-ignore: | ||||
|       - LICENSE | ||||
|       - "*.md" | ||||
|       - .vscode | ||||
|   workflow_dispatch: | ||||
|  | ||||
| # A workflow run is made up of one or more jobs that can run sequentially or in parallel | ||||
| jobs: | ||||
|   build: | ||||
|     strategy: | ||||
|       matrix: | ||||
|         os: [ubuntu-18.04, ubuntu-20.04] | ||||
|  | ||||
|     runs-on: ${{ matrix.os }} | ||||
|  | ||||
|     steps: | ||||
|       # Checkout repository and submodules | ||||
|       - uses: actions/checkout@v2 | ||||
|         with: | ||||
|           submodules: recursive | ||||
|  | ||||
|       - name: Build | ||||
|         run: | | ||||
|           ./scripts/install-ubuntu.sh | ||||
|           # assert no library missing | ||||
|           test $(ldd $(which gpclient) | grep 'not found' | wc -l) -eq 0 | ||||
							
								
								
									
										72
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,70 +1,4 @@ | ||||
| # Binaries | ||||
| *.rpm | ||||
| *.gz | ||||
| *.snap | ||||
| .DS_Store | ||||
| build-debian | ||||
| build | ||||
| artifacts | ||||
|  | ||||
| .cmake | ||||
| .idea | ||||
|  | ||||
| # Auto generated DBus files | ||||
| *_adaptor.cpp | ||||
| *_adaptor.h | ||||
|  | ||||
| gpservice_interface.* | ||||
|  | ||||
| # C++ objects and libs | ||||
| *.slo | ||||
| *.lo | ||||
| *.o | ||||
| *.a | ||||
| *.la | ||||
| *.lai | ||||
| *.so | ||||
| *.so.* | ||||
| *.dll | ||||
| *.dylib | ||||
|  | ||||
| # Qt-es | ||||
| object_script.*.Release | ||||
| object_script.*.Debug | ||||
| *_plugin_import.cpp | ||||
| /.qmake.cache | ||||
| /.qmake.stash | ||||
| *.pro.user | ||||
| *.pro.user.* | ||||
| *.qbs.user | ||||
| *.qbs.user.* | ||||
| *.moc | ||||
| moc_*.cpp | ||||
| moc_*.h | ||||
| qrc_*.cpp | ||||
| ui_*.h | ||||
| *.qmlc | ||||
| *.jsc | ||||
| Makefile* | ||||
| *build-* | ||||
| *.qm | ||||
| *.prl | ||||
|  | ||||
| # Qt unit tests | ||||
| target_wrapper.* | ||||
|  | ||||
| # QtCreator | ||||
| *.autosave | ||||
|  | ||||
| # QtCreator Qml | ||||
| *.qmlproject.user | ||||
| *.qmlproject.user.* | ||||
|  | ||||
| # QtCreator CMake | ||||
| CMakeLists.txt.user* | ||||
|  | ||||
| # QtCreator 4.8< compilation database | ||||
| compile_commands.json | ||||
|  | ||||
| # QtCreator local machine specific files for imported projects | ||||
| *creator.user* | ||||
| /target | ||||
| .pnpm-store | ||||
| .env | ||||
|   | ||||
							
								
								
									
										10
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,10 +0,0 @@ | ||||
| [submodule "singleapplication"] | ||||
| 	path = 3rdparty/SingleApplication | ||||
| 	url = https://github.com/itay-grudev/SingleApplication.git | ||||
|  | ||||
| [submodule "plog"] | ||||
| 	path = 3rdparty/plog | ||||
| 	url = https://github.com/SergiusTheBest/plog.git | ||||
| [submodule "3rdparty/qtkeychain"] | ||||
| 	path = 3rdparty/qtkeychain | ||||
| 	url = git@github.com:frankosterfeld/qtkeychain.git | ||||
							
								
								
									
										9
									
								
								.vscode/extensions.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,9 @@ | ||||
| { | ||||
|     "recommendations": [ | ||||
|         "rust-lang.rust-analyzer", | ||||
|         "tamasfe.even-better-toml", | ||||
|         "eamodio.gitlens", | ||||
|         "EditorConfig.EditorConfig", | ||||
|         "streetsidesoftware.code-spell-checker", | ||||
|     ] | ||||
| } | ||||
							
								
								
									
										73
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,26 +1,51 @@ | ||||
| { | ||||
|     "files.watcherExclude": { | ||||
|         "**/artifacts/**": true, | ||||
|     }, | ||||
|     "files.associations": { | ||||
|         "qregularexpression": "cpp", | ||||
|         "qfileinfo": "cpp", | ||||
|         "qregularexpressionmatch": "cpp", | ||||
|         "qdatetime": "cpp", | ||||
|         "qprocess": "cpp", | ||||
|         "qobject": "cpp", | ||||
|         "qstandardpaths": "cpp", | ||||
|         "qmainwindow": "cpp", | ||||
|         "qsystemtrayicon": "cpp", | ||||
|         "qpushbutton": "cpp", | ||||
|         "qmenu": "cpp", | ||||
|         "qjsondocument": "cpp", | ||||
|         "qnetworkaccessmanager": "cpp", | ||||
|         "qwebengineview": "cpp", | ||||
|         "qprocessenvironment": "cpp", | ||||
|         "qnetworkreply": "cpp", | ||||
|         "qicon": "cpp", | ||||
|         "qsslsocket": "cpp", | ||||
|         "qapplication": "cpp" | ||||
|     } | ||||
|     "cSpell.words": [ | ||||
|         "authcookie", | ||||
|         "bincode", | ||||
|         "chacha", | ||||
|         "clientos", | ||||
|         "datetime", | ||||
|         "disconnectable", | ||||
|         "distro", | ||||
|         "dotenv", | ||||
|         "dotenvy", | ||||
|         "getconfig", | ||||
|         "gpapi", | ||||
|         "gpauth", | ||||
|         "gpclient", | ||||
|         "gpcommon", | ||||
|         "gpgui", | ||||
|         "gpservice", | ||||
|         "hidpi", | ||||
|         "jnlp", | ||||
|         "LOGNAME", | ||||
|         "oneshot", | ||||
|         "openconnect", | ||||
|         "pkexec", | ||||
|         "Prelogin", | ||||
|         "prelogon", | ||||
|         "prelogonuserauthcookie", | ||||
|         "repr", | ||||
|         "reqwest", | ||||
|         "roxmltree", | ||||
|         "rspc", | ||||
|         "servercert", | ||||
|         "specta", | ||||
|         "sysinfo", | ||||
|         "tanstack", | ||||
|         "tauri", | ||||
|         "tempfile", | ||||
|         "thiserror", | ||||
|         "tungstenite", | ||||
|         "unistd", | ||||
|         "unlisten", | ||||
|         "urlencoding", | ||||
|         "userauthcookie", | ||||
|         "utsbuf", | ||||
|         "Vite", | ||||
|         "vpnc", | ||||
|         "vpninfo", | ||||
|         "wmctrl", | ||||
|         "XAUTHORITY" | ||||
|     ] | ||||
| } | ||||
|   | ||||
							
								
								
									
										1
									
								
								3rdparty/SingleApplication
									
									
									
									
										vendored
									
									
								
							
							
								
								
								
								
								
							
						
						
							
								
								
									
										12
									
								
								3rdparty/inih/CMakeLists.txt
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,12 +0,0 @@ | ||||
| cmake_minimum_required(VERSION 3.10.0) | ||||
|  | ||||
| set(CMAKE_CXX_STANDARD 17) | ||||
| project(inih) | ||||
|  | ||||
| add_library(inih STATIC | ||||
|     ini.h | ||||
|     ini.c | ||||
|     cpp/INIReader.h | ||||
|     cpp/INIReader.cpp | ||||
| ) | ||||
| target_include_directories(inih PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/cpp") | ||||
							
								
								
									
										27
									
								
								3rdparty/inih/LICENSE.txt
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,27 +0,0 @@ | ||||
|  | ||||
| The "inih" library is distributed under the New BSD license: | ||||
|  | ||||
| Copyright (c) 2009, Ben Hoyt | ||||
| All rights reserved. | ||||
|  | ||||
| Redistribution and use in source and binary forms, with or without | ||||
| modification, are permitted provided that the following conditions are met: | ||||
|     * Redistributions of source code must retain the above copyright | ||||
|       notice, this list of conditions and the following disclaimer. | ||||
|     * Redistributions in binary form must reproduce the above copyright | ||||
|       notice, this list of conditions and the following disclaimer in the | ||||
|       documentation and/or other materials provided with the distribution. | ||||
|     * Neither the name of Ben Hoyt nor the names of its contributors | ||||
|       may be used to endorse or promote products derived from this software | ||||
|       without specific prior written permission. | ||||
|  | ||||
| THIS SOFTWARE IS PROVIDED BY BEN HOYT ''AS IS'' AND ANY | ||||
| EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||||
| WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||||
| DISCLAIMED. IN NO EVENT SHALL BEN HOYT BE LIABLE FOR ANY | ||||
| DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||||
| (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||||
| LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||||
| ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||||
| (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||||
| SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||
							
								
								
									
										116
									
								
								3rdparty/inih/cpp/INIReader.cpp
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,116 +0,0 @@ | ||||
| // Read an INI file into easy-to-access name/value pairs. | ||||
|  | ||||
| // SPDX-License-Identifier: BSD-3-Clause | ||||
|  | ||||
| // Copyright (C) 2009-2020, Ben Hoyt | ||||
|  | ||||
| // inih and INIReader are released under the New BSD license (see LICENSE.txt). | ||||
| // Go to the project home page for more info: | ||||
| // | ||||
| // https://github.com/benhoyt/inih | ||||
|  | ||||
| #include <algorithm> | ||||
| #include <cctype> | ||||
| #include <cstdlib> | ||||
| #include "../ini.h" | ||||
| #include "INIReader.h" | ||||
|  | ||||
| using std::string; | ||||
|  | ||||
| INIReader::INIReader(const string& filename) | ||||
| { | ||||
|     _error = ini_parse(filename.c_str(), ValueHandler, this); | ||||
| } | ||||
|  | ||||
| INIReader::INIReader(const char *buffer, size_t buffer_size) | ||||
| { | ||||
|   string content(buffer, buffer_size); | ||||
|   _error = ini_parse_string(content.c_str(), ValueHandler, this); | ||||
| } | ||||
|  | ||||
| int INIReader::ParseError() const | ||||
| { | ||||
|     return _error; | ||||
| } | ||||
|  | ||||
| string INIReader::Get(const string& section, const string& name, const string& default_value) const | ||||
| { | ||||
|     string key = MakeKey(section, name); | ||||
|     // Use _values.find() here instead of _values.at() to support pre C++11 compilers | ||||
|     return _values.count(key) ? _values.find(key)->second : default_value; | ||||
| } | ||||
|  | ||||
| string INIReader::GetString(const string& section, const string& name, const string& default_value) const | ||||
| { | ||||
|     const string str = Get(section, name, ""); | ||||
|     return str.empty() ? default_value : str; | ||||
| } | ||||
|  | ||||
| long INIReader::GetInteger(const string& section, const string& name, long default_value) const | ||||
| { | ||||
|     string valstr = Get(section, name, ""); | ||||
|     const char* value = valstr.c_str(); | ||||
|     char* end; | ||||
|     // This parses "1234" (decimal) and also "0x4D2" (hex) | ||||
|     long n = strtol(value, &end, 0); | ||||
|     return end > value ? n : default_value; | ||||
| } | ||||
|  | ||||
| double INIReader::GetReal(const string& section, const string& name, double default_value) const | ||||
| { | ||||
|     string valstr = Get(section, name, ""); | ||||
|     const char* value = valstr.c_str(); | ||||
|     char* end; | ||||
|     double n = strtod(value, &end); | ||||
|     return end > value ? n : default_value; | ||||
| } | ||||
|  | ||||
| bool INIReader::GetBoolean(const string& section, const string& name, bool default_value) const | ||||
| { | ||||
|     string valstr = Get(section, name, ""); | ||||
|     // Convert to lower case to make string comparisons case-insensitive | ||||
|     std::transform(valstr.begin(), valstr.end(), valstr.begin(), ::tolower); | ||||
|     if (valstr == "true" || valstr == "yes" || valstr == "on" || valstr == "1") | ||||
|         return true; | ||||
|     else if (valstr == "false" || valstr == "no" || valstr == "off" || valstr == "0") | ||||
|         return false; | ||||
|     else | ||||
|         return default_value; | ||||
| } | ||||
|  | ||||
| bool INIReader::HasSection(const string& section) const | ||||
| { | ||||
|     const string key = MakeKey(section, ""); | ||||
|     std::map<string, string>::const_iterator pos = _values.lower_bound(key); | ||||
|     if (pos == _values.end()) | ||||
|         return false; | ||||
|     // Does the key at the lower_bound pos start with "section"? | ||||
|     return pos->first.compare(0, key.length(), key) == 0; | ||||
| } | ||||
|  | ||||
| bool INIReader::HasValue(const string& section, const string& name) const | ||||
| { | ||||
|     string key = MakeKey(section, name); | ||||
|     return _values.count(key); | ||||
| } | ||||
|  | ||||
| string INIReader::MakeKey(const string& section, const string& name) | ||||
| { | ||||
|     string key = section + "=" + name; | ||||
|     // Convert to lower case to make section/name lookups case-insensitive | ||||
|     std::transform(key.begin(), key.end(), key.begin(), ::tolower); | ||||
|     return key; | ||||
| } | ||||
|  | ||||
| int INIReader::ValueHandler(void* user, const char* section, const char* name, | ||||
|                             const char* value) | ||||
| { | ||||
|     if (!name)  // Happens when INI_CALL_HANDLER_ON_NEW_SECTION enabled | ||||
|         return 1; | ||||
|     INIReader* reader = static_cast<INIReader*>(user); | ||||
|     string key = MakeKey(section, name); | ||||
|     if (reader->_values[key].size() > 0) | ||||
|         reader->_values[key] += "\n"; | ||||
|     reader->_values[key] += value ? value : ""; | ||||
|     return 1; | ||||
| } | ||||
							
								
								
									
										94
									
								
								3rdparty/inih/cpp/INIReader.h
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,94 +0,0 @@ | ||||
| // Read an INI file into easy-to-access name/value pairs. | ||||
|  | ||||
| // SPDX-License-Identifier: BSD-3-Clause | ||||
|  | ||||
| // Copyright (C) 2009-2020, Ben Hoyt | ||||
|  | ||||
| // inih and INIReader are released under the New BSD license (see LICENSE.txt). | ||||
| // Go to the project home page for more info: | ||||
| // | ||||
| // https://github.com/benhoyt/inih | ||||
|  | ||||
| #ifndef INIREADER_H | ||||
| #define INIREADER_H | ||||
|  | ||||
| #include <map> | ||||
| #include <string> | ||||
|  | ||||
| // Visibility symbols, required for Windows DLLs | ||||
| #ifndef INI_API | ||||
| #if defined _WIN32 || defined __CYGWIN__ | ||||
| #	ifdef INI_SHARED_LIB | ||||
| #		ifdef INI_SHARED_LIB_BUILDING | ||||
| #			define INI_API __declspec(dllexport) | ||||
| #		else | ||||
| #			define INI_API __declspec(dllimport) | ||||
| #		endif | ||||
| #	else | ||||
| #		define INI_API | ||||
| #	endif | ||||
| #else | ||||
| #	if defined(__GNUC__) && __GNUC__ >= 4 | ||||
| #		define INI_API __attribute__ ((visibility ("default"))) | ||||
| #	else | ||||
| #		define INI_API | ||||
| #	endif | ||||
| #endif | ||||
| #endif | ||||
|  | ||||
| // Read an INI file into easy-to-access name/value pairs. (Note that I've gone | ||||
| // for simplicity here rather than speed, but it should be pretty decent.) | ||||
| class INIReader | ||||
| { | ||||
| public: | ||||
|     // Construct INIReader and parse given filename. See ini.h for more info | ||||
|     // about the parsing. | ||||
|     INI_API explicit INIReader(const std::string& filename); | ||||
|  | ||||
|     // Construct INIReader and parse given buffer. See ini.h for more info | ||||
|     // about the parsing. | ||||
|     INI_API explicit INIReader(const char *buffer, size_t buffer_size); | ||||
|  | ||||
|     // Return the result of ini_parse(), i.e., 0 on success, line number of | ||||
|     // first error on parse error, or -1 on file open error. | ||||
|     INI_API int ParseError() const; | ||||
|  | ||||
|     // Get a string value from INI file, returning default_value if not found. | ||||
|     INI_API std::string Get(const std::string& section, const std::string& name, | ||||
|                     const std::string& default_value) const; | ||||
|  | ||||
|     // Get a string value from INI file, returning default_value if not found, | ||||
|     // empty, or contains only whitespace. | ||||
|     INI_API std::string GetString(const std::string& section, const std::string& name, | ||||
|                     const std::string& default_value) const; | ||||
|  | ||||
|     // Get an integer (long) value from INI file, returning default_value if | ||||
|     // not found or not a valid integer (decimal "1234", "-1234", or hex "0x4d2"). | ||||
|     INI_API long GetInteger(const std::string& section, const std::string& name, long default_value) const; | ||||
|  | ||||
|     // Get a real (floating point double) value from INI file, returning | ||||
|     // default_value if not found or not a valid floating point value | ||||
|     // according to strtod(). | ||||
|     INI_API double GetReal(const std::string& section, const std::string& name, double default_value) const; | ||||
|  | ||||
|     // Get a boolean value from INI file, returning default_value if not found or if | ||||
|     // not a valid true/false value. Valid true values are "true", "yes", "on", "1", | ||||
|     // and valid false values are "false", "no", "off", "0" (not case sensitive). | ||||
|     INI_API bool GetBoolean(const std::string& section, const std::string& name, bool default_value) const; | ||||
|  | ||||
|     // Return true if the given section exists (section must contain at least | ||||
|     // one name=value pair). | ||||
|     INI_API bool HasSection(const std::string& section) const; | ||||
|  | ||||
|     // Return true if a value exists with the given section and field names. | ||||
|     INI_API bool HasValue(const std::string& section, const std::string& name) const; | ||||
|  | ||||
| private: | ||||
|     int _error; | ||||
|     std::map<std::string, std::string> _values; | ||||
|     static std::string MakeKey(const std::string& section, const std::string& name); | ||||
|     static int ValueHandler(void* user, const char* section, const char* name, | ||||
|                             const char* value); | ||||
| }; | ||||
|  | ||||
| #endif  // INIREADER_H | ||||
							
								
								
									
										298
									
								
								3rdparty/inih/ini.c
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,298 +0,0 @@ | ||||
| /* inih -- simple .INI file parser | ||||
|  | ||||
| SPDX-License-Identifier: BSD-3-Clause | ||||
|  | ||||
| Copyright (C) 2009-2020, Ben Hoyt | ||||
|  | ||||
| inih is released under the New BSD license (see LICENSE.txt). Go to the project | ||||
| home page for more info: | ||||
|  | ||||
| https://github.com/benhoyt/inih | ||||
|  | ||||
| */ | ||||
|  | ||||
| #if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) | ||||
| #define _CRT_SECURE_NO_WARNINGS | ||||
| #endif | ||||
|  | ||||
| #include <stdio.h> | ||||
| #include <ctype.h> | ||||
| #include <string.h> | ||||
|  | ||||
| #include "ini.h" | ||||
|  | ||||
| #if !INI_USE_STACK | ||||
| #if INI_CUSTOM_ALLOCATOR | ||||
| #include <stddef.h> | ||||
| void* ini_malloc(size_t size); | ||||
| void ini_free(void* ptr); | ||||
| void* ini_realloc(void* ptr, size_t size); | ||||
| #else | ||||
| #include <stdlib.h> | ||||
| #define ini_malloc malloc | ||||
| #define ini_free free | ||||
| #define ini_realloc realloc | ||||
| #endif | ||||
| #endif | ||||
|  | ||||
| #define MAX_SECTION 50 | ||||
| #define MAX_NAME 50 | ||||
|  | ||||
| /* Used by ini_parse_string() to keep track of string parsing state. */ | ||||
| typedef struct { | ||||
|     const char* ptr; | ||||
|     size_t num_left; | ||||
| } ini_parse_string_ctx; | ||||
|  | ||||
| /* Strip whitespace chars off end of given string, in place. Return s. */ | ||||
| static char* rstrip(char* s) | ||||
| { | ||||
|     char* p = s + strlen(s); | ||||
|     while (p > s && isspace((unsigned char)(*--p))) | ||||
|         *p = '\0'; | ||||
|     return s; | ||||
| } | ||||
|  | ||||
| /* Return pointer to first non-whitespace char in given string. */ | ||||
| static char* lskip(const char* s) | ||||
| { | ||||
|     while (*s && isspace((unsigned char)(*s))) | ||||
|         s++; | ||||
|     return (char*)s; | ||||
| } | ||||
|  | ||||
| /* Return pointer to first char (of chars) or inline comment in given string, | ||||
|    or pointer to NUL at end of string if neither found. Inline comment must | ||||
|    be prefixed by a whitespace character to register as a comment. */ | ||||
| static char* find_chars_or_comment(const char* s, const char* chars) | ||||
| { | ||||
| #if INI_ALLOW_INLINE_COMMENTS | ||||
|     int was_space = 0; | ||||
|     while (*s && (!chars || !strchr(chars, *s)) && | ||||
|            !(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) { | ||||
|         was_space = isspace((unsigned char)(*s)); | ||||
|         s++; | ||||
|     } | ||||
| #else | ||||
|     while (*s && (!chars || !strchr(chars, *s))) { | ||||
|         s++; | ||||
|     } | ||||
| #endif | ||||
|     return (char*)s; | ||||
| } | ||||
|  | ||||
| /* Similar to strncpy, but ensures dest (size bytes) is | ||||
|    NUL-terminated, and doesn't pad with NULs. */ | ||||
| static char* strncpy0(char* dest, const char* src, size_t size) | ||||
| { | ||||
|     /* Could use strncpy internally, but it causes gcc warnings (see issue #91) */ | ||||
|     size_t i; | ||||
|     for (i = 0; i < size - 1 && src[i]; i++) | ||||
|         dest[i] = src[i]; | ||||
|     dest[i] = '\0'; | ||||
|     return dest; | ||||
| } | ||||
|  | ||||
| /* See documentation in header file. */ | ||||
| int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler, | ||||
|                      void* user) | ||||
| { | ||||
|     /* Uses a fair bit of stack (use heap instead if you need to) */ | ||||
| #if INI_USE_STACK | ||||
|     char line[INI_MAX_LINE]; | ||||
|     int max_line = INI_MAX_LINE; | ||||
| #else | ||||
|     char* line; | ||||
|     size_t max_line = INI_INITIAL_ALLOC; | ||||
| #endif | ||||
| #if INI_ALLOW_REALLOC && !INI_USE_STACK | ||||
|     char* new_line; | ||||
|     size_t offset; | ||||
| #endif | ||||
|     char section[MAX_SECTION] = ""; | ||||
|     char prev_name[MAX_NAME] = ""; | ||||
|  | ||||
|     char* start; | ||||
|     char* end; | ||||
|     char* name; | ||||
|     char* value; | ||||
|     int lineno = 0; | ||||
|     int error = 0; | ||||
|  | ||||
| #if !INI_USE_STACK | ||||
|     line = (char*)ini_malloc(INI_INITIAL_ALLOC); | ||||
|     if (!line) { | ||||
|         return -2; | ||||
|     } | ||||
| #endif | ||||
|  | ||||
| #if INI_HANDLER_LINENO | ||||
| #define HANDLER(u, s, n, v) handler(u, s, n, v, lineno) | ||||
| #else | ||||
| #define HANDLER(u, s, n, v) handler(u, s, n, v) | ||||
| #endif | ||||
|  | ||||
|     /* Scan through stream line by line */ | ||||
|     while (reader(line, (int)max_line, stream) != NULL) { | ||||
| #if INI_ALLOW_REALLOC && !INI_USE_STACK | ||||
|         offset = strlen(line); | ||||
|         while (offset == max_line - 1 && line[offset - 1] != '\n') { | ||||
|             max_line *= 2; | ||||
|             if (max_line > INI_MAX_LINE) | ||||
|                 max_line = INI_MAX_LINE; | ||||
|             new_line = ini_realloc(line, max_line); | ||||
|             if (!new_line) { | ||||
|                 ini_free(line); | ||||
|                 return -2; | ||||
|             } | ||||
|             line = new_line; | ||||
|             if (reader(line + offset, (int)(max_line - offset), stream) == NULL) | ||||
|                 break; | ||||
|             if (max_line >= INI_MAX_LINE) | ||||
|                 break; | ||||
|             offset += strlen(line + offset); | ||||
|         } | ||||
| #endif | ||||
|  | ||||
|         lineno++; | ||||
|  | ||||
|         start = line; | ||||
| #if INI_ALLOW_BOM | ||||
|         if (lineno == 1 && (unsigned char)start[0] == 0xEF && | ||||
|                            (unsigned char)start[1] == 0xBB && | ||||
|                            (unsigned char)start[2] == 0xBF) { | ||||
|             start += 3; | ||||
|         } | ||||
| #endif | ||||
|         start = lskip(rstrip(start)); | ||||
|  | ||||
|         if (strchr(INI_START_COMMENT_PREFIXES, *start)) { | ||||
|             /* Start-of-line comment */ | ||||
|         } | ||||
| #if INI_ALLOW_MULTILINE | ||||
|         else if (*prev_name && *start && start > line) { | ||||
|             /* Non-blank line with leading whitespace, treat as continuation | ||||
|                of previous name's value (as per Python configparser). */ | ||||
|             if (!HANDLER(user, section, prev_name, start) && !error) | ||||
|                 error = lineno; | ||||
|         } | ||||
| #endif | ||||
|         else if (*start == '[') { | ||||
|             /* A "[section]" line */ | ||||
|             end = find_chars_or_comment(start + 1, "]"); | ||||
|             if (*end == ']') { | ||||
|                 *end = '\0'; | ||||
|                 strncpy0(section, start + 1, sizeof(section)); | ||||
|                 *prev_name = '\0'; | ||||
| #if INI_CALL_HANDLER_ON_NEW_SECTION | ||||
|                 if (!HANDLER(user, section, NULL, NULL) && !error) | ||||
|                     error = lineno; | ||||
| #endif | ||||
|             } | ||||
|             else if (!error) { | ||||
|                 /* No ']' found on section line */ | ||||
|                 error = lineno; | ||||
|             } | ||||
|         } | ||||
|         else if (*start) { | ||||
|             /* Not a comment, must be a name[=:]value pair */ | ||||
|             end = find_chars_or_comment(start, "=:"); | ||||
|             if (*end == '=' || *end == ':') { | ||||
|                 *end = '\0'; | ||||
|                 name = rstrip(start); | ||||
|                 value = end + 1; | ||||
| #if INI_ALLOW_INLINE_COMMENTS | ||||
|                 end = find_chars_or_comment(value, NULL); | ||||
|                 if (*end) | ||||
|                     *end = '\0'; | ||||
| #endif | ||||
|                 value = lskip(value); | ||||
|                 rstrip(value); | ||||
|  | ||||
|                 /* Valid name[=:]value pair found, call handler */ | ||||
|                 strncpy0(prev_name, name, sizeof(prev_name)); | ||||
|                 if (!HANDLER(user, section, name, value) && !error) | ||||
|                     error = lineno; | ||||
|             } | ||||
|             else if (!error) { | ||||
|                 /* No '=' or ':' found on name[=:]value line */ | ||||
| #if INI_ALLOW_NO_VALUE | ||||
|                 *end = '\0'; | ||||
|                 name = rstrip(start); | ||||
|                 if (!HANDLER(user, section, name, NULL) && !error) | ||||
|                     error = lineno; | ||||
| #else | ||||
|                 error = lineno; | ||||
| #endif | ||||
|             } | ||||
|         } | ||||
|  | ||||
| #if INI_STOP_ON_FIRST_ERROR | ||||
|         if (error) | ||||
|             break; | ||||
| #endif | ||||
|     } | ||||
|  | ||||
| #if !INI_USE_STACK | ||||
|     ini_free(line); | ||||
| #endif | ||||
|  | ||||
|     return error; | ||||
| } | ||||
|  | ||||
| /* See documentation in header file. */ | ||||
| int ini_parse_file(FILE* file, ini_handler handler, void* user) | ||||
| { | ||||
|     return ini_parse_stream((ini_reader)fgets, file, handler, user); | ||||
| } | ||||
|  | ||||
| /* See documentation in header file. */ | ||||
| int ini_parse(const char* filename, ini_handler handler, void* user) | ||||
| { | ||||
|     FILE* file; | ||||
|     int error; | ||||
|  | ||||
|     file = fopen(filename, "r"); | ||||
|     if (!file) | ||||
|         return -1; | ||||
|     error = ini_parse_file(file, handler, user); | ||||
|     fclose(file); | ||||
|     return error; | ||||
| } | ||||
|  | ||||
| /* An ini_reader function to read the next line from a string buffer. This | ||||
|    is the fgets() equivalent used by ini_parse_string(). */ | ||||
| static char* ini_reader_string(char* str, int num, void* stream) { | ||||
|     ini_parse_string_ctx* ctx = (ini_parse_string_ctx*)stream; | ||||
|     const char* ctx_ptr = ctx->ptr; | ||||
|     size_t ctx_num_left = ctx->num_left; | ||||
|     char* strp = str; | ||||
|     char c; | ||||
|  | ||||
|     if (ctx_num_left == 0 || num < 2) | ||||
|         return NULL; | ||||
|  | ||||
|     while (num > 1 && ctx_num_left != 0) { | ||||
|         c = *ctx_ptr++; | ||||
|         ctx_num_left--; | ||||
|         *strp++ = c; | ||||
|         if (c == '\n') | ||||
|             break; | ||||
|         num--; | ||||
|     } | ||||
|  | ||||
|     *strp = '\0'; | ||||
|     ctx->ptr = ctx_ptr; | ||||
|     ctx->num_left = ctx_num_left; | ||||
|     return str; | ||||
| } | ||||
|  | ||||
| /* See documentation in header file. */ | ||||
| int ini_parse_string(const char* string, ini_handler handler, void* user) { | ||||
|     ini_parse_string_ctx ctx; | ||||
|  | ||||
|     ctx.ptr = string; | ||||
|     ctx.num_left = strlen(string); | ||||
|     return ini_parse_stream((ini_reader)ini_reader_string, &ctx, handler, | ||||
|                             user); | ||||
| } | ||||
							
								
								
									
										178
									
								
								3rdparty/inih/ini.h
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,178 +0,0 @@ | ||||
| /* inih -- simple .INI file parser | ||||
|  | ||||
| SPDX-License-Identifier: BSD-3-Clause | ||||
|  | ||||
| Copyright (C) 2009-2020, Ben Hoyt | ||||
|  | ||||
| inih is released under the New BSD license (see LICENSE.txt). Go to the project | ||||
| home page for more info: | ||||
|  | ||||
| https://github.com/benhoyt/inih | ||||
|  | ||||
| */ | ||||
|  | ||||
| #ifndef INI_H | ||||
| #define INI_H | ||||
|  | ||||
| /* Make this header file easier to include in C++ code */ | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
|  | ||||
| #include <stdio.h> | ||||
|  | ||||
| /* Nonzero if ini_handler callback should accept lineno parameter. */ | ||||
| #ifndef INI_HANDLER_LINENO | ||||
| #define INI_HANDLER_LINENO 0 | ||||
| #endif | ||||
|  | ||||
| /* Visibility symbols, required for Windows DLLs */ | ||||
| #ifndef INI_API | ||||
| #if defined _WIN32 || defined __CYGWIN__ | ||||
| #	ifdef INI_SHARED_LIB | ||||
| #		ifdef INI_SHARED_LIB_BUILDING | ||||
| #			define INI_API __declspec(dllexport) | ||||
| #		else | ||||
| #			define INI_API __declspec(dllimport) | ||||
| #		endif | ||||
| #	else | ||||
| #		define INI_API | ||||
| #	endif | ||||
| #else | ||||
| #	if defined(__GNUC__) && __GNUC__ >= 4 | ||||
| #		define INI_API __attribute__ ((visibility ("default"))) | ||||
| #	else | ||||
| #		define INI_API | ||||
| #	endif | ||||
| #endif | ||||
| #endif | ||||
|  | ||||
| /* Typedef for prototype of handler function. */ | ||||
| #if INI_HANDLER_LINENO | ||||
| typedef int (*ini_handler)(void* user, const char* section, | ||||
|                            const char* name, const char* value, | ||||
|                            int lineno); | ||||
| #else | ||||
| typedef int (*ini_handler)(void* user, const char* section, | ||||
|                            const char* name, const char* value); | ||||
| #endif | ||||
|  | ||||
| /* Typedef for prototype of fgets-style reader function. */ | ||||
| typedef char* (*ini_reader)(char* str, int num, void* stream); | ||||
|  | ||||
| /* Parse given INI-style file. May have [section]s, name=value pairs | ||||
|    (whitespace stripped), and comments starting with ';' (semicolon). Section | ||||
|    is "" if name=value pair parsed before any section heading. name:value | ||||
|    pairs are also supported as a concession to Python's configparser. | ||||
|  | ||||
|    For each name=value pair parsed, call handler function with given user | ||||
|    pointer as well as section, name, and value (data only valid for duration | ||||
|    of handler call). Handler should return nonzero on success, zero on error. | ||||
|  | ||||
|    Returns 0 on success, line number of first error on parse error (doesn't | ||||
|    stop on first error), -1 on file open error, or -2 on memory allocation | ||||
|    error (only when INI_USE_STACK is zero). | ||||
| */ | ||||
| INI_API int ini_parse(const char* filename, ini_handler handler, void* user); | ||||
|  | ||||
| /* Same as ini_parse(), but takes a FILE* instead of filename. This doesn't | ||||
|    close the file when it's finished -- the caller must do that. */ | ||||
| INI_API int ini_parse_file(FILE* file, ini_handler handler, void* user); | ||||
|  | ||||
| /* Same as ini_parse(), but takes an ini_reader function pointer instead of | ||||
|    filename. Used for implementing custom or string-based I/O (see also | ||||
|    ini_parse_string). */ | ||||
| INI_API int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler, | ||||
|                      void* user); | ||||
|  | ||||
| /* Same as ini_parse(), but takes a zero-terminated string with the INI data | ||||
| instead of a file. Useful for parsing INI data from a network socket or | ||||
| already in memory. */ | ||||
| INI_API int ini_parse_string(const char* string, ini_handler handler, void* user); | ||||
|  | ||||
| /* Nonzero to allow multi-line value parsing, in the style of Python's | ||||
|    configparser. If allowed, ini_parse() will call the handler with the same | ||||
|    name for each subsequent line parsed. */ | ||||
| #ifndef INI_ALLOW_MULTILINE | ||||
| #define INI_ALLOW_MULTILINE 1 | ||||
| #endif | ||||
|  | ||||
| /* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of | ||||
|    the file. See https://github.com/benhoyt/inih/issues/21 */ | ||||
| #ifndef INI_ALLOW_BOM | ||||
| #define INI_ALLOW_BOM 1 | ||||
| #endif | ||||
|  | ||||
| /* Chars that begin a start-of-line comment. Per Python configparser, allow | ||||
|    both ; and # comments at the start of a line by default. */ | ||||
| #ifndef INI_START_COMMENT_PREFIXES | ||||
| #define INI_START_COMMENT_PREFIXES ";#" | ||||
| #endif | ||||
|  | ||||
| /* Nonzero to allow inline comments (with valid inline comment characters | ||||
|    specified by INI_INLINE_COMMENT_PREFIXES). Set to 0 to turn off and match | ||||
|    Python 3.2+ configparser behaviour. */ | ||||
| #ifndef INI_ALLOW_INLINE_COMMENTS | ||||
| #define INI_ALLOW_INLINE_COMMENTS 1 | ||||
| #endif | ||||
| #ifndef INI_INLINE_COMMENT_PREFIXES | ||||
| #define INI_INLINE_COMMENT_PREFIXES ";" | ||||
| #endif | ||||
|  | ||||
| /* Nonzero to use stack for line buffer, zero to use heap (malloc/free). */ | ||||
| #ifndef INI_USE_STACK | ||||
| #define INI_USE_STACK 1 | ||||
| #endif | ||||
|  | ||||
| /* Maximum line length for any line in INI file (stack or heap). Note that | ||||
|    this must be 3 more than the longest line (due to '\r', '\n', and '\0'). */ | ||||
| #ifndef INI_MAX_LINE | ||||
| #define INI_MAX_LINE 200 | ||||
| #endif | ||||
|  | ||||
| /* Nonzero to allow heap line buffer to grow via realloc(), zero for a | ||||
|    fixed-size buffer of INI_MAX_LINE bytes. Only applies if INI_USE_STACK is | ||||
|    zero. */ | ||||
| #ifndef INI_ALLOW_REALLOC | ||||
| #define INI_ALLOW_REALLOC 0 | ||||
| #endif | ||||
|  | ||||
| /* Initial size in bytes for heap line buffer. Only applies if INI_USE_STACK | ||||
|    is zero. */ | ||||
| #ifndef INI_INITIAL_ALLOC | ||||
| #define INI_INITIAL_ALLOC 200 | ||||
| #endif | ||||
|  | ||||
| /* Stop parsing on first error (default is to keep parsing). */ | ||||
| #ifndef INI_STOP_ON_FIRST_ERROR | ||||
| #define INI_STOP_ON_FIRST_ERROR 0 | ||||
| #endif | ||||
|  | ||||
| /* Nonzero to call the handler at the start of each new section (with | ||||
|    name and value NULL). Default is to only call the handler on | ||||
|    each name=value pair. */ | ||||
| #ifndef INI_CALL_HANDLER_ON_NEW_SECTION | ||||
| #define INI_CALL_HANDLER_ON_NEW_SECTION 0 | ||||
| #endif | ||||
|  | ||||
| /* Nonzero to allow a name without a value (no '=' or ':' on the line) and | ||||
|    call the handler with value NULL in this case. Default is to treat | ||||
|    no-value lines as an error. */ | ||||
| #ifndef INI_ALLOW_NO_VALUE | ||||
| #define INI_ALLOW_NO_VALUE 0 | ||||
| #endif | ||||
|  | ||||
| /* Nonzero to use custom ini_malloc, ini_free, and ini_realloc memory | ||||
|    allocation functions (INI_USE_STACK must also be 0). These functions must | ||||
|    have the same signatures as malloc/free/realloc and behave in a similar | ||||
|    way. ini_realloc is only needed if INI_ALLOW_REALLOC is set. */ | ||||
| #ifndef INI_CUSTOM_ALLOCATOR | ||||
| #define INI_CUSTOM_ALLOCATOR 0 | ||||
| #endif | ||||
|  | ||||
|  | ||||
| #ifdef __cplusplus | ||||
| } | ||||
| #endif | ||||
|  | ||||
| #endif /* INI_H */ | ||||
							
								
								
									
										1
									
								
								3rdparty/plog
									
									
									
									
										vendored
									
									
								
							
							
								
								
								
								
								
							
						
						
							
								
								
									
										14
									
								
								3rdparty/qt-unix-signals/CMakeLists.txt
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,14 +0,0 @@ | ||||
| cmake_minimum_required(VERSION 3.1.0) | ||||
|  | ||||
| project(QtSignals LANGUAGES CXX) | ||||
|  | ||||
| set(CMAKE_CXX_STANDARD 17) | ||||
| set(CMAKE_CXX_STANDARD_REQUIRED ON) | ||||
| # Instruct CMake to run moc automatically when needed. | ||||
| set(CMAKE_AUTOMOC ON) | ||||
|  | ||||
| find_package(Qt5 REQUIRED COMPONENTS Core) | ||||
|  | ||||
| add_library(QtSignals STATIC sigwatch.cpp) | ||||
| target_include_directories(QtSignals INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) | ||||
| target_link_libraries(QtSignals Qt5::Core) | ||||
							
								
								
									
										21
									
								
								3rdparty/qt-unix-signals/LICENCE
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,21 +0,0 @@ | ||||
| Unix signal watcher for Qt. | ||||
|  | ||||
| Copyright (C) 2014 Simon Knopp | ||||
|  | ||||
| Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| of this software and associated documentation files (the "Software"), to deal | ||||
| in the Software without restriction, including without limitation the rights | ||||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
| copies of the Software, and to permit persons to whom the Software is | ||||
| furnished to do so, subject to the following conditions: | ||||
|  | ||||
| The above copyright notice and this permission notice shall be included in | ||||
| all copies or substantial portions of the Software. | ||||
|  | ||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
| SOFTWARE. | ||||
							
								
								
									
										176
									
								
								3rdparty/qt-unix-signals/sigwatch.cpp
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,176 +0,0 @@ | ||||
| /* | ||||
|  * Unix signal watcher for Qt. | ||||
|  * | ||||
|  * Copyright (C) 2014 Simon Knopp | ||||
|  * | ||||
|  * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
|  * of this software and associated documentation files (the "Software"), to deal | ||||
|  * in the Software without restriction, including without limitation the rights | ||||
|  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
|  * copies of the Software, and to permit persons to whom the Software is | ||||
|  * furnished to do so, subject to the following conditions: | ||||
|  * | ||||
|  * The above copyright notice and this permission notice shall be included in | ||||
|  * all copies or substantial portions of the Software. | ||||
|  * | ||||
|  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
|  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
|  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
|  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
|  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
|  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
|  * SOFTWARE. | ||||
|  */ | ||||
|  | ||||
| #include <sys/socket.h> | ||||
| #include <unistd.h> | ||||
| #include <errno.h> | ||||
| #include <QMap> | ||||
| #include <QSocketNotifier> | ||||
| #include <QDebug> | ||||
| #include "sigwatch.h" | ||||
|  | ||||
|  | ||||
| /*! | ||||
|  * \brief The UnixSignalWatcherPrivate class implements the back-end signal | ||||
|  * handling for the UnixSignalWatcher. | ||||
|  * | ||||
|  * \see http://qt-project.org/doc/qt-5.0/qtdoc/unix-signals.html | ||||
|  */ | ||||
| class UnixSignalWatcherPrivate : public QObject | ||||
| { | ||||
|     UnixSignalWatcher * const q_ptr; | ||||
|     Q_DECLARE_PUBLIC(UnixSignalWatcher) | ||||
|  | ||||
| public: | ||||
|     UnixSignalWatcherPrivate(UnixSignalWatcher *q); | ||||
|     ~UnixSignalWatcherPrivate(); | ||||
|  | ||||
|     void watchForSignal(int signal); | ||||
|     static void signalHandler(int signal); | ||||
|  | ||||
|     void _q_onNotify(int sockfd); | ||||
|  | ||||
| private: | ||||
|     static int sockpair[2]; | ||||
|     QSocketNotifier *notifier; | ||||
|     QList<int> watchedSignals; | ||||
| }; | ||||
|  | ||||
|  | ||||
| int UnixSignalWatcherPrivate::sockpair[2]; | ||||
|  | ||||
| UnixSignalWatcherPrivate::UnixSignalWatcherPrivate(UnixSignalWatcher *q) : | ||||
|     q_ptr(q) | ||||
| { | ||||
|     // Create socket pair | ||||
|     if (::socketpair(AF_UNIX, SOCK_STREAM, 0, sockpair)) { | ||||
|         qDebug() << "UnixSignalWatcher: socketpair: " << ::strerror(errno); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     // Create a notifier for the read end of the pair | ||||
|     notifier = new QSocketNotifier(sockpair[1], QSocketNotifier::Read); | ||||
|     QObject::connect(notifier, SIGNAL(activated(int)), q, SLOT(_q_onNotify(int))); | ||||
|     notifier->setEnabled(true); | ||||
| } | ||||
|  | ||||
| UnixSignalWatcherPrivate::~UnixSignalWatcherPrivate() | ||||
| { | ||||
|     delete notifier; | ||||
| } | ||||
|  | ||||
| /*! | ||||
|  * Registers a handler for the given Unix \a signal. The handler will write to | ||||
|  * a socket pair, the other end of which is connected to a QSocketNotifier. | ||||
|  * This provides a way to break out of the asynchronous context from which the | ||||
|  * signal handler is called and back into the Qt event loop. | ||||
|  */ | ||||
| void UnixSignalWatcherPrivate::watchForSignal(int signal) | ||||
| { | ||||
|     if (watchedSignals.contains(signal)) { | ||||
|         qDebug() << "Already watching for signal" << signal; | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     // Register a sigaction which will write to the socket pair | ||||
|     struct sigaction sigact; | ||||
|     sigact.sa_handler = UnixSignalWatcherPrivate::signalHandler; | ||||
|     sigact.sa_flags = 0; | ||||
|     ::sigemptyset(&sigact.sa_mask); | ||||
|     sigact.sa_flags |= SA_RESTART; | ||||
|     if (::sigaction(signal, &sigact, NULL)) { | ||||
|         qDebug() << "UnixSignalWatcher: sigaction: " << ::strerror(errno); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     watchedSignals.append(signal); | ||||
| } | ||||
|  | ||||
| /*! | ||||
|  * Called when a Unix \a signal is received. Write to the socket to wake up the | ||||
|  * QSocketNotifier. | ||||
|  */ | ||||
| void UnixSignalWatcherPrivate::signalHandler(int signal) | ||||
| { | ||||
|     ssize_t nBytes = ::write(sockpair[0], &signal, sizeof(signal)); | ||||
|     Q_UNUSED(nBytes); | ||||
| } | ||||
|  | ||||
| /*! | ||||
|  * Called when the signal handler has written to the socket pair. Emits the Unix | ||||
|  * signal as a Qt signal. | ||||
|  */ | ||||
| void UnixSignalWatcherPrivate::_q_onNotify(int sockfd) | ||||
| { | ||||
|     Q_Q(UnixSignalWatcher); | ||||
|  | ||||
|     int signal; | ||||
|     ssize_t nBytes = ::read(sockfd, &signal, sizeof(signal)); | ||||
|     Q_UNUSED(nBytes); | ||||
|     qDebug() << "Caught signal:" << ::strsignal(signal); | ||||
|     emit q->unixSignal(signal); | ||||
| } | ||||
|  | ||||
|  | ||||
| /*! | ||||
|  * Create a new UnixSignalWatcher as a child of the given \a parent. | ||||
|  */ | ||||
| UnixSignalWatcher::UnixSignalWatcher(QObject *parent) : | ||||
|     QObject(parent), | ||||
|     d_ptr(new UnixSignalWatcherPrivate(this)) | ||||
| { | ||||
| } | ||||
|  | ||||
| /*! | ||||
|  * Destroy this UnixSignalWatcher. | ||||
|  */ | ||||
| UnixSignalWatcher::~UnixSignalWatcher() | ||||
| { | ||||
|     delete d_ptr; | ||||
| } | ||||
|  | ||||
| /*! | ||||
|  * Register a signal handler for the given \a signal. | ||||
|  * | ||||
|  * After calling this method you can \c connect() to the unixSignal() Qt signal | ||||
|  * to be notified when the Unix signal is received. | ||||
|  */ | ||||
| void UnixSignalWatcher::watchForSignal(int signal) | ||||
| { | ||||
|     Q_D(UnixSignalWatcher); | ||||
|     d->watchForSignal(signal); | ||||
| } | ||||
|  | ||||
| /*! | ||||
|  * \fn void UnixSignalWatcher::unixSignal(int signal) | ||||
|  * Emitted when the given Unix \a signal is received. | ||||
|  * | ||||
|  * watchForSignal() must be called for each Unix signal that you want to receive | ||||
|  * via the unixSignal() Qt signal. If a watcher is watching multiple signals, | ||||
|  * unixSignal() will be emitted whenever *any* of the watched Unix signals are | ||||
|  * received, and the \a signal argument can be inspected to find out which one | ||||
|  * was actually received. | ||||
|  */ | ||||
|  | ||||
| #include "moc_sigwatch.cpp" | ||||
							
								
								
									
										59
									
								
								3rdparty/qt-unix-signals/sigwatch.h
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,59 +0,0 @@ | ||||
| /* | ||||
|  * Unix signal watcher for Qt. | ||||
|  * | ||||
|  * Copyright (C) 2014 Simon Knopp | ||||
|  * | ||||
|  * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
|  * of this software and associated documentation files (the "Software"), to deal | ||||
|  * in the Software without restriction, including without limitation the rights | ||||
|  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
|  * copies of the Software, and to permit persons to whom the Software is | ||||
|  * furnished to do so, subject to the following conditions: | ||||
|  * | ||||
|  * The above copyright notice and this permission notice shall be included in | ||||
|  * all copies or substantial portions of the Software. | ||||
|  * | ||||
|  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
|  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
|  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
|  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
|  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
|  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
|  * SOFTWARE. | ||||
|  */ | ||||
|  | ||||
| #ifndef SIGWATCH_H | ||||
| #define SIGWATCH_H | ||||
|  | ||||
| #include <QObject> | ||||
| #include <signal.h> | ||||
|  | ||||
| class UnixSignalWatcherPrivate; | ||||
|  | ||||
|  | ||||
| /*! | ||||
|  * \brief The UnixSignalWatcher class converts Unix signals to Qt signals. | ||||
|  * | ||||
|  * To watch for a given signal, e.g. \c SIGINT, call \c watchForSignal(SIGINT) | ||||
|  * and \c connect() your handler to unixSignal(). | ||||
|  */ | ||||
|  | ||||
| class UnixSignalWatcher : public QObject | ||||
| { | ||||
|     Q_OBJECT | ||||
| public: | ||||
|     explicit UnixSignalWatcher(QObject *parent = 0); | ||||
|     ~UnixSignalWatcher(); | ||||
|  | ||||
|     void watchForSignal(int signal); | ||||
|  | ||||
| signals: | ||||
|     void unixSignal(int signal); | ||||
|  | ||||
| private: | ||||
|     UnixSignalWatcherPrivate * const d_ptr; | ||||
|     Q_DECLARE_PRIVATE(UnixSignalWatcher) | ||||
|     Q_PRIVATE_SLOT(d_func(), void _q_onNotify(int)) | ||||
| }; | ||||
|  | ||||
| #endif // SIGWATCH_H | ||||
							
								
								
									
										1
									
								
								3rdparty/qtkeychain
									
									
									
									
										vendored
									
									
								
							
							
								
								
								
								
								
							
						
						| @@ -1,39 +0,0 @@ | ||||
| cmake_minimum_required(VERSION 3.10.0) | ||||
|  | ||||
| set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake;${CMAKE_MODULE_PATH}") | ||||
| set(CMAKE_CXX_STANDARD 17) | ||||
| set(CMAKE_CXX_STANDARD_REQUIRED ON) | ||||
|  | ||||
| set(CMAKE_AUTOMOC ON) | ||||
| set(CMAKE_AUTORCC ON) | ||||
| set(CMAKE_AUTOUIC ON) | ||||
|  | ||||
| file(STRINGS "VERSION" version) | ||||
| project(GlobalProtect-openconnect LANGUAGES CXX) | ||||
|  | ||||
| # Set the CMAKE_INSTALL_PREFIX to /usr if not specified | ||||
| if (CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) | ||||
|     set(CMAKE_INSTALL_PREFIX "/usr" CACHE PATH "The default install prefix" FORCE) | ||||
| endif() | ||||
|  | ||||
| message(STATUS "CMAKE_INSTALL_PREFIX was set to: ${CMAKE_INSTALL_PREFIX}") | ||||
|  | ||||
| configure_file(version.h.in version.h) | ||||
|  | ||||
| find_package(Qt5 REQUIRED COMPONENTS | ||||
|     Core | ||||
|     Widgets | ||||
|     Network | ||||
|     WebSockets | ||||
|     WebEngine | ||||
|     WebEngineWidgets | ||||
|     DBus | ||||
| ) | ||||
|  | ||||
| find_package(Qt5Keychain REQUIRED) | ||||
|  | ||||
| add_subdirectory(3rdparty/qt-unix-signals) | ||||
| add_subdirectory(3rdparty/inih) | ||||
| add_subdirectory(GPService) | ||||
| add_subdirectory(GPClient) | ||||
| add_dependencies(gpclient gpservice) | ||||
							
								
								
									
										5052
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										52
									
								
								Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,52 @@ | ||||
| [workspace] | ||||
| resolver = "2" | ||||
|  | ||||
| members = ["crates/*", "apps/gpclient", "apps/gpservice", "apps/gpauth"] | ||||
|  | ||||
| [workspace.package] | ||||
| version = "2.0.0-beta.1" | ||||
| authors = ["Kevin Yue <k3vinyue@gmail.com>"] | ||||
| homepage = "https://github.com/yuezk/GlobalProtect-openconnect" | ||||
| edition = "2021" | ||||
| license = "GPL-3.0" | ||||
|  | ||||
| [workspace.dependencies] | ||||
| anyhow = "1.0" | ||||
| base64 = "0.21" | ||||
| clap = { version = "4.4.2", features = ["derive"] } | ||||
| ctrlc = "3.4" | ||||
| directories = "5.0" | ||||
| env_logger = "0.10" | ||||
| is_executable = "1.0" | ||||
| log = "0.4" | ||||
| regex = "1" | ||||
| reqwest = { version = "0.11", features = ["native-tls-vendored", "json"] } | ||||
| roxmltree = "0.18" | ||||
| serde = { version = "1.0", features = ["derive"] } | ||||
| serde_json = "1.0" | ||||
| sysinfo = "0.29" | ||||
| tempfile = "3.8" | ||||
| tokio = { version = "1", features = ["full"] } | ||||
| tokio-util = "0.7" | ||||
| url = "2.4" | ||||
| urlencoding = "2.1.3" | ||||
| axum = "0.7" | ||||
| futures = "0.3" | ||||
| futures-util = "0.3" | ||||
| tokio-tungstenite = "0.20.1" | ||||
| specta = "=2.0.0-rc.1" | ||||
| specta-macros = "=2.0.0-rc.1" | ||||
| users = "0.11" | ||||
| whoami = "1" | ||||
| tauri = { version = "1.5" } | ||||
| thiserror = "1" | ||||
| redact-engine = "0.1" | ||||
| dotenvy_macro = "0.15" | ||||
| compile-time = "0.2" | ||||
|  | ||||
| [profile.release] | ||||
| opt-level = 'z'   # Optimize for size | ||||
| lto = true        # Enable link-time optimization | ||||
| codegen-units = 1 # Reduce number of codegen units to increase optimizations | ||||
| panic = 'abort'   # Abort on panic | ||||
| strip = true      # Strip symbols from binary* | ||||
| @@ -1,110 +0,0 @@ | ||||
| include("${CMAKE_SOURCE_DIR}/cmake/Add3rdParty.cmake") | ||||
|  | ||||
| project(GPClient) | ||||
|  | ||||
| set(gpclient_GENERATED_SOURCES) | ||||
|  | ||||
| configure_file(com.yuezk.qt.gpclient.desktop.in com.yuezk.qt.gpclient.desktop) | ||||
| configure_file(com.yuezk.qt.gpclient.metainfo.xml.in com.yuezk.qt.gpclient.metainfo.xml) | ||||
|  | ||||
| qt5_add_dbus_interface( | ||||
|     gpclient_GENERATED_SOURCES | ||||
|     ${CMAKE_BINARY_DIR}/com.yuezk.qt.GPService.xml | ||||
|     gpserviceinterface | ||||
| ) | ||||
|  | ||||
| add_executable(gpclient | ||||
|     cdpcommand.cpp | ||||
|     cdpcommandmanager.cpp | ||||
|     enhancedwebview.cpp | ||||
|     enhancedwebpage.cpp | ||||
|     gatewayauthenticator.cpp | ||||
|     gatewayauthenticatorparams.cpp | ||||
|     gpgateway.cpp | ||||
|     gphelper.cpp | ||||
|     loginparams.cpp | ||||
|     main.cpp | ||||
|     standardloginwindow.cpp | ||||
|     portalauthenticator.cpp | ||||
|     portalconfigresponse.cpp | ||||
|     preloginresponse.cpp | ||||
|     samlloginwindow.cpp | ||||
|     gpclient.cpp | ||||
|     settingsdialog.cpp | ||||
|     gpclient.ui | ||||
|     standardloginwindow.ui | ||||
|     settingsdialog.ui | ||||
|     challengedialog.h | ||||
|     challengedialog.cpp | ||||
|     challengedialog.ui | ||||
|     vpn_dbus.cpp | ||||
|     vpn_json.cpp | ||||
|     resources.qrc | ||||
|     ${gpclient_GENERATED_SOURCES} | ||||
| ) | ||||
|  | ||||
| add_3rdparty( | ||||
|     SingleApplication | ||||
|     GIT_REPOSITORY https://github.com/itay-grudev/SingleApplication.git | ||||
|     GIT_TAG v3.3.0 | ||||
|     CMAKE_ARGS | ||||
|         -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} | ||||
|         -DCMAKE_CXX_FLAGS_RELEASE=${CMAKE_CXX_FLAGS_RELEASE} | ||||
|         -DCMAKE_FIND_ROOT_PATH=${CMAKE_FIND_ROOT_PATH} | ||||
|         -DCMAKE_PREFIX_PATH=$ENV{CMAKE_PREFIX_PATH} | ||||
|         -DQAPPLICATION_CLASS=QApplication | ||||
| ) | ||||
|  | ||||
| add_3rdparty( | ||||
|     plog | ||||
|     GIT_REPOSITORY https://github.com/SergiusTheBest/plog.git | ||||
|     GIT_TAG master | ||||
|     CMAKE_ARGS | ||||
|         -DPLOG_BUILD_SAMPLES=OFF | ||||
|         -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} | ||||
|         -DCMAKE_CXX_FLAGS_RELEASE=${CMAKE_CXX_FLAGS_RELEASE} | ||||
| ) | ||||
|  | ||||
| ExternalProject_Get_Property(SingleApplication-${PROJECT_NAME} SOURCE_DIR BINARY_DIR) | ||||
| set(SingleApplication_INCLUDE_DIR ${SOURCE_DIR}) | ||||
| set(SingleApplication_LIBRARY ${BINARY_DIR}/libSingleApplication.a) | ||||
|  | ||||
| ExternalProject_Get_Property(plog-${PROJECT_NAME} SOURCE_DIR) | ||||
| set(plog_INCLUDE_DIR "${SOURCE_DIR}/include") | ||||
|  | ||||
| add_dependencies(gpclient | ||||
|     SingleApplication-${PROJECT_NAME} | ||||
|     plog-${PROJECT_NAME} | ||||
| ) | ||||
|  | ||||
| target_include_directories(gpclient PRIVATE | ||||
|     ${CMAKE_BINARY_DIR} | ||||
|     ${CMAKE_CURRENT_SOURCE_DIR} | ||||
|     ${CMAKE_CURRENT_BINARY_DIR} | ||||
|     ${SingleApplication_INCLUDE_DIR} | ||||
|     ${plog_INCLUDE_DIR} | ||||
|     ${QTKEYCHAIN_INCLUDE_DIRS}/qt5keychain | ||||
| ) | ||||
|  | ||||
| target_link_libraries(gpclient | ||||
|     ${SingleApplication_LIBRARY} | ||||
|     Qt5::Widgets | ||||
|     Qt5::Network | ||||
|     Qt5::WebSockets | ||||
|     Qt5::WebEngine | ||||
|     Qt5::WebEngineWidgets | ||||
|     Qt5::DBus | ||||
|     QtSignals | ||||
|     ${QTKEYCHAIN_LIBRARIES} | ||||
| ) | ||||
|  | ||||
| if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 8.0 AND CMAKE_BUILD_TYPE STREQUAL Release) | ||||
|     target_compile_options(gpclient PUBLIC "-ffile-prefix-map=${CMAKE_SOURCE_DIR}=.") | ||||
| endif() | ||||
|  | ||||
| target_compile_definitions(gpclient PUBLIC QAPPLICATION_CLASS=QApplication) | ||||
|  | ||||
| install(TARGETS gpclient DESTINATION bin) | ||||
| install(FILES "${CMAKE_CURRENT_BINARY_DIR}/com.yuezk.qt.gpclient.metainfo.xml" DESTINATION share/metainfo) | ||||
| install(FILES "${CMAKE_CURRENT_BINARY_DIR}/com.yuezk.qt.gpclient.desktop" DESTINATION share/applications) | ||||
| install(FILES "com.yuezk.qt.gpclient.svg" DESTINATION share/icons/hicolor/scalable/apps) | ||||
| @@ -1,30 +0,0 @@ | ||||
| #include <QtCore/QVariantMap> | ||||
| #include <QtCore/QJsonDocument> | ||||
| #include <QtCore/QJsonObject> | ||||
|  | ||||
| #include "cdpcommand.h" | ||||
|  | ||||
| CDPCommand::CDPCommand(QObject *parent) : QObject(parent) | ||||
| { | ||||
| } | ||||
|  | ||||
| CDPCommand::CDPCommand(int id, QString cmd, QVariantMap& params) : | ||||
|     QObject(nullptr), | ||||
|     id(id), | ||||
|     cmd(cmd), | ||||
|     params(¶ms) | ||||
| { | ||||
| } | ||||
|  | ||||
| QByteArray CDPCommand::toJson() | ||||
| { | ||||
|     QVariantMap payloadMap; | ||||
|     payloadMap["id"] = id; | ||||
|     payloadMap["method"] = cmd; | ||||
|     payloadMap["params"] = *params; | ||||
|  | ||||
|     QJsonObject payloadJsonObject = QJsonObject::fromVariantMap(payloadMap); | ||||
|     QJsonDocument payloadJson(payloadJsonObject); | ||||
|  | ||||
|     return payloadJson.toJson(); | ||||
| } | ||||
| @@ -1,24 +0,0 @@ | ||||
| #ifndef CDPCOMMAND_H | ||||
| #define CDPCOMMAND_H | ||||
|  | ||||
| #include <QtCore/QObject> | ||||
|  | ||||
| class CDPCommand : public QObject | ||||
| { | ||||
|     Q_OBJECT | ||||
| public: | ||||
|     explicit CDPCommand(QObject *parent = nullptr); | ||||
|     CDPCommand(int id, QString cmd, QVariantMap& params); | ||||
|  | ||||
|     QByteArray toJson(); | ||||
|  | ||||
| signals: | ||||
|     void finished(); | ||||
|  | ||||
| private: | ||||
|     int id; | ||||
|     QString cmd; | ||||
|     QVariantMap *params; | ||||
| }; | ||||
|  | ||||
| #endif // CDPCOMMAND_H | ||||
| @@ -1,87 +0,0 @@ | ||||
| #include <QtCore/QVariantMap> | ||||
| #include <plog/Log.h> | ||||
|  | ||||
| #include "cdpcommandmanager.h" | ||||
|  | ||||
| CDPCommandManager::CDPCommandManager(QObject *parent) | ||||
|     : QObject(parent) | ||||
|     , networkManager(new QNetworkAccessManager) | ||||
|     , socket(new QWebSocket) | ||||
| { | ||||
|     // WebSocket setup | ||||
|     QObject::connect(socket, &QWebSocket::connected, this, &CDPCommandManager::ready); | ||||
|     QObject::connect(socket, &QWebSocket::textMessageReceived, this, &CDPCommandManager::onTextMessageReceived); | ||||
|     QObject::connect(socket, &QWebSocket::disconnected, this, &CDPCommandManager::onSocketDisconnected); | ||||
|     QObject::connect(socket, QOverload<QAbstractSocket::SocketError>::of(&QWebSocket::error), this, &CDPCommandManager::onSocketError); | ||||
| } | ||||
|  | ||||
| CDPCommandManager::~CDPCommandManager() | ||||
| { | ||||
|     delete networkManager; | ||||
|     delete socket; | ||||
| } | ||||
|  | ||||
| void CDPCommandManager::initialize(QString endpoint) | ||||
| { | ||||
|     QNetworkReply *reply = networkManager->get(QNetworkRequest(endpoint)); | ||||
|  | ||||
|     QObject::connect( | ||||
|         reply, &QNetworkReply::finished, | ||||
|         [reply, this]() { | ||||
|             if (reply->error()) { | ||||
|                 LOGE << "CDP request error"; | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             QJsonDocument doc = QJsonDocument::fromJson(reply->readAll()); | ||||
|             QJsonArray pages = doc.array(); | ||||
|             QJsonObject page = pages.first().toObject(); | ||||
|             QString wsUrl = page.value("webSocketDebuggerUrl").toString(); | ||||
|  | ||||
|             socket->open(wsUrl); | ||||
|         } | ||||
|     ); | ||||
| } | ||||
|  | ||||
| CDPCommand *CDPCommandManager::sendCommand(QString cmd) | ||||
| { | ||||
|     QVariantMap emptyParams; | ||||
|     return sendCommend(cmd, emptyParams); | ||||
| } | ||||
|  | ||||
| CDPCommand *CDPCommandManager::sendCommend(QString cmd, QVariantMap ¶ms) | ||||
| { | ||||
|     int id = ++commandId; | ||||
|     CDPCommand *command = new CDPCommand(id, cmd, params); | ||||
|     socket->sendTextMessage(command->toJson()); | ||||
|     commandPool.insert(id, command); | ||||
|  | ||||
|     return command; | ||||
| } | ||||
|  | ||||
| void CDPCommandManager::onTextMessageReceived(QString message) | ||||
| { | ||||
|     QJsonDocument responseDoc = QJsonDocument::fromJson(message.toUtf8()); | ||||
|     QJsonObject response = responseDoc.object(); | ||||
|  | ||||
|     // Response for method | ||||
|     if (response.contains("id")) { | ||||
|         int id = response.value("id").toInt(); | ||||
|         if (commandPool.contains(id)) { | ||||
|             CDPCommand *command = commandPool.take(id); | ||||
|             command->finished(); | ||||
|         } | ||||
|     } else { // Response for event | ||||
|         emit eventReceived(response.value("method").toString(), response.value("params").toObject()); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void CDPCommandManager::onSocketDisconnected() | ||||
| { | ||||
|     LOGI << "WebSocket disconnected"; | ||||
| } | ||||
|  | ||||
| void CDPCommandManager::onSocketError(QAbstractSocket::SocketError error) | ||||
| { | ||||
|     LOGE << "WebSocket error" << error; | ||||
| } | ||||
| @@ -1,40 +0,0 @@ | ||||
| #ifndef CDPCOMMANDMANAGER_H | ||||
| #define CDPCOMMANDMANAGER_H | ||||
|  | ||||
| #include <QtCore/QObject> | ||||
| #include <QtCore/QHash> | ||||
| #include <QtWebSockets/QtWebSockets> | ||||
| #include <QtNetwork/QNetworkAccessManager> | ||||
|  | ||||
| #include "cdpcommand.h" | ||||
|  | ||||
| class CDPCommandManager : public QObject | ||||
| { | ||||
|     Q_OBJECT | ||||
| public: | ||||
|     explicit CDPCommandManager(QObject *parent = nullptr); | ||||
|     ~CDPCommandManager(); | ||||
|  | ||||
|     void initialize(QString endpoint); | ||||
|  | ||||
|     CDPCommand *sendCommand(QString cmd); | ||||
|     CDPCommand *sendCommend(QString cmd, QVariantMap& params); | ||||
|  | ||||
| signals: | ||||
|     void ready(); | ||||
|     void eventReceived(QString eventName, QJsonObject params); | ||||
|  | ||||
| private: | ||||
|     QNetworkAccessManager *networkManager; | ||||
|     QWebSocket *socket; | ||||
|  | ||||
|     int commandId = 0; | ||||
|     QHash<int, CDPCommand*> commandPool; | ||||
|  | ||||
| private slots: | ||||
|     void onTextMessageReceived(QString message); | ||||
|     void onSocketDisconnected(); | ||||
|     void onSocketError(QAbstractSocket::SocketError error); | ||||
| }; | ||||
|  | ||||
| #endif // CDPCOMMANDMANAGER_H | ||||
| @@ -1,38 +0,0 @@ | ||||
| #include <QtWidgets/QDialogButtonBox> | ||||
| #include <QtWidgets/QPushButton> | ||||
|  | ||||
| #include "challengedialog.h" | ||||
| #include "ui_challengedialog.h" | ||||
|  | ||||
| ChallengeDialog::ChallengeDialog(QWidget *parent) : | ||||
|     QDialog(parent), | ||||
|     ui(new Ui::ChallengeDialog) | ||||
| { | ||||
|     ui->setupUi(this); | ||||
|     ui->buttonBox->button(QDialogButtonBox::Ok)->setDisabled(true); | ||||
| } | ||||
|  | ||||
| ChallengeDialog::~ChallengeDialog() | ||||
| { | ||||
|     delete ui; | ||||
| } | ||||
|  | ||||
| void ChallengeDialog::setMessage(const QString &message) | ||||
| { | ||||
|     ui->challengeMessage->setText(message); | ||||
| } | ||||
|  | ||||
| const QString ChallengeDialog::getChallenge() | ||||
| { | ||||
|     return ui->challengeInput->text(); | ||||
| } | ||||
|  | ||||
| void ChallengeDialog::on_challengeInput_textChanged(const QString &value) | ||||
| { | ||||
|     QPushButton *okBtn = ui->buttonBox->button(QDialogButtonBox::Ok); | ||||
|     if (value.isEmpty()) { | ||||
|         okBtn->setDisabled(true); | ||||
|     } else { | ||||
|         okBtn->setEnabled(true); | ||||
|     } | ||||
| } | ||||
| @@ -1,28 +0,0 @@ | ||||
| #ifndef CHALLENGEDIALOG_H | ||||
| #define CHALLENGEDIALOG_H | ||||
|  | ||||
| #include <QDialog> | ||||
|  | ||||
| namespace Ui { | ||||
| class ChallengeDialog; | ||||
| } | ||||
|  | ||||
| class ChallengeDialog : public QDialog | ||||
| { | ||||
|     Q_OBJECT | ||||
|  | ||||
| public: | ||||
|     explicit ChallengeDialog(QWidget *parent = nullptr); | ||||
|     ~ChallengeDialog(); | ||||
|  | ||||
|     void setMessage(const QString &message); | ||||
|     const QString getChallenge(); | ||||
|  | ||||
| private slots: | ||||
|     void on_challengeInput_textChanged(const QString &arg1); | ||||
|  | ||||
| private: | ||||
|     Ui::ChallengeDialog *ui; | ||||
| }; | ||||
|  | ||||
| #endif // CHALLENGEDIALOG_H | ||||
| @@ -1,111 +0,0 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <ui version="4.0"> | ||||
|  <class>ChallengeDialog</class> | ||||
|  <widget class="QDialog" name="ChallengeDialog"> | ||||
|   <property name="geometry"> | ||||
|    <rect> | ||||
|     <x>0</x> | ||||
|     <y>0</y> | ||||
|     <width>405</width> | ||||
|     <height>200</height> | ||||
|    </rect> | ||||
|   </property> | ||||
|   <property name="windowTitle"> | ||||
|    <string>GlobalProtect Challenge</string> | ||||
|   </property> | ||||
|   <property name="modal"> | ||||
|    <bool>true</bool> | ||||
|   </property> | ||||
|   <layout class="QVBoxLayout" name="verticalLayout"> | ||||
|    <item> | ||||
|     <layout class="QVBoxLayout" name="verticalLayout_2" stretch="1,1"> | ||||
|      <item> | ||||
|       <widget class="QLabel" name="label"> | ||||
|        <property name="font"> | ||||
|         <font> | ||||
|          <pointsize>14</pointsize> | ||||
|          <weight>50</weight> | ||||
|          <bold>false</bold> | ||||
|         </font> | ||||
|        </property> | ||||
|        <property name="text"> | ||||
|         <string>Sign In</string> | ||||
|        </property> | ||||
|       </widget> | ||||
|      </item> | ||||
|      <item> | ||||
|       <widget class="QLabel" name="challengeMessage"> | ||||
|        <property name="text"> | ||||
|         <string>Duo two-factor login for [redacted]  Enter a passcode or select one of the following options:   1. Duo Push to XXX-XXX-[redacted]  2. SMS passcodes to XXX-XXX-[redacted]  Passcode or option (1-2): </string> | ||||
|        </property> | ||||
|        <property name="alignment"> | ||||
|         <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set> | ||||
|        </property> | ||||
|        <property name="wordWrap"> | ||||
|         <bool>true</bool> | ||||
|        </property> | ||||
|       </widget> | ||||
|      </item> | ||||
|     </layout> | ||||
|    </item> | ||||
|    <item> | ||||
|     <widget class="QLineEdit" name="challengeInput"> | ||||
|      <property name="echoMode"> | ||||
|       <enum>QLineEdit::Password</enum> | ||||
|      </property> | ||||
|     </widget> | ||||
|    </item> | ||||
|    <item> | ||||
|     <widget class="QDialogButtonBox" name="buttonBox"> | ||||
|      <property name="layoutDirection"> | ||||
|       <enum>Qt::LeftToRight</enum> | ||||
|      </property> | ||||
|      <property name="orientation"> | ||||
|       <enum>Qt::Horizontal</enum> | ||||
|      </property> | ||||
|      <property name="standardButtons"> | ||||
|       <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> | ||||
|      </property> | ||||
|      <property name="centerButtons"> | ||||
|       <bool>false</bool> | ||||
|      </property> | ||||
|     </widget> | ||||
|    </item> | ||||
|   </layout> | ||||
|  </widget> | ||||
|  <resources/> | ||||
|  <connections> | ||||
|   <connection> | ||||
|    <sender>buttonBox</sender> | ||||
|    <signal>accepted()</signal> | ||||
|    <receiver>ChallengeDialog</receiver> | ||||
|    <slot>accept()</slot> | ||||
|    <hints> | ||||
|     <hint type="sourcelabel"> | ||||
|      <x>248</x> | ||||
|      <y>254</y> | ||||
|     </hint> | ||||
|     <hint type="destinationlabel"> | ||||
|      <x>157</x> | ||||
|      <y>274</y> | ||||
|     </hint> | ||||
|    </hints> | ||||
|   </connection> | ||||
|   <connection> | ||||
|    <sender>buttonBox</sender> | ||||
|    <signal>rejected()</signal> | ||||
|    <receiver>ChallengeDialog</receiver> | ||||
|    <slot>reject()</slot> | ||||
|    <hints> | ||||
|     <hint type="sourcelabel"> | ||||
|      <x>316</x> | ||||
|      <y>260</y> | ||||
|     </hint> | ||||
|     <hint type="destinationlabel"> | ||||
|      <x>286</x> | ||||
|      <y>274</y> | ||||
|     </hint> | ||||
|    </hints> | ||||
|   </connection> | ||||
|  </connections> | ||||
| </ui> | ||||
| @@ -1,12 +0,0 @@ | ||||
| [Desktop Entry] | ||||
|  | ||||
| Type=Application | ||||
| Version=1.0 | ||||
| Name=GlobalProtect VPN | ||||
| Comment=A GlobalProtect VPN client (GUI) for Linux based on OpenConnect and built with Qt5, supports SAML auth mode. | ||||
| GenericName=GlobalProtect VPN client, supports SAML auth mode | ||||
| Categories=Network;Dialup; | ||||
| Exec=env QT_AUTO_SCREEN_SCALE_FACTOR=1 @CMAKE_INSTALL_PREFIX@/bin/gpclient | ||||
| Icon=com.yuezk.qt.gpclient | ||||
| Keywords=GlobalProtect;Openconnect;SAML;connection;VPN; | ||||
| StartupWMClass=gpclient | ||||
| @@ -1,43 +0,0 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <component type="desktop-application"> | ||||
|     <id>com.yuezk.qt.gpclient</id> | ||||
|  | ||||
|     <name>globalprotect-openconnect</name> | ||||
|     <summary>A GlobalProtect VPN client powered by OpenConnect</summary> | ||||
|  | ||||
|     <metadata_license>CC0-1.0</metadata_license> | ||||
|     <project_license>AGPL-3.0-or-later</project_license> | ||||
|  | ||||
|     <description> | ||||
|         <p>A GlobalProtect VPN client (GUI) for Linux based on OpenConnect and built with Qt5, supports the SAML auth mode.</p> | ||||
|     </description> | ||||
|  | ||||
|     <categories> | ||||
|         <category>Network</category> | ||||
|     </categories> | ||||
|  | ||||
|     <update_contact>k3vinyue_AT_gmail.com</update_contact> | ||||
|     <developer_name>Kevin Yue</developer_name> | ||||
|  | ||||
|     <url type="homepage">https://github.com/yuezk/GlobalProtect-openconnect</url> | ||||
|     <url type="bugtracker">https://github.com/yuezk/GlobalProtect-openconnect/issues</url> | ||||
|     <url type="help">https://github.com/yuezk/GlobalProtect-openconnect/issues</url> | ||||
|  | ||||
|     <keywords> | ||||
|         <keyword>globalprotect</keyword> | ||||
|         <keyword>openconnect</keyword> | ||||
|         <keyword>vpn</keyword> | ||||
|         <keyword>saml</keyword> | ||||
|     </keywords> | ||||
|  | ||||
|     <launchable type="desktop-id">com.yuezk.qt.gpclient.desktop</launchable> | ||||
|     <screenshots> | ||||
|         <screenshot type="default"> | ||||
|             <image>https://user-images.githubusercontent.com/3297602/133869036-5c02b0d9-c2d9-4f87-8c81-e44f68cfd6ac.png</image> | ||||
|         </screenshot> | ||||
|     </screenshots> | ||||
|     <provides> | ||||
|         <binary>@CMAKE_INSTALL_PREFIX@/bin/gpclient</binary> | ||||
|         <dbus type="system">com.yuezk.qt.GPService</dbus> | ||||
|     </provides> | ||||
| </component> | ||||
| @@ -1,99 +0,0 @@ | ||||
| <?xml version="1.0" encoding="UTF-8" standalone="no"?> | ||||
| <!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  --> | ||||
|  | ||||
| <svg | ||||
|    xmlns:dc="http://purl.org/dc/elements/1.1/" | ||||
|    xmlns:cc="http://creativecommons.org/ns#" | ||||
|    xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" | ||||
|    xmlns:svg="http://www.w3.org/2000/svg" | ||||
|    xmlns="http://www.w3.org/2000/svg" | ||||
|    xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | ||||
|    xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | ||||
|    version="1.1" | ||||
|    id="Layer_1" | ||||
|    x="0px" | ||||
|    y="0px" | ||||
|    viewBox="0 0 96 96" | ||||
|    style="enable-background:new 0 0 96 96;" | ||||
|    xml:space="preserve" | ||||
|    sodipodi:docname="com.yuezk.qt.gpclient.svg" | ||||
|    inkscape:version="0.92.4 5da689c313, 2019-01-14"><metadata | ||||
|    id="metadata14"><rdf:RDF><cc:Work | ||||
|        rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type | ||||
|          rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><defs | ||||
|    id="defs12" /><sodipodi:namedview | ||||
|    pagecolor="#ffffff" | ||||
|    bordercolor="#666666" | ||||
|    borderopacity="1" | ||||
|    objecttolerance="10" | ||||
|    gridtolerance="10" | ||||
|    guidetolerance="10" | ||||
|    inkscape:pageopacity="0" | ||||
|    inkscape:pageshadow="2" | ||||
|    inkscape:window-width="1920" | ||||
|    inkscape:window-height="1006" | ||||
|    id="namedview10" | ||||
|    showgrid="false" | ||||
|    inkscape:zoom="6.9532168" | ||||
|    inkscape:cx="7.9545315" | ||||
|    inkscape:cy="59.062386" | ||||
|    inkscape:window-x="0" | ||||
|    inkscape:window-y="0" | ||||
|    inkscape:window-maximized="1" | ||||
|    inkscape:current-layer="g8499" /> | ||||
| <style | ||||
|    type="text/css" | ||||
|    id="style2"> | ||||
| 	.st0{fill:#2980B9;} | ||||
| 	.st1{fill:#3498DB;} | ||||
| 	.st2{fill:#2ECC71;} | ||||
| 	.st3{fill:#27AE60;} | ||||
| </style> | ||||
|  | ||||
| <g | ||||
|    id="g8499" | ||||
|    transform="matrix(1.3407388,0,0,1.3407388,-16.409202,-16.355463)"><g | ||||
|      id="XMLID_1_"> | ||||
| 	<circle | ||||
|    r="32.5" | ||||
|    cy="48" | ||||
|    cx="48" | ||||
|    class="st0" | ||||
|    id="XMLID_3_" | ||||
|    style="fill:#2980b9" /> | ||||
| 	<path | ||||
|    d="m 48,15.5 v 65 C 65.9,80.5 80.5,65.7 80.5,48 80.5,30 65.9,15.5 48,15.5 Z" | ||||
|    class="st1" | ||||
|    id="XMLID_4_" | ||||
|    inkscape:connector-curvature="0" | ||||
|    style="fill:#3498db" /> | ||||
| 	<path | ||||
|    d="m 48,15.5 v 0.6 l 1.2,-0.3 c 0.3,-0.3 0.4,-0.3 0.6,-0.3 h -1.1 z m 7.3,0.9 c -0.1,0 0.4,0.9 1.1,1.8 0.8,1.5 1.1,2.1 1.3,2.1 0.3,-0.3 1.9,-1.2 3,-2.1 -1.7,-0.9 -3.5,-1.5 -5.4,-1.8 z m 10.3,6.2 c -0.1,0 -0.4,0 -0.9,0.6 l -0.8,0.9 0.6,0.6 c 0.3,0.6 0.8,0.9 1,1.2 0.5,0.6 0.6,0.6 0.1,1.5 -0.2,0.6 -0.3,0.9 -0.3,0.9 0.1,0.3 0.3,0.3 1.4,0.3 h 1.6 c 0.1,0 0.3,-0.6 0.4,-1.2 l 0.1,-0.9 -1.1,-0.9 c -1,-0.9 -1,-0.9 -1.4,-1.8 -0.3,-0.6 -0.6,-1.2 -0.7,-1.2 z m -3,2.4 c -0.2,0 -1.3,2.1 -1.3,2.4 0,0 0.3,0.6 0.7,0.9 0.4,0.3 0.7,0.6 0.7,0.6 0.1,0 1.2,-1.2 1.4,-1.5 C 64.2,27.1 64,26.8 63.5,26.2 63.1,25.5 62.7,25 62.6,25 Z m 9.5,1.1 0.2,0.3 c 0,0.3 -0.7,0.9 -1.4,1.5 -1.2,0.9 -1.4,1.2 -2,1.2 -0.6,0 -0.9,0.3 -1.8,0.9 -0.6,0.6 -1.2,0.9 -1.2,1.2 0,0 0.2,0.3 0.6,0.9 0.7,0.6 0.7,0.9 0.2,1.8 l -0.4,0.3 h -1.1 c -0.6,0 -1.5,0 -1.8,-0.3 -0.9,0 -0.8,0 -0.1,2.1 1,3 1.1,3.2 1.3,3.2 0.1,0 1.3,-1.2 2.8,-2.4 1.5,-1.2 2.7,-2.4 2.8,-2.4 l 0.6,0.3 c 0.4,0.3 0.5,0 1.3,-0.6 l 0.8,-0.6 0.8,0.6 c 1.9,1.2 2.2,1.5 2.3,2.4 0.2,1.5 0.3,1.8 0.5,1.8 0.1,0 1.3,-1.5 1.6,-1.8 0.1,-0.3 -0.1,-0.6 -1.1,-2.1 -0.7,-0.9 -1.1,-1.8 -1.1,-2.1 0,0 0.1,0 0.3,-0.3 0.2,0 0.4,0.3 1,0.9 -1.6,-2.3 -3.2,-4.7 -5.1,-6.8 z m 2.8,10.7 c -0.2,0 -0.9,0.9 -0.8,1.2 l 0.5,0.3 H 75 c 0.2,0 0.3,0 0.2,-0.3 C 75.1,37.4 75,36.8 74.9,36.8 Z M 72.3,38 h -2.4 l -2.4,0.3 -4.5,3.5 -4.4,3.8 v 3.5 c 0,2.1 0,3.8 0.1,3.8 0.1,0 0.7,0.9 1.5,1.5 0.8,0.9 1.5,1.5 1.8,1.8 0.4,0.3 0.5,0.3 4,0.6 l 3.4,0.3 1.6,0.9 c 0.8,0.6 1.5,1.2 1.6,1.2 0.1,0 -0.3,0.3 -0.6,0.6 l -0.6,0.6 1,1.2 c 0.5,0.6 1.3,1.5 1.7,1.8 l 0.6,0.9 v 1.7 0.9 c 3.7,-5 5.9,-11.5 6.1,-18.3 0.1,-2.7 -0.3,-5.3 -0.8,-8 l -0.6,-0.3 c -0.1,0 -0.5,0.3 -1,0.6 -0.5,0.3 -1,0.9 -1.1,0.9 -0.1,0 -0.8,-0.3 -1.8,-0.6 l -1.8,-0.6 v -0.9 c 0,-0.6 0,-0.9 -0.6,-1.5 z M 48,63.7 V 64 h 0.2 z" | ||||
|    class="st2" | ||||
|    id="XMLID_13_" | ||||
|    inkscape:connector-curvature="0" | ||||
|    style="fill:#2ecc71" /> | ||||
| 	<path | ||||
|    d="m 48,15.5 c -3.1,0 -6.2,0.5 -9,1.3 0.3,0.4 0.3,0.4 0.6,0.9 1.5,2.5 1.7,2.8 2.1,2.9 0.3,0 0.9,0.1 1.6,0.1 h 1.2 l 0.9,-2 0.8,-1.9 1.8,-0.6 z m -16.9,4.7 c -2.8,1.7 -5.4,3.9 -7.6,6.4 -3.8,4.3 -6.3,9.6 -7.4,15.4 0.5,0 0.9,-0.1 1.8,-0.1 2.8,0.1 2.5,0 3.4,1.4 0.5,0.8 0.6,0.8 1.4,0.8 1,0.1 0.9,0 0.5,-1.6 -0.2,-0.6 -0.3,-1.2 -0.3,-1.4 0,-0.2 0.5,-0.7 1.7,-1.6 1.9,-1.5 1.8,-1.3 1.5,-2.9 -0.1,-0.3 0.1,-0.6 0.6,-1.2 0.7,-0.7 0.7,-0.6 1.4,-0.6 h 0.7 l 0.1,-1.2 c 0.1,-0.7 0.1,-1.3 0.2,-1.3 0,0 1.9,-1.1 4.1,-2.3 2.2,-1.2 4.1,-2.2 4.2,-2.3 0.2,-0.2 -0.3,-0.8 -2.7,-3.8 -1.5,-1.9 -2.8,-3.6 -2.9,-3.7 z m -5.8,23 c -0.1,0 -0.1,0.3 -0.1,0.6 0,0.6 0,0.7 0.6,1 0.8,0.4 0.9,0.5 0.8,0.2 -0.1,-0.4 -1.2,-1.9 -1.3,-1.8 z m -3.4,2.1 -0.5,1.8 c 0.1,0.1 0.9,0.3 1.8,0.5 1,0.2 1.6,0.4 1.8,0.3 l 0.5,-1.3 z m -3.8,1 -1.1,0.6 c -0.6,0.3 -1.2,0.6 -1.4,0.6 h -0.1 c 0,1.4 0.1,2.8 0.3,4.2 l 0.6,0.4 1,-0.1 h 1 l 0.6,1.4 c 0.3,0.7 0.7,1.4 0.8,1.5 0.1,0.1 1,0.1 1.8,0.1 h 1.5 L 23,56.2 c 0,1.2 0,1.3 -0.6,2.2 -0.4,0.5 -0.6,1.2 -0.6,1.4 0,0.2 0.7,2.1 1.6,4.3 l 1.5,4 1.6,0.8 c 1.2,0.6 1.5,0.8 1.5,1 0,0.1 -0.4,2.1 -0.6,3.1 3,2.5 6.4,4.5 10.2,5.8 3.5,-3.6 6.8,-7.1 7.3,-7.6 l 0.7,-0.7 0.2,-1.9 c 0.2,-1.1 0.4,-2.1 0.4,-2.2 0,-0.1 0.5,-0.6 1,-1.2 0.5,-0.5 0.8,-1 0.8,-1.1 v -0.2 c -0.1,-0.1 -1.4,-1.1 -3,-2.2 l -3.1,-2.1 -1.1,-0.1 c -0.8,0 -1.2,0 -1.3,-0.2 C 39.4,59.2 39.2,58.5 39.1,57.7 39,56.9 38.9,56.2 38.8,56.1 38.8,56 38,56 37.1,56 36.2,56 35.4,55.9 35.3,55.8 35.2,55.7 35.2,55.1 35.1,54.3 35,53.6 34.9,53 34.8,52.9 34.7,52.8 33.7,52.7 32.5,52.6 30.5,52.5 30.1,52.5 29.1,52 l -1.2,-0.6 -1.6,0.7 -1.7,0.9 -1.8,-0.1 c -2,0 -1.9,0.2 -2.1,-1.6 C 20.6,50.7 20.6,50.1 20.5,50.1 20.4,50 20,50 19.6,49.9 L 18.9,49.7 19,49.2 c 0,-0.3 0,-1 0.1,-1.4 L 19.2,47 18.7,46.5 Z m 9.1,1.1 C 27.1,47.5 27.1,47.8 27,48 l -0.1,0.5 2.9,1.2 c 2.9,1.1 3.4,1.2 3.9,0.7 0.2,-0.2 0.1,-0.2 -0.3,-0.4 -0.3,-0.1 -1.7,-0.9 -3.2,-1.6 -1.7,-0.7 -2.9,-1.1 -3,-1 z" | ||||
|    class="st3" | ||||
|    id="XMLID_20_" | ||||
|    inkscape:connector-curvature="0" | ||||
|    style="fill:#27ae60" /> | ||||
| </g><g | ||||
|      transform="matrix(1.458069,0,0,1.458069,-22.631538,-19.615144)" | ||||
|      id="g7664"><path | ||||
|        inkscape:connector-curvature="0" | ||||
|        id="XMLID_6_" | ||||
|        class="st3" | ||||
|        d="m 38.8,56.1 c 0,1.2 1,2.2 2.2,2.2 h 15.2 c 1.2,0 2.2,-1 2.2,-2.2 V 45.3 c 0,-1.2 -1,-2.2 -2.2,-2.2 H 40.9 c -1.2,0 -2.2,1 -2.2,2.2 v 10.8 z" | ||||
|        style="fill:#f1aa27;fill-opacity:1" /><path | ||||
|        style="fill:#e6e6e6" | ||||
|        inkscape:connector-curvature="0" | ||||
|        id="XMLID_7_" | ||||
|        class="st4" | ||||
|        d="m 55.5,43.1 h -3.3 v -3.7 c 0,-2.1 -1.7,-3.8 -3.8,-3.8 -2.1,0 -3.8,1.7 -3.8,3.8 v 3.8 h -3.1 v -3.8 c 0,-3.9 3.2,-7 7,-7 3.9,0 7,3.2 7,7 z" /><path | ||||
|        style="fill:#e6e6e6;fill-opacity:1" | ||||
|        inkscape:connector-curvature="0" | ||||
|        id="XMLID_8_" | ||||
|        class="st5" | ||||
|        d="m 50.35,48.2 c 0,-1 -0.8,-1.8 -1.8,-1.8 -1,0 -1.8,0.8 -1.8,1.8 0,0.7 0.4,1.3 1,1.6 l -1,5.2 h 3.6 l -1,-5.2 c 0.6,-0.3 1,-0.9 1,-1.6 z" /></g></g></svg> | ||||
| Before Width: | Height: | Size: 6.7 KiB | 
| Before Width: | Height: | Size: 18 KiB | 
| @@ -1,8 +0,0 @@ | ||||
| #include "enhancedwebpage.h" | ||||
| #include <QWebEngineCertificateError> | ||||
| #include <plog/Log.h> | ||||
|  | ||||
| bool EnhancedWebPage::certificateError(const QWebEngineCertificateError &certificateError) { | ||||
|     LOGI << "An error occurred during certificate verification for " << certificateError.url().toString() << "; " << certificateError.errorDescription(); | ||||
|     return certificateError.isOverridable(); | ||||
| }; | ||||
| @@ -1,12 +0,0 @@ | ||||
| #ifndef ENHANCEDWEBPAGE_H | ||||
| #define ENHANCEDWEBPAGE_H | ||||
|  | ||||
| #include <QtWebEngineWidgets/qwebenginepage.h> | ||||
|  | ||||
| class EnhancedWebPage : public QWebEnginePage | ||||
| { | ||||
| protected: | ||||
|     bool certificateError(const QWebEngineCertificateError &certificateError) override; | ||||
| }; | ||||
|  | ||||
| #endif // !ECHANCEDWEBPAG | ||||
| @@ -1,33 +0,0 @@ | ||||
| #include <QtCore/QProcessEnvironment> | ||||
| #include <QtWebEngineWidgets/QWebEngineView> | ||||
|  | ||||
| #include "enhancedwebpage.h" | ||||
| #include "enhancedwebview.h" | ||||
| #include "cdpcommandmanager.h" | ||||
|  | ||||
| EnhancedWebView::EnhancedWebView(QWidget *parent) | ||||
|     : QWebEngineView(parent) | ||||
|     , cdp(new CDPCommandManager) | ||||
| { | ||||
|    QObject::connect(cdp, &CDPCommandManager::ready, this, &EnhancedWebView::onCDPReady); | ||||
|    QObject::connect(cdp, &CDPCommandManager::eventReceived, this, &EnhancedWebView::onEventReceived); | ||||
| } | ||||
|  | ||||
| void EnhancedWebView::initialize() | ||||
| { | ||||
|     setPage(new EnhancedWebPage()); | ||||
|     auto port = QProcessEnvironment::systemEnvironment().value(ENV_CDP_PORT); | ||||
|     cdp->initialize("http://127.0.0.1:" + port + "/json"); | ||||
| } | ||||
|  | ||||
| void EnhancedWebView::onCDPReady() | ||||
| { | ||||
|     cdp->sendCommand("Network.enable"); | ||||
| } | ||||
|  | ||||
| void EnhancedWebView::onEventReceived(QString eventName, QJsonObject params) | ||||
| { | ||||
|     if (eventName == "Network.responseReceived") { | ||||
|         emit responseReceived(params); | ||||
|     } | ||||
| } | ||||
| @@ -1,29 +0,0 @@ | ||||
| #ifndef ENHANCEDWEBVIEW_H | ||||
| #define ENHANCEDWEBVIEW_H | ||||
|  | ||||
| #include <QtWebEngineWidgets/QWebEngineView> | ||||
|  | ||||
| #include "cdpcommandmanager.h" | ||||
|  | ||||
| #define ENV_CDP_PORT "QTWEBENGINE_REMOTE_DEBUGGING" | ||||
|  | ||||
| class EnhancedWebView : public QWebEngineView | ||||
| { | ||||
|     Q_OBJECT | ||||
| public: | ||||
|     explicit EnhancedWebView(QWidget *parent = nullptr); | ||||
|  | ||||
|     void initialize(); | ||||
|  | ||||
| signals: | ||||
|     void responseReceived(QJsonObject params); | ||||
|  | ||||
| private slots: | ||||
|     void onCDPReady(); | ||||
|     void onEventReceived(QString eventName, QJsonObject params); | ||||
|  | ||||
| private: | ||||
|     CDPCommandManager *cdp { nullptr }; | ||||
| }; | ||||
|  | ||||
| #endif // ENHANCEDWEBVIEW_H | ||||
| @@ -1,226 +0,0 @@ | ||||
| #include <QtNetwork/QNetworkReply> | ||||
| #include <QtCore/QRegularExpression> | ||||
| #include <QtCore/QRegularExpressionMatch> | ||||
| #include <plog/Log.h> | ||||
|  | ||||
| #include "gatewayauthenticator.h" | ||||
| #include "gphelper.h" | ||||
| #include "loginparams.h" | ||||
| #include "preloginresponse.h" | ||||
| #include "challengedialog.h" | ||||
|  | ||||
| using namespace gpclient::helper; | ||||
|  | ||||
| GatewayAuthenticator::GatewayAuthenticator(const QString& gateway, GatewayAuthenticatorParams params) | ||||
|     : QObject() | ||||
|     , gateway(gateway) | ||||
|     , params(params) | ||||
|     , preloginUrl("https://" + gateway + "/ssl-vpn/prelogin.esp?tmp=tmp&kerberos-support=yes&ipv6-support=yes&clientVer=4100") | ||||
|     , loginUrl("https://" + gateway + "/ssl-vpn/login.esp") | ||||
| { | ||||
|     if (!params.clientos().isEmpty()) { | ||||
|         preloginUrl = preloginUrl + "&clientos=" + params.clientos(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void GatewayAuthenticator::authenticate() | ||||
| { | ||||
|     LOGI << "Start gateway authentication..."; | ||||
|  | ||||
|     LoginParams loginParams { params.clientos() }; | ||||
|     loginParams.setUser(params.username()); | ||||
|     loginParams.setPassword(params.password()); | ||||
|     loginParams.setUserAuthCookie(params.userAuthCookie()); | ||||
|     loginParams.setInputStr(params.inputStr()); | ||||
|  | ||||
|     login(loginParams); | ||||
| } | ||||
|  | ||||
| void GatewayAuthenticator::login(const LoginParams &loginParams) | ||||
| { | ||||
|     LOGI << QString("Trying to login the gateway at %1, with %2").arg(loginUrl).arg(QString(loginParams.toUtf8())); | ||||
|  | ||||
|     auto *reply = createRequest(loginUrl, loginParams.toUtf8()); | ||||
|     connect(reply, &QNetworkReply::finished, this, &GatewayAuthenticator::onLoginFinished); | ||||
| } | ||||
|  | ||||
| void GatewayAuthenticator::onLoginFinished() | ||||
| { | ||||
|     QNetworkReply *reply = qobject_cast<QNetworkReply*>(sender()); | ||||
|     QByteArray response = reply->readAll(); | ||||
|  | ||||
|     if (reply->error() || response.contains("Authentication failure")) { | ||||
|         LOGE << QString("Failed to login the gateway at %1, %2").arg(loginUrl, reply->errorString()); | ||||
|  | ||||
|         if (standardLoginWindow) { | ||||
|             standardLoginWindow->setProcessing(false); | ||||
|             openMessageBox("Gateway login failed.", "Please check your credentials and try again."); | ||||
|         } else { | ||||
|             doAuth(); | ||||
|         } | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     // 2FA | ||||
|     if (response.contains("Challenge")) { | ||||
|         LOGI << "The server need input the challenge..."; | ||||
|         showChallenge(response); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     if (standardLoginWindow) { | ||||
|         standardLoginWindow->close(); | ||||
|     } | ||||
|  | ||||
|     const auto params = gpclient::helper::parseGatewayResponse(response); | ||||
|     emit success(params.toString()); | ||||
| } | ||||
|  | ||||
| void GatewayAuthenticator::doAuth() | ||||
| { | ||||
|     LOGI << "Perform the gateway prelogin at " << preloginUrl; | ||||
|  | ||||
|     auto *reply = createRequest(preloginUrl); | ||||
|     connect(reply, &QNetworkReply::finished, this, &GatewayAuthenticator::onPreloginFinished); | ||||
| } | ||||
|  | ||||
| void GatewayAuthenticator::onPreloginFinished() | ||||
| { | ||||
|     auto *reply = qobject_cast<QNetworkReply*>(sender()); | ||||
|  | ||||
|     if (reply->error()) { | ||||
|         LOGE << QString("Failed to prelogin the gateway at %1, %2").arg(preloginUrl, reply->errorString()); | ||||
|  | ||||
|         emit fail("Error occurred on the gateway prelogin interface."); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     LOGI << "Gateway prelogin succeeded."; | ||||
|  | ||||
|     auto response = PreloginResponse::parse(reply->readAll()); | ||||
|  | ||||
|     if (response.hasSamlAuthFields()) { | ||||
|         samlAuth(response.samlMethod(), response.samlRequest(), reply->url().toString()); | ||||
|     } else if (response.hasNormalAuthFields()) { | ||||
|         normalAuth(response.labelUsername(), response.labelPassword(), response.authMessage()); | ||||
|     } else { | ||||
|         LOGE << QString("Unknown prelogin response for %1, got %2").arg(preloginUrl, QString::fromUtf8(response.rawResponse())); | ||||
|         emit fail("Unknown response for gateway prelogin interface."); | ||||
|     } | ||||
|  | ||||
|     delete reply; | ||||
| } | ||||
|  | ||||
| void GatewayAuthenticator::normalAuth(QString labelUsername, QString labelPassword, QString authMessage) | ||||
| { | ||||
|     LOGI << QString("Trying to perform the normal login with %1 / %2 credentials").arg(labelUsername, labelPassword); | ||||
|  | ||||
|     standardLoginWindow = new StandardLoginWindow {gateway, labelUsername, labelPassword, authMessage}; | ||||
|  | ||||
|     // Do login | ||||
|     connect(standardLoginWindow, &StandardLoginWindow::performLogin, this, &GatewayAuthenticator::onPerformStandardLogin); | ||||
|     connect(standardLoginWindow, &StandardLoginWindow::rejected, this, &GatewayAuthenticator::onLoginWindowRejected); | ||||
|     connect(standardLoginWindow, &StandardLoginWindow::finished, this, &GatewayAuthenticator::onLoginWindowFinished); | ||||
|  | ||||
|     standardLoginWindow->show(); | ||||
| } | ||||
|  | ||||
| void GatewayAuthenticator::onPerformStandardLogin(const QString &username, const QString &password) | ||||
| { | ||||
|     LOGI << "Start to perform normal login..."; | ||||
|  | ||||
|     standardLoginWindow->setProcessing(true); | ||||
|     params.setUsername(username); | ||||
|     params.setPassword(password); | ||||
|  | ||||
|     authenticate(); | ||||
| } | ||||
|  | ||||
| void GatewayAuthenticator::onLoginWindowRejected() | ||||
| { | ||||
|     emit fail(); | ||||
| } | ||||
|  | ||||
| void GatewayAuthenticator::onLoginWindowFinished() | ||||
| { | ||||
|     delete standardLoginWindow; | ||||
|     standardLoginWindow = nullptr; | ||||
| } | ||||
|  | ||||
| void GatewayAuthenticator::samlAuth(QString samlMethod, QString samlRequest, QString preloginUrl) | ||||
| { | ||||
|     LOGI << "Trying to perform SAML login with saml-method " << samlMethod; | ||||
|  | ||||
|     auto *loginWindow = new SAMLLoginWindow; | ||||
|  | ||||
|     connect(loginWindow, &SAMLLoginWindow::success, [this, loginWindow](const QMap<QString, QString> &samlResult) { | ||||
|         this->onSAMLLoginSuccess(samlResult); | ||||
|         loginWindow->deleteLater(); | ||||
|     }); | ||||
|     connect(loginWindow, &SAMLLoginWindow::fail, [this, loginWindow](const QString &code, const QString &error) { | ||||
|         this->onSAMLLoginFail(code, error); | ||||
|         loginWindow->deleteLater(); | ||||
|     }); | ||||
|     connect(loginWindow, &SAMLLoginWindow::rejected, [this, loginWindow]() { | ||||
|         this->onLoginWindowRejected(); | ||||
|         loginWindow->deleteLater(); | ||||
|     }); | ||||
|  | ||||
|     loginWindow->login(samlMethod, samlRequest, preloginUrl); | ||||
| } | ||||
|  | ||||
| void GatewayAuthenticator::onSAMLLoginSuccess(const QMap<QString, QString> &samlResult) | ||||
| { | ||||
|     if (samlResult.contains("preloginCookie")) { | ||||
|         LOGI << "SAML login succeeded, got the prelogin-cookie " << samlResult.value("preloginCookie"); | ||||
|     } else { | ||||
|         LOGI << "SAML login succeeded, got the portal-userauthcookie " << samlResult.value("userAuthCookie"); | ||||
|     } | ||||
|  | ||||
|     LoginParams loginParams { params.clientos() }; | ||||
|     loginParams.setUser(samlResult.value("username")); | ||||
|     loginParams.setPreloginCookie(samlResult.value("preloginCookie")); | ||||
|     loginParams.setUserAuthCookie(samlResult.value("userAuthCookie")); | ||||
|  | ||||
|     login(loginParams); | ||||
| } | ||||
|  | ||||
| void GatewayAuthenticator::onSAMLLoginFail(const QString &code, const QString &msg) | ||||
| { | ||||
|     emit fail(msg); | ||||
| } | ||||
|  | ||||
| void GatewayAuthenticator::showChallenge(const QString &responseText) | ||||
| { | ||||
|     QRegularExpression re("\"(.*?)\";"); | ||||
|     QRegularExpressionMatchIterator i = re.globalMatch(responseText); | ||||
|  | ||||
|     i.next(); // Skip the status value | ||||
|     QString message = i.next().captured(1); | ||||
|     QString inputStr = i.next().captured(1); | ||||
|     // update the inputSrc field | ||||
|     params.setInputStr(inputStr); | ||||
|  | ||||
|     challengeDialog = new ChallengeDialog; | ||||
|     challengeDialog->setMessage(message); | ||||
|  | ||||
|     connect(challengeDialog, &ChallengeDialog::accepted, this, [this] { | ||||
|         params.setPassword(challengeDialog->getChallenge()); | ||||
|         LOGI << "Challenge submitted, try to re-authenticate..."; | ||||
|         authenticate(); | ||||
|     }); | ||||
|  | ||||
|     connect(challengeDialog, &ChallengeDialog::rejected, this, [this] { | ||||
|         if (standardLoginWindow) { | ||||
|             standardLoginWindow->close(); | ||||
|         } | ||||
|         emit fail(); | ||||
|     }); | ||||
|  | ||||
|     connect(challengeDialog, &ChallengeDialog::finished, this, [this] { | ||||
|         delete challengeDialog; | ||||
|         challengeDialog = nullptr; | ||||
|     }); | ||||
|  | ||||
|     challengeDialog->show(); | ||||
| } | ||||
| @@ -1,48 +0,0 @@ | ||||
| #ifndef GATEWAYAUTHENTICATOR_H | ||||
| #define GATEWAYAUTHENTICATOR_H | ||||
|  | ||||
| #include <QtCore/QObject> | ||||
|  | ||||
| #include "standardloginwindow.h" | ||||
| #include "challengedialog.h" | ||||
| #include "loginparams.h" | ||||
| #include "gatewayauthenticatorparams.h" | ||||
|  | ||||
| class GatewayAuthenticator : public QObject | ||||
| { | ||||
|     Q_OBJECT | ||||
| public: | ||||
|     explicit GatewayAuthenticator(const QString &gateway, GatewayAuthenticatorParams params); | ||||
|  | ||||
|     void authenticate(); | ||||
|  | ||||
| signals: | ||||
|     void success(const QString &authCookie); | ||||
|     void fail(const QString &msg = ""); | ||||
|  | ||||
| private slots: | ||||
|     void onLoginFinished(); | ||||
|     void onPreloginFinished(); | ||||
|     void onPerformStandardLogin(const QString &username, const QString &password); | ||||
|     void onLoginWindowRejected(); | ||||
|     void onLoginWindowFinished(); | ||||
|     void onSAMLLoginSuccess(const QMap<QString, QString> &samlResult); | ||||
|     void onSAMLLoginFail(const QString &code, const QString &msg); | ||||
|  | ||||
| private: | ||||
|     QString gateway; | ||||
|     GatewayAuthenticatorParams params; | ||||
|     QString preloginUrl; | ||||
|     QString loginUrl; | ||||
|  | ||||
|     StandardLoginWindow *standardLoginWindow { nullptr }; | ||||
|     ChallengeDialog *challengeDialog { nullptr }; | ||||
|  | ||||
|     void login(const LoginParams& loginParams); | ||||
|     void doAuth(); | ||||
|     void normalAuth(QString labelUsername, QString labelPassword, QString authMessage); | ||||
|     void samlAuth(QString samlMethod, QString samlRequest, QString preloginUrl = ""); | ||||
|     void showChallenge(const QString &responseText); | ||||
| }; | ||||
|  | ||||
| #endif // GATEWAYAUTHENTICATOR_H | ||||
| @@ -1,66 +0,0 @@ | ||||
| #include "gatewayauthenticatorparams.h" | ||||
|  | ||||
| GatewayAuthenticatorParams::GatewayAuthenticatorParams() | ||||
| { | ||||
|  | ||||
| } | ||||
|  | ||||
| GatewayAuthenticatorParams GatewayAuthenticatorParams::fromPortalConfigResponse(const PortalConfigResponse &portalConfig) | ||||
| { | ||||
|     GatewayAuthenticatorParams params; | ||||
|     params.setUsername(portalConfig.username()); | ||||
|     params.setPassword(portalConfig.password()); | ||||
|     params.setUserAuthCookie(portalConfig.userAuthCookie()); | ||||
|  | ||||
|     return params; | ||||
| } | ||||
|  | ||||
| const QString &GatewayAuthenticatorParams::username() const | ||||
| { | ||||
|     return m_username; | ||||
| } | ||||
|  | ||||
| void GatewayAuthenticatorParams::setUsername(const QString &newUsername) | ||||
| { | ||||
|     m_username = newUsername; | ||||
| } | ||||
|  | ||||
| const QString &GatewayAuthenticatorParams::password() const | ||||
| { | ||||
|     return m_password; | ||||
| } | ||||
|  | ||||
| void GatewayAuthenticatorParams::setPassword(const QString &newPassword) | ||||
| { | ||||
|     m_password = newPassword; | ||||
| } | ||||
|  | ||||
| const QString &GatewayAuthenticatorParams::userAuthCookie() const | ||||
| { | ||||
|     return m_userAuthCookie; | ||||
| } | ||||
|  | ||||
| void GatewayAuthenticatorParams::setUserAuthCookie(const QString &newUserAuthCookie) | ||||
| { | ||||
|     m_userAuthCookie = newUserAuthCookie; | ||||
| } | ||||
|  | ||||
| const QString &GatewayAuthenticatorParams::clientos() const | ||||
| { | ||||
|     return m_clientos; | ||||
| } | ||||
|  | ||||
| void GatewayAuthenticatorParams::setClientos(const QString &newClientos) | ||||
| { | ||||
|     m_clientos = newClientos; | ||||
| } | ||||
|  | ||||
| const QString &GatewayAuthenticatorParams::inputStr() const | ||||
| { | ||||
|     return m_inputStr; | ||||
| } | ||||
|  | ||||
| void GatewayAuthenticatorParams::setInputStr(const QString &inputStr) | ||||
| { | ||||
|     m_inputStr = inputStr; | ||||
| } | ||||
| @@ -1,38 +0,0 @@ | ||||
| #ifndef GATEWAYAUTHENTICATORPARAMS_H | ||||
| #define GATEWAYAUTHENTICATORPARAMS_H | ||||
|  | ||||
| #include <QtCore/QString> | ||||
|  | ||||
| #include "portalconfigresponse.h" | ||||
|  | ||||
| class GatewayAuthenticatorParams | ||||
| { | ||||
| public: | ||||
|     GatewayAuthenticatorParams(); | ||||
|  | ||||
|     static GatewayAuthenticatorParams fromPortalConfigResponse(const PortalConfigResponse &portalConfig); | ||||
|  | ||||
|     const QString &username() const; | ||||
|     void setUsername(const QString &newUsername); | ||||
|  | ||||
|     const QString &password() const; | ||||
|     void setPassword(const QString &newPassword); | ||||
|  | ||||
|     const QString &userAuthCookie() const; | ||||
|     void setUserAuthCookie(const QString &newUserAuthCookie); | ||||
|  | ||||
|     const QString &clientos() const; | ||||
|     void setClientos(const QString &newClientos); | ||||
|  | ||||
|     const QString &inputStr() const; | ||||
|     void setInputStr(const QString &inputStr); | ||||
|  | ||||
| private: | ||||
|     QString m_username; | ||||
|     QString m_password; | ||||
|     QString m_userAuthCookie; | ||||
|     QString m_clientos; | ||||
|     QString m_inputStr; | ||||
| }; | ||||
|  | ||||
| #endif // GATEWAYAUTHENTICATORPARAMS_H | ||||
| @@ -1,519 +0,0 @@ | ||||
| #include <QtGui/QIcon> | ||||
| #include <plog/Log.h> | ||||
|  | ||||
| #include "gpclient.h" | ||||
| #include "gphelper.h" | ||||
| #include "ui_gpclient.h" | ||||
| #include "portalauthenticator.h" | ||||
| #include "gatewayauthenticator.h" | ||||
| #include "settingsdialog.h" | ||||
| #include "gatewayauthenticatorparams.h" | ||||
|  | ||||
| using namespace gpclient::helper; | ||||
|  | ||||
| GPClient::GPClient(QWidget *parent, IVpn *vpn) | ||||
|     : QMainWindow(parent) | ||||
|     , ui(new Ui::GPClient) | ||||
|     , vpn(vpn) | ||||
|     , settingsDialog(new SettingsDialog(this)) | ||||
| { | ||||
|     ui->setupUi(this); | ||||
|  | ||||
|     setWindowTitle("GlobalProtect"); | ||||
|     setFixedSize(width(), height()); | ||||
|     gpclient::helper::moveCenter(this); | ||||
|  | ||||
|     setupSettings(); | ||||
|  | ||||
|     // Restore portal from the previous settings | ||||
|     this->portal(settings::get("portal", "").toString()); | ||||
|  | ||||
|     // DBus service setup | ||||
|     QObject *ov = dynamic_cast<QObject*>(vpn); | ||||
|     connect(ov, SIGNAL(connected()), this, SLOT(onVPNConnected())); | ||||
|     connect(ov, SIGNAL(disconnected()), this, SLOT(onVPNDisconnected())); | ||||
|     connect(ov, SIGNAL(error(QString)), this, SLOT(onVPNError(QString))); | ||||
|     connect(ov, SIGNAL(logAvailable(QString)), this, SLOT(onVPNLogAvailable(QString))); | ||||
|  | ||||
|     // Initialize the context menu of system tray. | ||||
|     initSystemTrayIcon(); | ||||
|     initVpnStatus(); | ||||
| } | ||||
|  | ||||
| void GPClient::setupSettings() | ||||
| { | ||||
|     settingsButton = new QPushButton(this); | ||||
|     settingsButton->setIcon(QIcon(":/images/settings_icon.png")); | ||||
|     settingsButton->setFixedSize(QSize(28, 28)); | ||||
|  | ||||
|     QRect rect = this->geometry(); | ||||
|     settingsButton->setGeometry( | ||||
|                 rect.width() - settingsButton->width() - 15, | ||||
|                 15, | ||||
|                 settingsButton->geometry().width(), | ||||
|                 settingsButton->geometry().height() | ||||
|                 ); | ||||
|  | ||||
|     connect(settingsButton, &QPushButton::clicked, this, &GPClient::onSettingsButtonClicked); | ||||
|     connect(settingsDialog, &QDialog::accepted, this, &GPClient::onSettingsAccepted); | ||||
| } | ||||
|  | ||||
| void GPClient::onSettingsButtonClicked() | ||||
| { | ||||
|     settingsDialog->setClientos(settings::get("clientos", "Linux").toString()); | ||||
|     settingsDialog->setOsVersion(settings::get("os-version", QSysInfo::prettyProductName()).toString()); | ||||
|     settingsDialog->show(); | ||||
| } | ||||
|  | ||||
| void GPClient::onSettingsAccepted() | ||||
| { | ||||
|     settings::save("clientos", settingsDialog->clientos()); | ||||
|     settings::save("os-version", settingsDialog->osVersion()); | ||||
| } | ||||
|  | ||||
| void GPClient::on_connectButton_clicked() | ||||
| { | ||||
|     doConnect(); | ||||
| } | ||||
|  | ||||
| void GPClient::on_portalInput_returnPressed() | ||||
| { | ||||
|     doConnect(); | ||||
| } | ||||
|  | ||||
| void GPClient::on_portalInput_editingFinished() | ||||
| { | ||||
|     populateGatewayMenu(); | ||||
| } | ||||
|  | ||||
| void GPClient::initSystemTrayIcon() | ||||
| { | ||||
|     systemTrayIcon = new QSystemTrayIcon(this); | ||||
|     contextMenu = new QMenu("GlobalProtect", this); | ||||
|  | ||||
|     gatewaySwitchMenu = new QMenu("Switch Gateway", this); | ||||
|     gatewaySwitchMenu->setIcon(QIcon::fromTheme("network-workgroup")); | ||||
|     populateGatewayMenu(); | ||||
|  | ||||
|     systemTrayIcon->setIcon(QIcon(":/images/not_connected.png")); | ||||
|     systemTrayIcon->setToolTip("GlobalProtect"); | ||||
|     systemTrayIcon->setContextMenu(contextMenu); | ||||
|  | ||||
|     connect(systemTrayIcon, &QSystemTrayIcon::activated, this, &GPClient::onSystemTrayActivated); | ||||
|     connect(gatewaySwitchMenu, &QMenu::triggered, this, &GPClient::onGatewayChanged); | ||||
|  | ||||
|     openAction = contextMenu->addAction(QIcon::fromTheme("window-new"), "Open", this, &GPClient::activate); | ||||
|     connectAction = contextMenu->addAction(QIcon::fromTheme("preferences-system-network"), "Connect", this, &GPClient::doConnect); | ||||
|     contextMenu->addMenu(gatewaySwitchMenu); | ||||
|     contextMenu->addSeparator(); | ||||
|     clearAction = contextMenu->addAction(QIcon::fromTheme("edit-clear"), "Reset", this, &GPClient::reset); | ||||
|     quitAction = contextMenu->addAction(QIcon::fromTheme("application-exit"), "Quit", this, &GPClient::quit); | ||||
|  | ||||
|     systemTrayIcon->show(); | ||||
| } | ||||
|  | ||||
| void GPClient::initVpnStatus() { | ||||
|     int status = vpn->status(); | ||||
|  | ||||
|     if (status == 1) { | ||||
|         ui->statusLabel->setText("Connecting..."); | ||||
|         updateConnectionStatus(VpnStatus::pending); | ||||
|     } else if (status == 2) { | ||||
|         updateConnectionStatus(VpnStatus::connected); | ||||
|     } else if (status == 3) { | ||||
|         ui->statusLabel->setText("Disconnecting..."); | ||||
|         updateConnectionStatus(VpnStatus::pending); | ||||
|     } else { | ||||
|         updateConnectionStatus(VpnStatus::disconnected); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void GPClient::populateGatewayMenu() | ||||
| { | ||||
|     LOGI << "Populating the Switch Gateway menu..."; | ||||
|  | ||||
|     const QList<GPGateway> gateways = allGateways(); | ||||
|     gatewaySwitchMenu->clear(); | ||||
|  | ||||
|     if (gateways.isEmpty()) { | ||||
|         gatewaySwitchMenu->addAction("<None>")->setData(-1); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     const QString currentGatewayName = currentGateway().name(); | ||||
|     for (int i = 0; i < gateways.length(); i++) { | ||||
|         const GPGateway g = gateways.at(i); | ||||
|         QString iconImage = ":/images/radio_unselected.png"; | ||||
|         if (g.name() == currentGatewayName) { | ||||
|             iconImage = ":/images/radio_selected.png"; | ||||
|         } | ||||
|         gatewaySwitchMenu->addAction(QIcon(iconImage), QString("%1 (%2)").arg(g.name(), g.address()))->setData(i); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void GPClient::updateConnectionStatus(const GPClient::VpnStatus &status) | ||||
| { | ||||
|     switch (status) { | ||||
|         case VpnStatus::disconnected: | ||||
|             ui->statusLabel->setText("Not Connected"); | ||||
|             ui->statusImage->setStyleSheet("image: url(:/images/not_connected.png); padding: 15;"); | ||||
|             ui->connectButton->setText("Connect"); | ||||
|             ui->connectButton->setDisabled(false); | ||||
|             ui->portalInput->setReadOnly(false); | ||||
|  | ||||
|             systemTrayIcon->setIcon(QIcon{ ":/images/not_connected.png" }); | ||||
|             connectAction->setEnabled(true); | ||||
|             connectAction->setText("Connect"); | ||||
|             gatewaySwitchMenu->setEnabled(true); | ||||
|             clearAction->setEnabled(true); | ||||
|             break; | ||||
|         case VpnStatus::pending: | ||||
|             ui->statusImage->setStyleSheet("image: url(:/images/pending.png); padding: 15;"); | ||||
|             ui->connectButton->setDisabled(true); | ||||
|             ui->portalInput->setReadOnly(true); | ||||
|  | ||||
|             systemTrayIcon->setIcon(QIcon{ ":/images/pending.png" }); | ||||
|             connectAction->setEnabled(false); | ||||
|             gatewaySwitchMenu->setEnabled(false); | ||||
|             clearAction->setEnabled(false); | ||||
|             break; | ||||
|         case VpnStatus::connected: | ||||
|             ui->statusLabel->setText("Connected"); | ||||
|             ui->statusImage->setStyleSheet("image: url(:/images/connected.png); padding: 15;"); | ||||
|             ui->connectButton->setText("Disconnect"); | ||||
|             ui->connectButton->setDisabled(false); | ||||
|             ui->portalInput->setReadOnly(true); | ||||
|  | ||||
|             systemTrayIcon->setIcon(QIcon{ ":/images/connected.png" }); | ||||
|             connectAction->setEnabled(true); | ||||
|             connectAction->setText("Disconnect"); | ||||
|             gatewaySwitchMenu->setEnabled(true); | ||||
|             clearAction->setEnabled(false); | ||||
|             break; | ||||
|         default: | ||||
|             break; | ||||
|     } | ||||
| } | ||||
|  | ||||
| void GPClient::onSystemTrayActivated(QSystemTrayIcon::ActivationReason reason) | ||||
| { | ||||
|     switch (reason) { | ||||
|         case QSystemTrayIcon::Trigger: | ||||
|         case QSystemTrayIcon::DoubleClick: | ||||
|             this->activate(); | ||||
|             break; | ||||
|         default: | ||||
|             break; | ||||
|     } | ||||
| } | ||||
|  | ||||
| void GPClient::onGatewayChanged(QAction *action) | ||||
| { | ||||
|     const int index = action->data().toInt(); | ||||
|  | ||||
|     if (index == -1) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     const auto g = allGateways().at(index); | ||||
|  | ||||
|     // If the selected gateway is the same as the current gateway | ||||
|     if (g.name() == currentGateway().name()) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     setCurrentGateway(g); | ||||
|  | ||||
|     if (connected()) { | ||||
|         ui->statusLabel->setText("Switching Gateway..."); | ||||
|         ui->connectButton->setEnabled(false); | ||||
|  | ||||
|         vpn->disconnect(); | ||||
|         isSwitchingGateway = true; | ||||
|     } | ||||
| } | ||||
|  | ||||
| void GPClient::doConnect() | ||||
| { | ||||
|     LOGI << "Start connecting..."; | ||||
|  | ||||
|     const auto btnText = ui->connectButton->text(); | ||||
|     const auto portal = this->portal(); | ||||
|  | ||||
|     // Display the main window if portal is empty | ||||
|     if (portal.isEmpty()) { | ||||
|         activate(); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     if (btnText.endsWith("Connect")) { | ||||
|         settings::save("portal", portal); | ||||
|  | ||||
|         // Login to the previously saved gateway | ||||
|         if (!currentGateway().name().isEmpty()) { | ||||
|             LOGI << "Start gateway login using the previously saved gateway..."; | ||||
|             isQuickConnect = true; | ||||
|             gatewayLogin(); | ||||
|         } else { | ||||
|             // Perform the portal login | ||||
|             LOGI << "Start portal login..."; | ||||
|             portalLogin(); | ||||
|         } | ||||
|     } else { | ||||
|         LOGI << "Start disconnecting the VPN..."; | ||||
|  | ||||
|         ui->statusLabel->setText("Disconnecting..."); | ||||
|         updateConnectionStatus(VpnStatus::pending); | ||||
|         vpn->disconnect(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| // Login to the portal interface to get the portal config and preferred gateway | ||||
| void GPClient::portalLogin() | ||||
| { | ||||
|     auto *portalAuth = new PortalAuthenticator(portal(), settings::get("clientos", "Linux").toString()); | ||||
|  | ||||
|     connect(portalAuth, &PortalAuthenticator::success, [this, portalAuth](const PortalConfigResponse response, const QString region) { | ||||
|         this->onPortalSuccess(response, region); | ||||
|         portalAuth->deleteLater(); | ||||
|     }); | ||||
|     // Prelogin failed on the portal interface, try to treat the portal as a gateway interface | ||||
|     connect(portalAuth, &PortalAuthenticator::preloginFailed, [this, portalAuth](const QString msg) { | ||||
|         this->onPortalPreloginFail(msg); | ||||
|         portalAuth->deleteLater(); | ||||
|     }); | ||||
|     connect(portalAuth, &PortalAuthenticator::portalConfigFailed, [this, portalAuth](const QString msg) { | ||||
|         this->onPortalConfigFail(msg); | ||||
|         portalAuth->deleteLater(); | ||||
|     }); | ||||
|     // Portal login failed | ||||
|     connect(portalAuth, &PortalAuthenticator::fail, [this, portalAuth](const QString &msg) { | ||||
|         this->onPortalFail(msg); | ||||
|         portalAuth->deleteLater(); | ||||
|     }); | ||||
|  | ||||
|     ui->statusLabel->setText("Authenticating..."); | ||||
|     updateConnectionStatus(VpnStatus::pending); | ||||
|     portalAuth->authenticate(); | ||||
| } | ||||
|  | ||||
| void GPClient::onPortalSuccess(const PortalConfigResponse portalConfig, const QString region) | ||||
| { | ||||
|     LOGI << "Portal authentication succeeded."; | ||||
|  | ||||
|     // No gateway found in portal configuration | ||||
|     if (portalConfig.allGateways().size() == 0) { | ||||
|         LOGI << "No gateway found in portal configuration, treat the portal address as a gateway."; | ||||
|         tryGatewayLogin(); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     GPGateway gateway = filterPreferredGateway(portalConfig.allGateways(), region); | ||||
|     setAllGateways(portalConfig.allGateways()); | ||||
|     setCurrentGateway(gateway); | ||||
|     this->portalConfig = portalConfig; | ||||
|  | ||||
|     gatewayLogin(); | ||||
| } | ||||
|  | ||||
| void GPClient::onPortalPreloginFail(const QString msg) | ||||
| { | ||||
|     LOGI << "Portal prelogin failed, treat the portal address as a gateway." << msg; | ||||
|     tryGatewayLogin(); | ||||
| } | ||||
|  | ||||
| void GPClient::onPortalConfigFail(const QString msg) | ||||
| { | ||||
|     LOGI << "Failed to get the portal configuration, " << msg << " Treat the portal address as gateway."; | ||||
|     tryGatewayLogin(); | ||||
| } | ||||
|  | ||||
| void GPClient::onPortalFail(const QString &msg) | ||||
| { | ||||
|     if (!msg.isEmpty()) { | ||||
|         openMessageBox("Portal authentication failed.", msg); | ||||
|     } | ||||
|  | ||||
|     updateConnectionStatus(VpnStatus::disconnected); | ||||
| } | ||||
|  | ||||
| void GPClient::tryGatewayLogin() | ||||
| { | ||||
|     LOGI << "Try to perform login on the the gateway interface..."; | ||||
|  | ||||
|     // Treat the portal input as the gateway address | ||||
|     GPGateway g; | ||||
|     g.setName(portal()); | ||||
|     g.setAddress(portal()); | ||||
|  | ||||
|     QList<GPGateway> gateways; | ||||
|     gateways.append(g); | ||||
|  | ||||
|     setAllGateways(gateways); | ||||
|     setCurrentGateway(g); | ||||
|  | ||||
|     gatewayLogin(); | ||||
| } | ||||
|  | ||||
| // Login to the gateway | ||||
| void GPClient::gatewayLogin() | ||||
| { | ||||
|     LOGI << "Performing gateway login..."; | ||||
|  | ||||
|     GatewayAuthenticatorParams params = GatewayAuthenticatorParams::fromPortalConfigResponse(portalConfig); | ||||
|     params.setClientos(settings::get("clientos", "Linux").toString()); | ||||
|  | ||||
|     GatewayAuthenticator *gatewayAuth; | ||||
|     gatewayAuth = new GatewayAuthenticator(currentGateway().address(), params); | ||||
|  | ||||
|     connect(gatewayAuth, &GatewayAuthenticator::success, [this, gatewayAuth](const QString &authToken) { | ||||
|         this->onGatewaySuccess(authToken); | ||||
|         gatewayAuth->deleteLater(); | ||||
|     }); | ||||
|     connect(gatewayAuth, &GatewayAuthenticator::fail, [this, gatewayAuth](const QString &msg) { | ||||
|         this->onGatewayFail(msg); | ||||
|         gatewayAuth->deleteLater(); | ||||
|     }); | ||||
|  | ||||
|     ui->statusLabel->setText("Authenticating..."); | ||||
|     updateConnectionStatus(VpnStatus::pending); | ||||
|     gatewayAuth->authenticate(); | ||||
| } | ||||
|  | ||||
| void GPClient::onGatewaySuccess(const QString &authCookie) | ||||
| { | ||||
|     LOGI << "Gateway login succeeded, got the cookie " << authCookie; | ||||
|  | ||||
|     isQuickConnect = false; | ||||
|     QList<QString> gatewayAddresses; | ||||
|     for (GPGateway &gw : allGateways()) { | ||||
|       gatewayAddresses.push_back(gw.address()); | ||||
|     } | ||||
|     vpn->connect(currentGateway().address(), gatewayAddresses, portalConfig.username(), authCookie); | ||||
|     ui->statusLabel->setText("Connecting..."); | ||||
|     updateConnectionStatus(VpnStatus::pending); | ||||
| } | ||||
|  | ||||
| void GPClient::onGatewayFail(const QString &msg) | ||||
| { | ||||
|     // If the quick connect on gateway failed, perform the portal login | ||||
|     if (isQuickConnect && !msg.isEmpty()) { | ||||
|         isQuickConnect = false; | ||||
|         LOGI << "Quick connection failed, trying to portal login..."; | ||||
|         portalLogin(); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     if (!msg.isEmpty()) { | ||||
|         openMessageBox("Gateway authentication failed.", msg); | ||||
|     } | ||||
|  | ||||
|     updateConnectionStatus(VpnStatus::disconnected); | ||||
| } | ||||
|  | ||||
| void GPClient::activate() | ||||
| { | ||||
|     activateWindow(); | ||||
|     showNormal(); | ||||
| } | ||||
|  | ||||
| QString GPClient::portal() const | ||||
| { | ||||
|     const QString input = ui->portalInput->text().trimmed(); | ||||
|  | ||||
|     if (input.startsWith("http")) { | ||||
|         return QUrl(input).authority(); | ||||
|     } | ||||
|     return input; | ||||
| } | ||||
|  | ||||
| void GPClient::portal(QString p) | ||||
| { | ||||
|     ui->portalInput->setText(p); | ||||
| } | ||||
|  | ||||
| bool GPClient::connected() const | ||||
| { | ||||
|     const QString statusText = ui->statusLabel->text(); | ||||
|     return statusText.contains("Connected") && !statusText.contains("Not"); | ||||
| } | ||||
|  | ||||
| QList<GPGateway> GPClient::allGateways() const | ||||
| { | ||||
|  | ||||
|     QList<GPGateway> gateways; | ||||
|  | ||||
|     for (auto g :settings::get_all("_gateways$") ){ | ||||
|  | ||||
|     	gateways.append(GPGateway::fromJson(settings::get(g).toString())); | ||||
|     } | ||||
|     return gateways; | ||||
| } | ||||
|  | ||||
| void GPClient::setAllGateways(QList<GPGateway> gateways) | ||||
| { | ||||
|     LOGI << "Updating all the gateways..."; | ||||
|  | ||||
|     settings::save(portal() + "_gateways", GPGateway::serialize(gateways)); | ||||
|     populateGatewayMenu(); | ||||
| } | ||||
|  | ||||
| GPGateway GPClient::currentGateway() const | ||||
| { | ||||
|     const QString selectedGateway = settings::get(portal() + "_selectedGateway").toString(); | ||||
|  | ||||
|     for (auto g : allGateways()) { | ||||
|         if (g.name() == selectedGateway) { | ||||
|             return g; | ||||
|         } | ||||
|     } | ||||
|     return GPGateway{}; | ||||
| } | ||||
|  | ||||
| void GPClient::setCurrentGateway(const GPGateway gateway) | ||||
| { | ||||
|     LOGI << "Updating the current gateway to " << gateway.name(); | ||||
|  | ||||
|     settings::save(portal() + "_selectedGateway", gateway.name()); | ||||
|     ui->portalInput->setText(gateway.address()); | ||||
|     populateGatewayMenu(); | ||||
| } | ||||
|  | ||||
| void GPClient::reset() | ||||
| { | ||||
|     settings::clear(); | ||||
|     populateGatewayMenu(); | ||||
|     ui->portalInput->clear(); | ||||
| } | ||||
|  | ||||
| void GPClient::quit() | ||||
| { | ||||
|     vpn->disconnect(); | ||||
|     QApplication::quit(); | ||||
| } | ||||
|  | ||||
| void GPClient::onVPNConnected() | ||||
| { | ||||
|     updateConnectionStatus(VpnStatus::connected); | ||||
| } | ||||
|  | ||||
| void GPClient::onVPNDisconnected() | ||||
| { | ||||
|     updateConnectionStatus(VpnStatus::disconnected); | ||||
|  | ||||
|     if (isSwitchingGateway) { | ||||
|         gatewayLogin(); | ||||
|         isSwitchingGateway = false; | ||||
|     } | ||||
| } | ||||
|  | ||||
| void GPClient::onVPNError(QString errorMessage) | ||||
| { | ||||
|     updateConnectionStatus(VpnStatus::disconnected); | ||||
|     openMessageBox("Failed to connect", errorMessage); | ||||
| } | ||||
|  | ||||
| void GPClient::onVPNLogAvailable(QString log) | ||||
| { | ||||
|     LOGI << log; | ||||
| } | ||||
| @@ -1,104 +0,0 @@ | ||||
| #ifndef GPCLIENT_H | ||||
| #define GPCLIENT_H | ||||
|  | ||||
| #include <QtWidgets/QMainWindow> | ||||
| #include <QtWidgets/QSystemTrayIcon> | ||||
| #include <QtWidgets/QMenu> | ||||
| #include <QtWidgets/QPushButton> | ||||
|  | ||||
| #include "portalconfigresponse.h" | ||||
| #include "settingsdialog.h" | ||||
| #include "vpn.h" | ||||
| #include "gatewayauthenticator.h" | ||||
|  | ||||
| QT_BEGIN_NAMESPACE | ||||
| namespace Ui { class GPClient; } | ||||
| QT_END_NAMESPACE | ||||
|  | ||||
| class GPClient : public QMainWindow | ||||
| { | ||||
|     Q_OBJECT | ||||
|  | ||||
| public: | ||||
|     GPClient(QWidget *parent, IVpn *vpn); | ||||
|  | ||||
|     void activate(); | ||||
|     void quit(); | ||||
|  | ||||
|     QString portal() const; | ||||
|     void portal(QString); | ||||
|  | ||||
|     GPGateway currentGateway() const; | ||||
|     void setCurrentGateway(const GPGateway gateway); | ||||
|  | ||||
|     void doConnect(); | ||||
|     void reset(); | ||||
|  | ||||
| private slots: | ||||
|     void onSettingsButtonClicked(); | ||||
|     void onSettingsAccepted(); | ||||
|  | ||||
|     void on_connectButton_clicked(); | ||||
|     void on_portalInput_returnPressed(); | ||||
|     void on_portalInput_editingFinished(); | ||||
|  | ||||
|     void onSystemTrayActivated(QSystemTrayIcon::ActivationReason reason); | ||||
|     void onGatewayChanged(QAction *action); | ||||
|  | ||||
|     void onPortalSuccess(const PortalConfigResponse portalConfig, const QString region); | ||||
|     void onPortalPreloginFail(const QString msg); | ||||
|     void onPortalConfigFail(const QString msg); | ||||
|     void onPortalFail(const QString &msg); | ||||
|  | ||||
|     void onGatewaySuccess(const QString &authCookie); | ||||
|     void onGatewayFail(const QString &msg); | ||||
|  | ||||
|     void onVPNConnected(); | ||||
|     void onVPNDisconnected(); | ||||
|     void onVPNError(QString errorMessage); | ||||
|     void onVPNLogAvailable(QString log); | ||||
|  | ||||
| private: | ||||
|     enum class VpnStatus | ||||
|     { | ||||
|         disconnected, | ||||
|         pending, | ||||
|         connected | ||||
|     }; | ||||
|  | ||||
|     Ui::GPClient *ui; | ||||
|     IVpn *vpn; | ||||
|  | ||||
|     QSystemTrayIcon *systemTrayIcon; | ||||
|     QMenu *contextMenu; | ||||
|     QAction *openAction; | ||||
|     QAction *connectAction; | ||||
|  | ||||
|     QMenu *gatewaySwitchMenu; | ||||
|     QAction *clearAction; | ||||
|     QAction *quitAction; | ||||
|  | ||||
|     SettingsDialog *settingsDialog; | ||||
|     QPushButton *settingsButton; | ||||
|  | ||||
|     bool isQuickConnect { false }; | ||||
|     bool isSwitchingGateway { false }; | ||||
|     PortalConfigResponse portalConfig; | ||||
|  | ||||
|     void setupSettings(); | ||||
|  | ||||
|     void initSystemTrayIcon(); | ||||
|     void initVpnStatus(); | ||||
|     void populateGatewayMenu(); | ||||
|     void updateConnectionStatus(const VpnStatus &status); | ||||
|  | ||||
|     void portalLogin(); | ||||
|     void tryGatewayLogin(); | ||||
|     void gatewayLogin(); | ||||
|  | ||||
|     bool connected() const; | ||||
|  | ||||
|     QList<GPGateway> allGateways() const; | ||||
|     void setAllGateways(QList<GPGateway> gateways); | ||||
| }; | ||||
| #endif // GPCLIENT_H | ||||
| @@ -1,143 +0,0 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <ui version="4.0"> | ||||
|  <class>GPClient</class> | ||||
|  <widget class="QMainWindow" name="GPClient"> | ||||
|   <property name="geometry"> | ||||
|    <rect> | ||||
|     <x>0</x> | ||||
|     <y>0</y> | ||||
|     <width>260</width> | ||||
|     <height>362</height> | ||||
|    </rect> | ||||
|   </property> | ||||
|   <property name="windowTitle"> | ||||
|    <string>GlobalProtect OpenConnect</string> | ||||
|   </property> | ||||
|   <property name="windowIcon"> | ||||
|    <iconset resource="resources.qrc"> | ||||
|     <normaloff>:/images/logo.svg</normaloff>:/images/logo.svg</iconset> | ||||
|   </property> | ||||
|   <property name="styleSheet"> | ||||
|    <string notr="true"/> | ||||
|   </property> | ||||
|   <property name="iconSize"> | ||||
|    <size> | ||||
|     <width>22</width> | ||||
|     <height>22</height> | ||||
|    </size> | ||||
|   </property> | ||||
|   <widget class="QWidget" name="centralwidget"> | ||||
|    <property name="sizePolicy"> | ||||
|     <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> | ||||
|      <horstretch>0</horstretch> | ||||
|      <verstretch>0</verstretch> | ||||
|     </sizepolicy> | ||||
|    </property> | ||||
|    <property name="layoutDirection"> | ||||
|     <enum>Qt::LeftToRight</enum> | ||||
|    </property> | ||||
|    <layout class="QVBoxLayout" name="verticalLayout_3" stretch="1,0,0"> | ||||
|     <property name="leftMargin"> | ||||
|      <number>15</number> | ||||
|     </property> | ||||
|     <property name="topMargin"> | ||||
|      <number>15</number> | ||||
|     </property> | ||||
|     <property name="rightMargin"> | ||||
|      <number>15</number> | ||||
|     </property> | ||||
|     <property name="bottomMargin"> | ||||
|      <number>15</number> | ||||
|     </property> | ||||
|     <item> | ||||
|      <layout class="QVBoxLayout" name="verticalLayout" stretch="1,0"> | ||||
|       <property name="bottomMargin"> | ||||
|        <number>15</number> | ||||
|       </property> | ||||
|       <item> | ||||
|        <widget class="QLabel" name="statusImage"> | ||||
|         <property name="styleSheet"> | ||||
|          <string notr="true">#statusImage { | ||||
| 	image: url(:/images/not_connected.png); | ||||
| 	padding: 15 | ||||
| }</string> | ||||
|         </property> | ||||
|         <property name="text"> | ||||
|          <string/> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item> | ||||
|        <widget class="QLabel" name="statusLabel"> | ||||
|         <property name="font"> | ||||
|          <font> | ||||
|           <pointsize>14</pointsize> | ||||
|           <weight>50</weight> | ||||
|           <bold>false</bold> | ||||
|           <underline>false</underline> | ||||
|          </font> | ||||
|         </property> | ||||
|         <property name="text"> | ||||
|          <string>Not Connected</string> | ||||
|         </property> | ||||
|         <property name="alignment"> | ||||
|          <set>Qt::AlignCenter</set> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|      </layout> | ||||
|     </item> | ||||
|     <item> | ||||
|      <layout class="QVBoxLayout" name="verticalLayout_2"> | ||||
|       <property name="bottomMargin"> | ||||
|        <number>0</number> | ||||
|       </property> | ||||
|       <item> | ||||
|        <widget class="QLineEdit" name="portalInput"> | ||||
|         <property name="text"> | ||||
|          <string/> | ||||
|         </property> | ||||
|         <property name="placeholderText"> | ||||
|          <string>Please enter your portal address</string> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item> | ||||
|        <widget class="QPushButton" name="connectButton"> | ||||
|         <property name="sizePolicy"> | ||||
|          <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> | ||||
|           <horstretch>0</horstretch> | ||||
|           <verstretch>0</verstretch> | ||||
|          </sizepolicy> | ||||
|         </property> | ||||
|         <property name="text"> | ||||
|          <string>Connect</string> | ||||
|         </property> | ||||
|         <property name="autoDefault"> | ||||
|          <bool>true</bool> | ||||
|         </property> | ||||
|         <property name="default"> | ||||
|          <bool>false</bool> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|      </layout> | ||||
|     </item> | ||||
|     <item> | ||||
|      <widget class="QLabel" name="label"> | ||||
|       <property name="text"> | ||||
|        <string><html><head/><body><p align="center"><a href="https://bit.ly/3g5DHqy"><span style=" text-decoration: underline; color:#4c6b8a;">Report a bug</span></a> / <a href="https://bit.ly/3jQYfEi"><span style=" text-decoration: underline; color:#4c6b8a;">Buy me a coffee</span></a></p></body></html></string> | ||||
|       </property> | ||||
|       <property name="openExternalLinks"> | ||||
|        <bool>true</bool> | ||||
|       </property> | ||||
|      </widget> | ||||
|     </item> | ||||
|    </layout> | ||||
|   </widget> | ||||
|  </widget> | ||||
|  <resources> | ||||
|   <include location="resources.qrc"/> | ||||
|  </resources> | ||||
|  <connections/> | ||||
| </ui> | ||||
| @@ -1,97 +0,0 @@ | ||||
| #include <QtCore/QJsonObject> | ||||
| #include <QtCore/QJsonDocument> | ||||
| #include <QtCore/QJsonArray> | ||||
|  | ||||
| #include "gpgateway.h" | ||||
|  | ||||
| GPGateway::GPGateway() | ||||
| { | ||||
| } | ||||
|  | ||||
| QString GPGateway::name() const | ||||
| { | ||||
|     return _name; | ||||
| } | ||||
|  | ||||
| QString GPGateway::address() const | ||||
| { | ||||
|     return _address; | ||||
| } | ||||
|  | ||||
| void GPGateway::setName(const QString &name) | ||||
| { | ||||
|     _name = name; | ||||
| } | ||||
|  | ||||
| void GPGateway::setAddress(const QString &address) | ||||
| { | ||||
|     _address = address; | ||||
| } | ||||
|  | ||||
| void GPGateway::setPriorityRules(const QMap<QString, int> &priorityRules) | ||||
| { | ||||
|     _priorityRules = priorityRules; | ||||
| } | ||||
|  | ||||
| int GPGateway::priorityOf(QString ruleName) const | ||||
| { | ||||
|     if (_priorityRules.contains(ruleName)) { | ||||
|         return _priorityRules.value(ruleName); | ||||
|     } | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
| QJsonObject GPGateway::toJsonObject() const | ||||
| { | ||||
|     QJsonObject obj; | ||||
|     obj.insert("name", name()); | ||||
|     obj.insert("address", address()); | ||||
|  | ||||
|     return obj; | ||||
| } | ||||
|  | ||||
| QString GPGateway::toString() const | ||||
| { | ||||
|     QJsonDocument jsonDoc{ toJsonObject() }; | ||||
|     return QString::fromUtf8(jsonDoc.toJson()); | ||||
| } | ||||
|  | ||||
| QString GPGateway::serialize(QList<GPGateway> &gateways) | ||||
| { | ||||
|     QJsonArray arr; | ||||
|  | ||||
|     for (auto g : gateways) { | ||||
|         arr.append(g.toJsonObject()); | ||||
|     } | ||||
|  | ||||
|     QJsonDocument jsonDoc{ arr }; | ||||
|     return QString::fromUtf8(jsonDoc.toJson()); | ||||
| } | ||||
|  | ||||
| QList<GPGateway> GPGateway::fromJson(const QString &jsonString) | ||||
| { | ||||
|     QList<GPGateway> gateways; | ||||
|  | ||||
|     if (jsonString.isEmpty()) { | ||||
|         return gateways; | ||||
|     } | ||||
|  | ||||
|     QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonString.toUtf8()); | ||||
|  | ||||
|     for (auto item : jsonDoc.array()) { | ||||
|         GPGateway g = GPGateway::fromJsonObject(item.toObject()); | ||||
|         gateways.append(g); | ||||
|     } | ||||
|  | ||||
|     return gateways; | ||||
| } | ||||
|  | ||||
| GPGateway GPGateway::fromJsonObject(const QJsonObject &jsonObj) | ||||
| { | ||||
|     GPGateway g; | ||||
|  | ||||
|     g.setName(jsonObj.value("name").toString()); | ||||
|     g.setAddress(jsonObj.value("address").toString()); | ||||
|  | ||||
|     return g; | ||||
| } | ||||
| @@ -1,33 +0,0 @@ | ||||
| #ifndef GPGATEWAY_H | ||||
| #define GPGATEWAY_H | ||||
|  | ||||
| #include <QtCore/QString> | ||||
| #include <QtCore/QMap> | ||||
| #include <QtCore/QJsonObject> | ||||
|  | ||||
| class GPGateway | ||||
| { | ||||
| public: | ||||
|     GPGateway(); | ||||
|  | ||||
|     QString name() const; | ||||
|     QString address() const; | ||||
|  | ||||
|     void setName(const QString &name); | ||||
|     void setAddress(const QString &address); | ||||
|     void setPriorityRules(const QMap<QString, int> &priorityRules); | ||||
|     int priorityOf(QString ruleName) const; | ||||
|     QJsonObject toJsonObject() const; | ||||
|     QString toString() const; | ||||
|  | ||||
|     static QString serialize(QList<GPGateway> &gateways); | ||||
|     static QList<GPGateway> fromJson(const QString &jsonString); | ||||
|     static GPGateway fromJsonObject(const QJsonObject &jsonObj); | ||||
|  | ||||
| private: | ||||
|     QString _name; | ||||
|     QString _address; | ||||
|     QMap<QString, int> _priorityRules; | ||||
| }; | ||||
|  | ||||
| #endif // GPGATEWAY_H | ||||
| @@ -1,178 +0,0 @@ | ||||
| #include <QtCore/QXmlStreamReader> | ||||
| #include <QtWidgets/QMessageBox> | ||||
| #include <QtWidgets/QDesktopWidget> | ||||
| #include <QtWidgets/QApplication> | ||||
| #include <QtWidgets/QWidget> | ||||
| #include <QtNetwork/QNetworkRequest> | ||||
| #include <QtNetwork/QSslConfiguration> | ||||
| #include <QtNetwork/QSslSocket> | ||||
| #include <plog/Log.h> | ||||
| #include <QWebEngineProfile> | ||||
| #include <QWebEngineCookieStore> | ||||
| #include <keychain.h> | ||||
|  | ||||
| #include "gphelper.h" | ||||
|  | ||||
| using namespace QKeychain; | ||||
|  | ||||
| QNetworkAccessManager* gpclient::helper::networkManager = new QNetworkAccessManager; | ||||
|  | ||||
| QNetworkReply* gpclient::helper::createRequest(QString url, QByteArray params) | ||||
| { | ||||
|     QNetworkRequest request(url); | ||||
|  | ||||
|     // Skip the ssl verifying | ||||
|     QSslConfiguration conf = request.sslConfiguration(); | ||||
|     conf.setPeerVerifyMode(QSslSocket::VerifyNone); | ||||
|     conf.setSslOption(QSsl::SslOptionDisableLegacyRenegotiation, false); | ||||
|     request.setSslConfiguration(conf); | ||||
|  | ||||
|     request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); | ||||
|     request.setHeader(QNetworkRequest::UserAgentHeader, UA); | ||||
|  | ||||
|     if (params == nullptr) { | ||||
|         return networkManager->post(request, QByteArray(nullptr)); | ||||
|     } | ||||
|     return networkManager->post(request, params); | ||||
| } | ||||
|  | ||||
| GPGateway gpclient::helper::filterPreferredGateway(QList<GPGateway> gateways, const QString ruleName) | ||||
| { | ||||
|     LOGI << gateways.size() << " gateway(s) available, filter the gateways with rule: " << ruleName; | ||||
|  | ||||
|     GPGateway gateway = gateways.first(); | ||||
|  | ||||
|     for (GPGateway g : gateways) { | ||||
|         if (g.priorityOf(ruleName) > gateway.priorityOf(ruleName)) { | ||||
|             LOGI << "Find a preferred gateway: " << g.name(); | ||||
|             gateway = g; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     return gateway; | ||||
| } | ||||
|  | ||||
| QUrlQuery gpclient::helper::parseGatewayResponse(const QByteArray &xml) | ||||
| { | ||||
|     LOGI << "Start parsing the gateway response..."; | ||||
|     LOGI << "The gateway response is: " << xml; | ||||
|  | ||||
|     QXmlStreamReader xmlReader{xml}; | ||||
|     QList<QString> args; | ||||
|  | ||||
|     while (!xmlReader.atEnd()) { | ||||
|         xmlReader.readNextStartElement(); | ||||
|         if (xmlReader.name() == "argument") { | ||||
|             args.append(QUrl::toPercentEncoding(xmlReader.readElementText())); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     QUrlQuery params{}; | ||||
|     params.addQueryItem("authcookie", args.at(1)); | ||||
|     params.addQueryItem("portal", args.at(3)); | ||||
|     params.addQueryItem("user", args.at(4)); | ||||
|     params.addQueryItem("domain", args.at(7)); | ||||
|     params.addQueryItem("preferred-ip", args.at(15)); | ||||
|     params.addQueryItem("computer", QUrl::toPercentEncoding(QSysInfo::machineHostName())); | ||||
|  | ||||
|     return params; | ||||
| } | ||||
|  | ||||
| void gpclient::helper::openMessageBox(const QString &message, const QString& informativeText) | ||||
| { | ||||
|     QMessageBox msgBox; | ||||
|     msgBox.setWindowTitle("Notice"); | ||||
|     msgBox.setText(message); | ||||
|     msgBox.setFixedWidth(500); | ||||
|     msgBox.setStyleSheet("QLabel{min-width: 250px}"); | ||||
|     msgBox.setInformativeText(informativeText); | ||||
|     msgBox.exec(); | ||||
| } | ||||
|  | ||||
| void gpclient::helper::moveCenter(QWidget *widget) | ||||
| { | ||||
|     QDesktopWidget *desktop = QApplication::desktop(); | ||||
|  | ||||
|     int screenWidth, width; | ||||
|     int screenHeight, height; | ||||
|     int x, y; | ||||
|     QSize windowSize; | ||||
|  | ||||
|     screenWidth = desktop->width(); | ||||
|     screenHeight = desktop->height(); | ||||
|  | ||||
|     windowSize = widget->size(); | ||||
|     width = windowSize.width(); | ||||
|     height = windowSize.height(); | ||||
|  | ||||
|     x = (screenWidth - width) / 2; | ||||
|     y = (screenHeight - height) / 2; | ||||
|     y -= 50; | ||||
|     widget->move(x, y); | ||||
| } | ||||
|  | ||||
| QSettings *gpclient::helper::settings::_settings = new QSettings("com.yuezk.qt", "GPClient"); | ||||
|  | ||||
| QVariant gpclient::helper::settings::get(const QString &key, const QVariant &defaultValue) | ||||
| { | ||||
|     return _settings->value(key, defaultValue); | ||||
| } | ||||
|  | ||||
| QStringList gpclient::helper::settings::get_all(const QString &key, const QVariant &defaultValue) | ||||
| { | ||||
| 	QRegularExpression re(key); | ||||
| 	return 	_settings->allKeys().filter(re); | ||||
| } | ||||
|  | ||||
| void gpclient::helper::settings::save(const QString &key, const QVariant &value) | ||||
| { | ||||
|     _settings->setValue(key, value); | ||||
| } | ||||
|  | ||||
|  | ||||
| void gpclient::helper::settings::clear() | ||||
| { | ||||
|     QStringList keys = _settings->allKeys(); | ||||
|     for (const auto &key : qAsConst(keys)) { | ||||
|         if (!reservedKeys.contains(key)) { | ||||
|             _settings->remove(key); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     QWebEngineProfile::defaultProfile()->cookieStore()->deleteAllCookies(); | ||||
| } | ||||
|  | ||||
|  | ||||
| bool gpclient::helper::settings::secureSave(const QString &key, const QString &value) { | ||||
|     WritePasswordJob job( QLatin1String("gpclient") ); | ||||
|     job.setAutoDelete( false ); | ||||
|     job.setKey( key ); | ||||
|     job.setTextData( value ); | ||||
|     QEventLoop loop; | ||||
|     job.connect( &job, SIGNAL(finished(QKeychain::Job*)), &loop, SLOT(quit()) ); | ||||
|     job.start(); | ||||
|     loop.exec(); | ||||
|     if ( job.error() ) { | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| bool gpclient::helper::settings::secureGet(const QString &key, QString &value) { | ||||
|     ReadPasswordJob job( QLatin1String("gpclient") ); | ||||
|     job.setAutoDelete( false ); | ||||
|     job.setKey( key ); | ||||
|     QEventLoop loop; | ||||
|     job.connect( &job, SIGNAL(finished(QKeychain::Job*)), &loop, SLOT(quit()) ); | ||||
|     job.start(); | ||||
|     loop.exec(); | ||||
|  | ||||
|     const QString pw = job.textData(); | ||||
|     if ( job.error() ) { | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     value = pw; | ||||
|     return true; | ||||
| } | ||||
| @@ -1,47 +0,0 @@ | ||||
| #ifndef GPHELPER_H | ||||
| #define GPHELPER_H | ||||
|  | ||||
| #include <QtCore/QObject> | ||||
| #include <QtCore/QUrlQuery> | ||||
| #include <QtCore/QSettings> | ||||
| #include <QtNetwork/QNetworkAccessManager> | ||||
| #include <QtNetwork/QNetworkRequest> | ||||
| #include <QtNetwork/QNetworkReply> | ||||
|  | ||||
| #include "samlloginwindow.h" | ||||
| #include "gpgateway.h" | ||||
|  | ||||
|  | ||||
| const QString UA = "PAN GlobalProtect"; | ||||
|  | ||||
| namespace gpclient { | ||||
|     namespace helper { | ||||
|         extern QNetworkAccessManager *networkManager; | ||||
|  | ||||
|         QNetworkReply* createRequest(QString url, QByteArray params = nullptr); | ||||
|  | ||||
|         GPGateway filterPreferredGateway(QList<GPGateway> gateways, const QString ruleName); | ||||
|  | ||||
|         QUrlQuery parseGatewayResponse(const QByteArray& xml); | ||||
|  | ||||
|         void openMessageBox(const QString& message, const QString& informativeText = ""); | ||||
|  | ||||
|         void moveCenter(QWidget *widget); | ||||
|  | ||||
|         namespace settings { | ||||
|  | ||||
|             extern QSettings *_settings; | ||||
|             static const QStringList reservedKeys {"extraArgs", "clientos"}; | ||||
|  | ||||
|             QVariant get(const QString &key, const QVariant &defaultValue = QVariant()); | ||||
|             QStringList get_all(const QString &key, const QVariant &defaultValue = QVariant()); | ||||
|             void save(const QString &key, const QVariant &value); | ||||
|             void clear(); | ||||
|  | ||||
|             bool secureSave(const QString &key, const QString &value); | ||||
|             bool secureGet(const QString &key, QString &value); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| #endif // GPHELPER_H | ||||
| @@ -1,88 +0,0 @@ | ||||
| #include <QtCore/QUrlQuery> | ||||
|  | ||||
| #include "loginparams.h" | ||||
| #include "gphelper.h" | ||||
|  | ||||
| using namespace gpclient::helper; | ||||
|  | ||||
| LoginParams::LoginParams(const QString clientos) | ||||
| { | ||||
|     params.addQueryItem("prot", QUrl::toPercentEncoding("https:")); | ||||
|     params.addQueryItem("server", ""); | ||||
|     params.addQueryItem("inputStr", ""); | ||||
|     params.addQueryItem("jnlpReady", "jnlpReady"); | ||||
|     params.addQueryItem("user", ""); | ||||
|     params.addQueryItem("passwd", ""); | ||||
|     params.addQueryItem("computer", QUrl::toPercentEncoding(QSysInfo::machineHostName())); | ||||
|     params.addQueryItem("ok", "Login"); | ||||
|     params.addQueryItem("direct", "yes"); | ||||
|     params.addQueryItem("clientVer", "4100"); | ||||
|  | ||||
|     // add the clientos parameter if not empty | ||||
|     if (!clientos.isEmpty()) { | ||||
|         params.addQueryItem("clientos", clientos); | ||||
|     } | ||||
|  | ||||
|     auto osVersion = settings::get("os-version", "").toString(); | ||||
|     if (osVersion.isEmpty()) { | ||||
|         osVersion = QSysInfo::prettyProductName(); | ||||
|     } | ||||
|     params.addQueryItem("os-version", QUrl::toPercentEncoding(osVersion)); | ||||
|  | ||||
|     params.addQueryItem("portal-userauthcookie", ""); | ||||
|     params.addQueryItem("portal-prelogonuserauthcookie", ""); | ||||
|     params.addQueryItem("prelogin-cookie", ""); | ||||
|     params.addQueryItem("ipv6-support", "yes"); | ||||
| } | ||||
|  | ||||
| LoginParams::~LoginParams() | ||||
| { | ||||
| } | ||||
|  | ||||
| void LoginParams::setUser(const QString user) | ||||
| { | ||||
|     updateQueryItem("user", user); | ||||
| } | ||||
|  | ||||
| void LoginParams::setServer(const QString server) | ||||
| { | ||||
|     updateQueryItem("server", server); | ||||
| } | ||||
|  | ||||
| void LoginParams::setPassword(const QString password) | ||||
| { | ||||
|     updateQueryItem("passwd", password); | ||||
| } | ||||
|  | ||||
| void LoginParams::setUserAuthCookie(const QString cookie) | ||||
| { | ||||
|     updateQueryItem("portal-userauthcookie", cookie); | ||||
| } | ||||
|  | ||||
| void LoginParams::setPrelogonAuthCookie(const QString cookie) | ||||
| { | ||||
|     updateQueryItem("portal-prelogonuserauthcookie", cookie); | ||||
| } | ||||
|  | ||||
| void LoginParams::setPreloginCookie(const QString cookie) | ||||
| { | ||||
|     updateQueryItem("prelogin-cookie", cookie); | ||||
| } | ||||
|  | ||||
| void LoginParams::setInputStr(const QString inputStr) | ||||
| { | ||||
|     updateQueryItem("inputStr", inputStr); | ||||
| } | ||||
|  | ||||
| QByteArray LoginParams::toUtf8() const | ||||
| { | ||||
|     return params.toString().toUtf8(); | ||||
| } | ||||
|  | ||||
| void LoginParams::updateQueryItem(const QString key, const QString value) | ||||
| { | ||||
|     if (params.hasQueryItem(key)) { | ||||
|         params.removeQueryItem(key); | ||||
|     } | ||||
|     params.addQueryItem(key, QUrl::toPercentEncoding(value)); | ||||
| } | ||||
| @@ -1,28 +0,0 @@ | ||||
| #ifndef LOGINPARAMS_H | ||||
| #define LOGINPARAMS_H | ||||
|  | ||||
| #include <QtCore/QUrlQuery> | ||||
|  | ||||
| class LoginParams | ||||
| { | ||||
| public: | ||||
|     LoginParams(const QString clientos); | ||||
|     ~LoginParams(); | ||||
|  | ||||
|     void setUser(const QString user); | ||||
|     void setServer(const QString server); | ||||
|     void setPassword(const QString password); | ||||
|     void setUserAuthCookie(const QString cookie); | ||||
|     void setPrelogonAuthCookie(const QString cookie); | ||||
|     void setPreloginCookie(const QString cookie); | ||||
|     void setInputStr(const QString inputStr); | ||||
|  | ||||
|     QByteArray toUtf8() const; | ||||
|  | ||||
| private: | ||||
|     QUrlQuery params; | ||||
|  | ||||
|     void updateQueryItem(const QString key, const QString value); | ||||
| }; | ||||
|  | ||||
| #endif // LOGINPARAMS_H | ||||
| @@ -1,96 +0,0 @@ | ||||
| #include <QtCore/QObject> | ||||
| #include <QtCore/QString> | ||||
| #include <QtCore/QStandardPaths> | ||||
| #include <plog/Log.h> | ||||
| #include <plog/Init.h> | ||||
| #include <plog/Appenders/ColorConsoleAppender.h> | ||||
| #include <plog/Formatters/TxtFormatter.h> | ||||
|  | ||||
| #include "singleapplication.h" | ||||
| #include "gpclient.h" | ||||
| #include "vpn_dbus.h" | ||||
| #include "vpn_json.h" | ||||
| #include "enhancedwebview.h" | ||||
| #include "sigwatch.h" | ||||
| #include "version.h" | ||||
|  | ||||
| #define QT_AUTO_SCREEN_SCALE_FACTOR "QT_AUTO_SCREEN_SCALE_FACTOR" | ||||
|  | ||||
| int main(int argc, char *argv[]) | ||||
| { | ||||
|     plog::ColorConsoleAppender<plog::TxtFormatter> consoleAppender(plog::streamStdErr); | ||||
|     plog::init(plog::debug, &consoleAppender); | ||||
|  | ||||
|     LOGI << "GlobalProtect started, version: " << VERSION; | ||||
|  | ||||
|     auto port = QString::fromLocal8Bit(qgetenv(ENV_CDP_PORT)); | ||||
|     auto hidpiSupport = QString::fromLocal8Bit(qgetenv(QT_AUTO_SCREEN_SCALE_FACTOR)); | ||||
|  | ||||
|     if (port.isEmpty()) { | ||||
|         qputenv(ENV_CDP_PORT, "12315"); | ||||
|     } | ||||
|  | ||||
|     if (hidpiSupport.isEmpty()) { | ||||
|         qputenv(QT_AUTO_SCREEN_SCALE_FACTOR, "1"); | ||||
|     } | ||||
|  | ||||
|     SingleApplication app(argc, argv); | ||||
|     app.setQuitOnLastWindowClosed(false); | ||||
|  | ||||
|     QCommandLineParser parser; | ||||
|     parser.addHelpOption(); | ||||
|     parser.addVersionOption(); | ||||
|     parser.addPositionalArgument("server", "The URL of the VPN server. Optional."); | ||||
|     parser.addPositionalArgument("gateway", "The URL of the specific VPN gateway. Optional."); | ||||
|     parser.addOptions({ | ||||
|       {"json", "Write the result of the handshake with the GlobalConnect server to stdout as JSON and terminate. Useful for scripting."}, | ||||
|       {"now", "Do not show the dialog with the connect button; connect immediately instead."}, | ||||
|       {"start-minimized", "Launch the client minimized."}, | ||||
|       {"reset", "Reset the client's settings."}, | ||||
|     }); | ||||
|     parser.process(app); | ||||
|  | ||||
|     const auto positional = parser.positionalArguments(); | ||||
|  | ||||
|     auto *vpn = parser.isSet("json") // yes it leaks, but this is cleared on exit anyway | ||||
|       ? static_cast<IVpn*>(new VpnJson(nullptr)) // Print to stdout and exit | ||||
|       : static_cast<IVpn*>(new VpnDbus(nullptr)); // Contact GPService daemon via dbus | ||||
|     GPClient w(nullptr, vpn); | ||||
|  | ||||
|     if (positional.size() > 0) { | ||||
|       w.portal(positional.at(0)); | ||||
|     } | ||||
|     if (positional.size() > 1) { | ||||
|       GPGateway gw; | ||||
|       gw.setName(positional.at(1)); | ||||
|       gw.setAddress(positional.at(1)); | ||||
|       w.setCurrentGateway(gw); | ||||
|     } | ||||
|  | ||||
|     QObject::connect(&app, &SingleApplication::instanceStarted, &w, &GPClient::activate); | ||||
|  | ||||
|     UnixSignalWatcher sigwatch; | ||||
|     sigwatch.watchForSignal(SIGINT); | ||||
|     sigwatch.watchForSignal(SIGTERM); | ||||
|     sigwatch.watchForSignal(SIGQUIT); | ||||
|     sigwatch.watchForSignal(SIGHUP); | ||||
|     QObject::connect(&sigwatch, &UnixSignalWatcher::unixSignal, &w, &GPClient::quit); | ||||
|  | ||||
|     if (parser.isSet("json")) { | ||||
|         QObject::connect(static_cast<VpnJson*>(vpn), &VpnJson::connected, &w, &GPClient::quit); | ||||
|     } | ||||
|  | ||||
|     if (parser.isSet("reset")) { | ||||
|         w.reset(); | ||||
|     } | ||||
|  | ||||
|     if (parser.isSet("now")) { | ||||
|       w.doConnect(); | ||||
|     } else if (parser.isSet("start-minimized")) { | ||||
|       w.showMinimized(); | ||||
|     } else { | ||||
|       w.show(); | ||||
|     } | ||||
|  | ||||
|     return app.exec(); | ||||
| } | ||||
| Before Width: | Height: | Size: 16 KiB | 
| Before Width: | Height: | Size: 16 KiB | 
| @@ -1,219 +0,0 @@ | ||||
| #include <QtNetwork/QNetworkReply> | ||||
| #include <plog/Log.h> | ||||
|  | ||||
| #include "portalauthenticator.h" | ||||
| #include "gphelper.h" | ||||
| #include "standardloginwindow.h" | ||||
| #include "samlloginwindow.h" | ||||
| #include "loginparams.h" | ||||
| #include "preloginresponse.h" | ||||
| #include "portalconfigresponse.h" | ||||
| #include "gpgateway.h" | ||||
|  | ||||
| using namespace gpclient::helper; | ||||
|  | ||||
| PortalAuthenticator::PortalAuthenticator(const QString& portal, const QString& clientos) : QObject() | ||||
|   , portal(portal) | ||||
|   , clientos(clientos) | ||||
|   , preloginUrl("https://" + portal + "/global-protect/prelogin.esp?tmp=tmp&kerberos-support=yes&ipv6-support=yes&clientVer=4100") | ||||
|   , configUrl("https://" + portal + "/global-protect/getconfig.esp") | ||||
| { | ||||
|     if (!clientos.isEmpty()) { | ||||
|         preloginUrl = preloginUrl + "&clientos=" + clientos; | ||||
|     } | ||||
| } | ||||
|  | ||||
| PortalAuthenticator::~PortalAuthenticator() | ||||
| { | ||||
|     delete standardLoginWindow; | ||||
| } | ||||
|  | ||||
| void PortalAuthenticator::authenticate() | ||||
| { | ||||
|     attempts++; | ||||
|  | ||||
|     LOGI << QString("(%1/%2) attempts").arg(attempts).arg(MAX_ATTEMPTS) << ", perform portal prelogin at " << preloginUrl; | ||||
|  | ||||
|     QNetworkReply *reply = createRequest(preloginUrl); | ||||
|     connect(reply, &QNetworkReply::finished, this, &PortalAuthenticator::onPreloginFinished); | ||||
| } | ||||
|  | ||||
| void PortalAuthenticator::onPreloginFinished() | ||||
| { | ||||
|     auto *reply = qobject_cast<QNetworkReply*>(sender()); | ||||
|  | ||||
|     if (reply->error()) { | ||||
|         LOGE << QString("Error occurred while accessing %1, %2").arg(preloginUrl, reply->errorString()); | ||||
|         emit preloginFailed("Error occurred on the portal prelogin interface."); | ||||
|         delete reply; | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     LOGI << "Portal prelogin succeeded."; | ||||
|  | ||||
|     preloginResponse = PreloginResponse::parse(reply->readAll()); | ||||
|  | ||||
|     LOGI << "Finished parsing the prelogin response. The region field is: " << preloginResponse.region(); | ||||
|  | ||||
|     if (preloginResponse.hasSamlAuthFields()) { | ||||
|         // Do SAML authentication | ||||
|         samlAuth(); | ||||
|     } else if (preloginResponse.hasNormalAuthFields()) { | ||||
|         // Do normal username/password authentication | ||||
|         tryAutoLogin(); | ||||
|     } else { | ||||
|         LOGE << QString("Unknown prelogin response for %1 got %2").arg(preloginUrl).arg(QString::fromUtf8(preloginResponse.rawResponse())); | ||||
|         emit preloginFailed("Unknown response for portal prelogin interface."); | ||||
|     } | ||||
|  | ||||
|     delete reply; | ||||
| } | ||||
|  | ||||
| void PortalAuthenticator::tryAutoLogin() | ||||
| { | ||||
|     const QString username = settings::get("username").toString(); | ||||
|     const QString password = settings::get("password").toString(); | ||||
|  | ||||
|     if (!username.isEmpty() && !password.isEmpty()) { | ||||
|         LOGI << "Trying auto login using the saved credentials"; | ||||
|         isAutoLogin = true; | ||||
|         fetchConfig(settings::get("username").toString(), settings::get("password").toString()); | ||||
|     } else { | ||||
|         normalAuth(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void PortalAuthenticator::normalAuth() | ||||
| { | ||||
|     LOGI << "Trying to launch the normal login window..."; | ||||
|  | ||||
|     standardLoginWindow = new StandardLoginWindow {portal, preloginResponse.labelUsername(), preloginResponse.labelPassword(), preloginResponse.authMessage() }; | ||||
|  | ||||
|     // Do login | ||||
|     connect(standardLoginWindow, &StandardLoginWindow::performLogin, this, &PortalAuthenticator::onPerformNormalLogin); | ||||
|     connect(standardLoginWindow, &StandardLoginWindow::rejected, this, &PortalAuthenticator::onLoginWindowRejected); | ||||
|     connect(standardLoginWindow, &StandardLoginWindow::finished, this, &PortalAuthenticator::onLoginWindowFinished); | ||||
|  | ||||
|     standardLoginWindow->show(); | ||||
| } | ||||
|  | ||||
| void PortalAuthenticator::onPerformNormalLogin(const QString &username, const QString &password) | ||||
| { | ||||
|     standardLoginWindow->setProcessing(true); | ||||
|     fetchConfig(username, password); | ||||
| } | ||||
|  | ||||
| void PortalAuthenticator::onLoginWindowRejected() | ||||
| { | ||||
|     emitFail(); | ||||
| } | ||||
|  | ||||
| void PortalAuthenticator::onLoginWindowFinished() | ||||
| { | ||||
|     delete standardLoginWindow; | ||||
|     standardLoginWindow = nullptr; | ||||
| } | ||||
|  | ||||
| void PortalAuthenticator::samlAuth() | ||||
| { | ||||
|     LOGI << "Trying to perform SAML login with saml-method " << preloginResponse.samlMethod(); | ||||
|  | ||||
|     auto *loginWindow = new SAMLLoginWindow; | ||||
|  | ||||
|     connect(loginWindow, &SAMLLoginWindow::success, [this, loginWindow](const QMap<QString, QString> samlResult) { | ||||
|         this->onSAMLLoginSuccess(samlResult); | ||||
|         loginWindow->deleteLater(); | ||||
|     }); | ||||
|     connect(loginWindow, &SAMLLoginWindow::fail, [this, loginWindow](const QString &code, const QString msg) { | ||||
|         this->onSAMLLoginFail(code, msg); | ||||
|         loginWindow->deleteLater(); | ||||
|     }); | ||||
|     connect(loginWindow, &SAMLLoginWindow::rejected, [this, loginWindow]() { | ||||
|         this->onLoginWindowRejected(); | ||||
|         loginWindow->deleteLater(); | ||||
|     }); | ||||
|  | ||||
|     loginWindow->login(preloginResponse.samlMethod(), preloginResponse.samlRequest(), preloginUrl); | ||||
| } | ||||
|  | ||||
| void PortalAuthenticator::onSAMLLoginSuccess(const QMap<QString, QString> samlResult) | ||||
| { | ||||
|     if (samlResult.contains("preloginCookie")) { | ||||
|         LOGI << "SAML login succeeded, got the prelogin-cookie"; | ||||
|     } else { | ||||
|         LOGI << "SAML login succeeded, got the portal-userauthcookie"; | ||||
|     } | ||||
|  | ||||
|     fetchConfig(samlResult.value("username"), "", samlResult.value("preloginCookie"), samlResult.value("userAuthCookie")); | ||||
| } | ||||
|  | ||||
| void PortalAuthenticator::onSAMLLoginFail(const QString &code, const QString &msg) | ||||
| { | ||||
|     if (code == "ERR002" && attempts < MAX_ATTEMPTS) { | ||||
|         LOGI << "Failed to authenticate, trying to re-authenticate..."; | ||||
|         authenticate(); | ||||
|     } else { | ||||
|         emitFail(msg); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void PortalAuthenticator::fetchConfig(QString username, QString password, QString preloginCookie, QString userAuthCookie) | ||||
| { | ||||
|     LoginParams loginParams { clientos }; | ||||
|     loginParams.setServer(portal); | ||||
|     loginParams.setUser(username); | ||||
|     loginParams.setPassword(password); | ||||
|     loginParams.setPreloginCookie(preloginCookie); | ||||
|     loginParams.setUserAuthCookie(userAuthCookie); | ||||
|  | ||||
|     // Save the username and password for future use. | ||||
|     this->username = username; | ||||
|     this->password = password; | ||||
|  | ||||
|     LOGI << "Fetching the portal config from " << configUrl; | ||||
|  | ||||
|     auto *reply = createRequest(configUrl, loginParams.toUtf8()); | ||||
|     connect(reply, &QNetworkReply::finished, this, &PortalAuthenticator::onFetchConfigFinished); | ||||
| } | ||||
|  | ||||
| void PortalAuthenticator::onFetchConfigFinished() | ||||
| { | ||||
|     QNetworkReply *reply = qobject_cast<QNetworkReply*>(sender()); | ||||
|  | ||||
|     if (reply->error()) { | ||||
|         LOGE << QString("Failed to fetch the portal config from %1, %2").arg(configUrl).arg(reply->errorString()); | ||||
|  | ||||
|         // Login failed, enable the fields of the normal login window | ||||
|         if (standardLoginWindow) { | ||||
|             standardLoginWindow->setProcessing(false); | ||||
|             openMessageBox("Portal login failed.", "Please check your credentials and try again."); | ||||
|         } else if (isAutoLogin) { | ||||
|             isAutoLogin = false; | ||||
|             normalAuth(); | ||||
|         } else { | ||||
|             emit portalConfigFailed("Failed to fetch the portal config."); | ||||
|         } | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     LOGI << "Fetch the portal config succeeded."; | ||||
|     PortalConfigResponse response = PortalConfigResponse::parse(reply->readAll()); | ||||
|  | ||||
|     // Add the username & password to the response object | ||||
|     response.setUsername(username); | ||||
|     response.setPassword(password); | ||||
|  | ||||
|     // Close the login window | ||||
|     if (standardLoginWindow) { | ||||
|         LOGI << "Closing the StandardLoginWindow..."; | ||||
|  | ||||
|         standardLoginWindow->close(); | ||||
|     } | ||||
|  | ||||
|     emit success(response, preloginResponse.region()); | ||||
| } | ||||
|  | ||||
| void PortalAuthenticator::emitFail(const QString& msg) | ||||
| { | ||||
|     emit fail(msg); | ||||
| } | ||||
| @@ -1,60 +0,0 @@ | ||||
| #ifndef PORTALAUTHENTICATOR_H | ||||
| #define PORTALAUTHENTICATOR_H | ||||
|  | ||||
| #include <QtCore/QObject> | ||||
|  | ||||
| #include "portalconfigresponse.h" | ||||
| #include "standardloginwindow.h" | ||||
| #include "samlloginwindow.h" | ||||
| #include "preloginresponse.h" | ||||
|  | ||||
|  | ||||
| class PortalAuthenticator : public QObject | ||||
| { | ||||
|     Q_OBJECT | ||||
| public: | ||||
|     explicit PortalAuthenticator(const QString& portal, const QString& clientos); | ||||
|     ~PortalAuthenticator(); | ||||
|  | ||||
|     void authenticate(); | ||||
|  | ||||
| signals: | ||||
|     void success(const PortalConfigResponse response, const QString region); | ||||
|     void fail(const QString& msg); | ||||
|     void preloginFailed(const QString& msg); | ||||
|     void portalConfigFailed(const QString msg); | ||||
|  | ||||
| private slots: | ||||
|     void onPreloginFinished(); | ||||
|     void onPerformNormalLogin(const QString &username, const QString &password); | ||||
|     void onLoginWindowRejected(); | ||||
|     void onLoginWindowFinished(); | ||||
|     void onSAMLLoginSuccess(const QMap<QString, QString> samlResult); | ||||
|     void onSAMLLoginFail(const QString &code, const QString &msg); | ||||
|     void onFetchConfigFinished(); | ||||
|  | ||||
| private: | ||||
|     static const auto MAX_ATTEMPTS{ 5 }; | ||||
|  | ||||
|     QString portal; | ||||
|     QString clientos; | ||||
|     QString preloginUrl; | ||||
|     QString configUrl; | ||||
|     QString username; | ||||
|     QString password; | ||||
|  | ||||
|     int attempts{ 0 }; | ||||
|     PreloginResponse preloginResponse; | ||||
|  | ||||
|     bool isAutoLogin{ false }; | ||||
|  | ||||
|     StandardLoginWindow *standardLoginWindow { nullptr }; | ||||
|  | ||||
|     void tryAutoLogin(); | ||||
|     void normalAuth(); | ||||
|     void samlAuth(); | ||||
|     void fetchConfig(QString username, QString password, QString preloginCookie = "", QString userAuthCookie = ""); | ||||
|     void emitFail(const QString& msg = ""); | ||||
| }; | ||||
|  | ||||
| #endif // PORTALAUTHENTICATOR_H | ||||
| @@ -1,174 +0,0 @@ | ||||
| #include <QtCore/QXmlStreamReader> | ||||
| #include <plog/Log.h> | ||||
|  | ||||
| #include "portalconfigresponse.h" | ||||
|  | ||||
| QString PortalConfigResponse::xmlUserAuthCookie = "portal-userauthcookie"; | ||||
| QString PortalConfigResponse::xmlPrelogonUserAuthCookie = "portal-prelogonuserauthcookie"; | ||||
| QString PortalConfigResponse::xmlGateways = "gateways"; | ||||
|  | ||||
| PortalConfigResponse::PortalConfigResponse() | ||||
| { | ||||
| } | ||||
|  | ||||
| PortalConfigResponse::~PortalConfigResponse() | ||||
| { | ||||
| } | ||||
|  | ||||
| PortalConfigResponse PortalConfigResponse::parse(const QByteArray xml) | ||||
| { | ||||
|     LOGI << "Start parsing the portal configuration..."; | ||||
|  | ||||
|     QXmlStreamReader xmlReader(xml); | ||||
|     PortalConfigResponse response; | ||||
|     response.setRawResponse(xml); | ||||
|  | ||||
|     while (!xmlReader.atEnd()) { | ||||
|         xmlReader.readNextStartElement(); | ||||
|  | ||||
|         QString name = xmlReader.name().toString(); | ||||
|  | ||||
|         if (name == xmlUserAuthCookie) { | ||||
|             LOGI << "Start reading " << name; | ||||
|             response.setUserAuthCookie(xmlReader.readElementText()); | ||||
|         } else if (name == xmlPrelogonUserAuthCookie) { | ||||
|             LOGI << "Start reading " << name; | ||||
|             response.setPrelogonUserAuthCookie(xmlReader.readElementText()); | ||||
|         } else if (name == xmlGateways) { | ||||
|             response.setAllGateways(parseGateways(xmlReader)); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     LOGI << "Finished parsing portal configuration."; | ||||
|  | ||||
|     return response; | ||||
| } | ||||
|  | ||||
| const QByteArray PortalConfigResponse::rawResponse() const | ||||
| { | ||||
|     return m_rawResponse; | ||||
| } | ||||
|  | ||||
| const QString &PortalConfigResponse::username() const | ||||
| { | ||||
|     return m_username; | ||||
| } | ||||
|  | ||||
| QString PortalConfigResponse::password() const | ||||
| { | ||||
|     return m_password; | ||||
| } | ||||
|  | ||||
| QList<GPGateway> PortalConfigResponse::parseGateways(QXmlStreamReader &xmlReader) | ||||
| { | ||||
|     LOGI << "Start parsing the gateways from portal configuration..."; | ||||
|  | ||||
|     QList<GPGateway> gateways; | ||||
|  | ||||
|     while (xmlReader.name() != "external"){ | ||||
|         xmlReader.readNext(); | ||||
|     } | ||||
|  | ||||
|     while (xmlReader.name() != "list"){ | ||||
|         xmlReader.readNext(); | ||||
|     } | ||||
|  | ||||
|     while (xmlReader.name() != xmlGateways || !xmlReader.isEndElement()) { | ||||
|         xmlReader.readNext(); | ||||
|         // Parse the gateways -> external -> list -> entry | ||||
|         if (xmlReader.name() == "entry" && xmlReader.isStartElement()) { | ||||
|             GPGateway g; | ||||
|             parseGateway(xmlReader, g); | ||||
|             gateways.append(g); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     LOGI << "Finished parsing the gateways."; | ||||
|  | ||||
|     return gateways; | ||||
| } | ||||
|  | ||||
| void PortalConfigResponse::parseGateway(QXmlStreamReader &reader, GPGateway &gateway) { | ||||
|     LOGI << "Start parsing gateway..."; | ||||
|  | ||||
|     auto finished = false; | ||||
|     while (!finished) { | ||||
|         if (reader.name() == "entry" && reader.isStartElement()) { | ||||
|             auto address = reader.attributes().value("name").toString(); | ||||
|             gateway.setAddress(address); | ||||
|         } else if (reader.name() == "description" && reader.isStartElement()) { // gateway name | ||||
|             gateway.setName(reader.readElementText()); | ||||
|         } else if (reader.name() == "priority-rule" && reader.isStartElement()) { // priority rules | ||||
|             parsePriorityRule(reader, gateway); | ||||
|         } | ||||
|  | ||||
|         auto result = reader.readNext(); | ||||
|         finished = result == QXmlStreamReader::Invalid || (reader.name() == "entry" && reader.isEndElement()); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void PortalConfigResponse::parsePriorityRule(QXmlStreamReader &reader, GPGateway &gateway) { | ||||
|     LOGI << "Start parsing priority rule..."; | ||||
|  | ||||
|     QMap<QString, int> priorityRules; | ||||
|     auto finished = false; | ||||
|  | ||||
|     while (!finished) { | ||||
|         // Parse the priority-rule -> entry | ||||
|         if (reader.name() == "entry" && reader.isStartElement()) { | ||||
|             auto ruleName = reader.attributes().value("name").toString(); | ||||
|             // move to the priority value | ||||
|             while (reader.readNextStartElement()) { | ||||
|                 if (reader.name() == "priority") { | ||||
|                     auto priority = reader.readElementText().toInt(); | ||||
|                     priorityRules.insert(ruleName, priority); | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         auto result = reader.readNext(); | ||||
|         finished = result == QXmlStreamReader::Invalid || (reader.name() == "priority-rule" && reader.isEndElement()); | ||||
|     } | ||||
|  | ||||
|     gateway.setPriorityRules(priorityRules); | ||||
| } | ||||
|  | ||||
| QString PortalConfigResponse::userAuthCookie() const | ||||
| { | ||||
|     return m_userAuthCookie; | ||||
| } | ||||
|  | ||||
| QList<GPGateway> PortalConfigResponse::allGateways() const | ||||
| { | ||||
|     return m_gateways; | ||||
| } | ||||
|  | ||||
| void PortalConfigResponse::setAllGateways(QList<GPGateway> gateways) | ||||
| { | ||||
|     m_gateways = gateways; | ||||
| } | ||||
|  | ||||
| void PortalConfigResponse::setRawResponse(const QByteArray response) | ||||
| { | ||||
|     m_rawResponse = response; | ||||
| } | ||||
|  | ||||
| void PortalConfigResponse::setUsername(const QString username) | ||||
| { | ||||
|     m_username = username; | ||||
| } | ||||
|  | ||||
| void PortalConfigResponse::setPassword(const QString password) | ||||
| { | ||||
|     m_password = password; | ||||
| } | ||||
|  | ||||
| void PortalConfigResponse::setUserAuthCookie(const QString cookie) | ||||
| { | ||||
|     m_userAuthCookie = cookie; | ||||
| } | ||||
|  | ||||
| void PortalConfigResponse::setPrelogonUserAuthCookie(const QString cookie) | ||||
| { | ||||
|     m_prelogonAuthCookie = cookie; | ||||
| } | ||||
| @@ -1,51 +0,0 @@ | ||||
| #ifndef PORTALCONFIGRESPONSE_H | ||||
| #define PORTALCONFIGRESPONSE_H | ||||
|  | ||||
| #include <QtCore/QString> | ||||
| #include <QtCore/QList> | ||||
| #include <QtCore/QXmlStreamReader> | ||||
|  | ||||
| #include "gpgateway.h" | ||||
|  | ||||
| class PortalConfigResponse | ||||
| { | ||||
| public: | ||||
|     PortalConfigResponse(); | ||||
|     ~PortalConfigResponse(); | ||||
|  | ||||
|     static PortalConfigResponse parse(const QByteArray xml); | ||||
|  | ||||
|     const QByteArray rawResponse() const; | ||||
|     const QString &username() const; | ||||
|     QString password() const; | ||||
|     QString userAuthCookie() const; | ||||
|     QList<GPGateway> allGateways() const; | ||||
|     void setAllGateways(QList<GPGateway> gateways); | ||||
|  | ||||
|     void setUsername(const QString username); | ||||
|     void setPassword(const QString password); | ||||
|  | ||||
| private: | ||||
|     static QString xmlUserAuthCookie; | ||||
|     static QString xmlPrelogonUserAuthCookie; | ||||
|     static QString xmlGateways; | ||||
|  | ||||
|     QByteArray m_rawResponse; | ||||
|     QString m_username; | ||||
|     QString m_password; | ||||
|     QString m_userAuthCookie; | ||||
|     QString m_prelogonAuthCookie; | ||||
|  | ||||
|     QList<GPGateway> m_gateways; | ||||
|  | ||||
|     void setRawResponse(const QByteArray response); | ||||
|     void setUserAuthCookie(const QString cookie); | ||||
|     void setPrelogonUserAuthCookie(const QString cookie); | ||||
|  | ||||
|     static QList<GPGateway> parseGateways(QXmlStreamReader &xmlReader); | ||||
|     static void parseGateway(QXmlStreamReader &reader, GPGateway &gateway); | ||||
|     static void parsePriorityRule(QXmlStreamReader &reader, GPGateway &gateway); | ||||
|  | ||||
| }; | ||||
|  | ||||
| #endif // PORTALCONFIGRESPONSE_H | ||||
| @@ -1,100 +0,0 @@ | ||||
| #include <QtCore/QXmlStreamReader> | ||||
| #include <QtCore/QMap> | ||||
| #include <plog/Log.h> | ||||
|  | ||||
| #include "preloginresponse.h" | ||||
|  | ||||
| QString PreloginResponse::xmlAuthMessage = "authentication-message"; | ||||
| QString PreloginResponse::xmlLabelUsername = "username-label"; | ||||
| QString PreloginResponse::xmlLabelPassword = "password-label"; | ||||
| QString PreloginResponse::xmlSamlMethod = "saml-auth-method"; | ||||
| QString PreloginResponse::xmlSamlRequest = "saml-request"; | ||||
| QString PreloginResponse::xmlRegion = "region"; | ||||
|  | ||||
| PreloginResponse::PreloginResponse() | ||||
| { | ||||
|     add(xmlAuthMessage, ""); | ||||
|     add(xmlLabelUsername, ""); | ||||
|     add(xmlLabelPassword, ""); | ||||
|     add(xmlSamlMethod, ""); | ||||
|     add(xmlSamlRequest, ""); | ||||
|     add(xmlRegion, ""); | ||||
| } | ||||
|  | ||||
| PreloginResponse PreloginResponse::parse(const QByteArray& xml) | ||||
| { | ||||
|     LOGI << "Start parsing the prelogin response..."; | ||||
|  | ||||
|     QXmlStreamReader xmlReader(xml); | ||||
|     PreloginResponse response; | ||||
|     response.setRawResponse(xml); | ||||
|  | ||||
|     while (!xmlReader.atEnd()) { | ||||
|         xmlReader.readNextStartElement(); | ||||
|         QString name = xmlReader.name().toString(); | ||||
|         if (response.has(name)) { | ||||
|             response.add(name, xmlReader.readElementText()); | ||||
|         } | ||||
|     } | ||||
|     return response; | ||||
| } | ||||
|  | ||||
| const QByteArray& PreloginResponse::rawResponse() const | ||||
| { | ||||
|     return _rawResponse; | ||||
| } | ||||
|  | ||||
| QString PreloginResponse::authMessage() const | ||||
| { | ||||
|     return resultMap.value(xmlAuthMessage); | ||||
| } | ||||
|  | ||||
| QString PreloginResponse::labelUsername() const | ||||
| { | ||||
|     return resultMap.value(xmlLabelUsername); | ||||
| } | ||||
|  | ||||
| QString PreloginResponse::labelPassword() const | ||||
| { | ||||
|     return resultMap.value(xmlLabelPassword); | ||||
| } | ||||
|  | ||||
| QString PreloginResponse::samlMethod() const | ||||
| { | ||||
|     return resultMap.value(xmlSamlMethod); | ||||
| } | ||||
|  | ||||
| QString PreloginResponse::samlRequest() const | ||||
| { | ||||
|     return QByteArray::fromBase64(resultMap.value(xmlSamlRequest).toUtf8()); | ||||
| } | ||||
|  | ||||
| QString PreloginResponse::region() const | ||||
| { | ||||
|     return resultMap.value(xmlRegion); | ||||
| } | ||||
|  | ||||
| bool PreloginResponse::hasSamlAuthFields() const | ||||
| { | ||||
|     return !samlMethod().isEmpty() && !samlRequest().isEmpty(); | ||||
| } | ||||
|  | ||||
| bool PreloginResponse::hasNormalAuthFields() const | ||||
| { | ||||
|     return !labelUsername().isEmpty() && !labelPassword().isEmpty(); | ||||
| } | ||||
|  | ||||
| void PreloginResponse::setRawResponse(const QByteArray response) | ||||
| { | ||||
|     _rawResponse = response; | ||||
| } | ||||
|  | ||||
| bool PreloginResponse::has(const QString name) const | ||||
| { | ||||
|     return resultMap.contains(name); | ||||
| } | ||||
|  | ||||
| void PreloginResponse::add(const QString name, const QString value) | ||||
| { | ||||
|     resultMap.insert(name, value); | ||||
| } | ||||
| @@ -1,41 +0,0 @@ | ||||
| #ifndef PRELOGINRESPONSE_H | ||||
| #define PRELOGINRESPONSE_H | ||||
|  | ||||
| #include <QtCore/QString> | ||||
| #include <QtCore/QMap> | ||||
|  | ||||
| class PreloginResponse | ||||
| { | ||||
| public: | ||||
|     PreloginResponse(); | ||||
|  | ||||
|     static PreloginResponse parse(const QByteArray& xml); | ||||
|  | ||||
|     const QByteArray& rawResponse() const; | ||||
|     QString authMessage() const; | ||||
|     QString labelUsername() const; | ||||
|     QString labelPassword() const; | ||||
|     QString samlMethod() const; | ||||
|     QString samlRequest() const; | ||||
|     QString region() const; | ||||
|  | ||||
|     bool hasSamlAuthFields() const; | ||||
|     bool hasNormalAuthFields() const; | ||||
|  | ||||
| private: | ||||
|     static QString xmlAuthMessage; | ||||
|     static QString xmlLabelUsername; | ||||
|     static QString xmlLabelPassword; | ||||
|     static QString xmlSamlMethod; | ||||
|     static QString xmlSamlRequest; | ||||
|     static QString xmlRegion; | ||||
|  | ||||
|     QMap<QString, QString> resultMap; | ||||
|     QByteArray _rawResponse; | ||||
|  | ||||
|     void setRawResponse(const QByteArray response); | ||||
|     void add(const QString name, const QString value); | ||||
|     bool has(const QString name) const; | ||||
| }; | ||||
|  | ||||
| #endif // PRELOGINRESPONSE_H | ||||
| Before Width: | Height: | Size: 1.2 KiB | 
| Before Width: | Height: | Size: 993 B | 
| @@ -1,11 +0,0 @@ | ||||
| <RCC> | ||||
|     <qresource prefix="/images"> | ||||
|         <file alias="logo.svg">com.yuezk.qt.gpclient.svg</file> | ||||
|         <file>connected.png</file> | ||||
|         <file>pending.png</file> | ||||
|         <file>not_connected.png</file> | ||||
|         <file>radio_unselected.png</file> | ||||
|         <file>radio_selected.png</file> | ||||
|         <file>settings_icon.png</file> | ||||
|     </qresource> | ||||
| </RCC> | ||||
| @@ -1,136 +0,0 @@ | ||||
| #include <QtWidgets/QVBoxLayout> | ||||
| #include <QtWebEngineWidgets/QWebEngineProfile> | ||||
| #include <QtWebEngineWidgets/QWebEngineView> | ||||
| #include <QWebEngineCookieStore> | ||||
| #include <plog/Log.h> | ||||
|  | ||||
| #include "samlloginwindow.h" | ||||
|  | ||||
| SAMLLoginWindow::SAMLLoginWindow(QWidget *parent) | ||||
|     : QDialog(parent) | ||||
|     , webView(new EnhancedWebView(this)) | ||||
| { | ||||
|     setWindowTitle("GlobalProtect Login"); | ||||
|     setModal(true); | ||||
|     resize(700, 550); | ||||
|  | ||||
|     QVBoxLayout *verticalLayout = new QVBoxLayout(this); | ||||
|     webView->setUrl(QUrl("about:blank")); | ||||
|     webView->setAttribute(Qt::WA_DeleteOnClose); | ||||
|     verticalLayout->addWidget(webView); | ||||
|  | ||||
|     webView->initialize(); | ||||
|     connect(webView, &EnhancedWebView::responseReceived, this, &SAMLLoginWindow::onResponseReceived); | ||||
|     connect(webView, &EnhancedWebView::loadFinished, this, &SAMLLoginWindow::onLoadFinished); | ||||
|  | ||||
|     // Show the login window automatically when exceeds the MAX_WAIT_TIME | ||||
|     QTimer::singleShot(MAX_WAIT_TIME, this, [this]() { | ||||
|         if (failed) { | ||||
|             return; | ||||
|         } | ||||
|         LOGI << "MAX_WAIT_TIME exceeded, display the login window."; | ||||
|         this->show(); | ||||
|     }); | ||||
| } | ||||
|  | ||||
| void SAMLLoginWindow::closeEvent(QCloseEvent *event) | ||||
| { | ||||
|     event->accept(); | ||||
|     reject(); | ||||
| } | ||||
|  | ||||
| void SAMLLoginWindow::login(const QString samlMethod, const QString samlRequest, const QString preloginUrl) | ||||
| { | ||||
|     webView->page()->profile()->cookieStore()->deleteSessionCookies(); | ||||
|  | ||||
|     if (samlMethod == "POST") { | ||||
|         webView->setHtml(samlRequest, preloginUrl); | ||||
|     } else if (samlMethod == "REDIRECT") { | ||||
|         LOGI << "Redirect to " << samlRequest; | ||||
|         webView->load(samlRequest); | ||||
|     } else { | ||||
|         LOGE << "Unknown saml-auth-method expected POST or REDIRECT, got " << samlMethod; | ||||
|         failed = true; | ||||
|         emit fail("ERR001", "Unknown saml-auth-method, got " + samlMethod); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void SAMLLoginWindow::onResponseReceived(QJsonObject params) | ||||
| { | ||||
|     const auto type = params.value("type").toString(); | ||||
|     // Skip non-document response | ||||
|     if (type != "Document") { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     auto response = params.value("response").toObject(); | ||||
|     auto headers = response.value("headers").toObject(); | ||||
|  | ||||
|     LOGI << "Trying to receive authentication cookie from " << response.value("url").toString(); | ||||
|  | ||||
|     const auto username = headers.value("saml-username").toString(); | ||||
|     const auto preloginCookie = headers.value("prelogin-cookie").toString(); | ||||
|     const auto userAuthCookie = headers.value("portal-userauthcookie").toString(); | ||||
|  | ||||
|     this->checkSamlResult(username, preloginCookie, userAuthCookie); | ||||
| } | ||||
|  | ||||
| void SAMLLoginWindow::checkSamlResult(QString username, QString preloginCookie, QString userAuthCookie) | ||||
| { | ||||
|     LOGI << "Checking the authentication result..."; | ||||
|  | ||||
|     if (!username.isEmpty()) { | ||||
|         samlResult.insert("username", username); | ||||
|     } | ||||
|  | ||||
|     if (!preloginCookie.isEmpty()) { | ||||
|         samlResult.insert("preloginCookie", preloginCookie); | ||||
|     } | ||||
|  | ||||
|     if (!userAuthCookie.isEmpty()) { | ||||
|         samlResult.insert("userAuthCookie", userAuthCookie); | ||||
|     } | ||||
|  | ||||
|     // Check the SAML result | ||||
|     if (samlResult.contains("username") | ||||
|             && (samlResult.contains("preloginCookie") || samlResult.contains("userAuthCookie"))) { | ||||
|         LOGI << "Got the SAML authentication information successfully. " | ||||
|              << "username: " << samlResult.value("username") | ||||
|              << ", preloginCookie: " << samlResult.value("preloginCookie") | ||||
|              << ", userAuthCookie: " << samlResult.value("userAuthCookie"); | ||||
|  | ||||
|         emit success(samlResult); | ||||
|         accept(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void SAMLLoginWindow::onLoadFinished() | ||||
| { | ||||
|      LOGI << "Load finished " << webView->page()->url().toString(); | ||||
|      webView->page()->toHtml([this] (const QString &html) { this->handleHtml(html); }); | ||||
| } | ||||
|  | ||||
| void SAMLLoginWindow::handleHtml(const QString &html) | ||||
| { | ||||
|     // try to check the html body and extract from there | ||||
|     const auto samlAuthStatus = parseTag("saml-auth-status", html); | ||||
|  | ||||
|     if (samlAuthStatus == "1") { | ||||
|         const auto preloginCookie = parseTag("prelogin-cookie", html); | ||||
|         const auto username = parseTag("saml-username", html); | ||||
|         const auto userAuthCookie = parseTag("portal-userauthcookie", html); | ||||
|  | ||||
|         checkSamlResult(username, preloginCookie, userAuthCookie); | ||||
|     } else if (samlAuthStatus == "-1") { | ||||
|         LOGI << "SAML authentication failed..."; | ||||
|         failed = true; | ||||
|         emit fail("ERR002", "Authentication failed, please try again."); | ||||
|     } else { | ||||
|         show(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| QString SAMLLoginWindow::parseTag(const QString &tag, const QString &html) { | ||||
|     const QRegularExpression expression(QString("<%1>(.*)</%1>").arg(tag)); | ||||
|     return expression.match(html).captured(1); | ||||
| } | ||||
| @@ -1,41 +0,0 @@ | ||||
| #ifndef SAMLLOGINWINDOW_H | ||||
| #define SAMLLOGINWINDOW_H | ||||
|  | ||||
| #include <QtCore/QMap> | ||||
| #include <QtGui/QCloseEvent> | ||||
| #include <QtWidgets/QDialog> | ||||
|  | ||||
| #include "enhancedwebview.h" | ||||
|  | ||||
| class SAMLLoginWindow : public QDialog | ||||
| { | ||||
|     Q_OBJECT | ||||
|  | ||||
| public: | ||||
|     explicit SAMLLoginWindow(QWidget *parent = nullptr); | ||||
|  | ||||
|     void login(const QString samlMethod, const QString samlRequest, const QString preloginUrl); | ||||
|  | ||||
| signals: | ||||
|     void success(QMap<QString, QString> samlResult); | ||||
|     void fail(const QString code, const QString msg); | ||||
|  | ||||
| private slots: | ||||
|     void onResponseReceived(QJsonObject params); | ||||
|     void onLoadFinished(); | ||||
|     void checkSamlResult(QString username, QString preloginCookie, QString userAuthCookie); | ||||
|  | ||||
| private: | ||||
|     static const auto MAX_WAIT_TIME { 10 * 1000 }; | ||||
|  | ||||
|     bool failed { false }; | ||||
|     EnhancedWebView *webView { nullptr }; | ||||
|     QMap<QString, QString> samlResult; | ||||
|  | ||||
|     void closeEvent(QCloseEvent *event); | ||||
|     void handleHtml(const QString &html); | ||||
|  | ||||
|     static QString parseTag(const QString &tag, const QString &html); | ||||
| }; | ||||
|  | ||||
| #endif // SAMLLOGINWINDOW_H | ||||
| Before Width: | Height: | Size: 1.1 KiB | 
| @@ -1,42 +0,0 @@ | ||||
| #include "settingsdialog.h" | ||||
| #include "ui_settingsdialog.h" | ||||
|  | ||||
| SettingsDialog::SettingsDialog(QWidget *parent) : | ||||
|     QDialog(parent), | ||||
|     ui(new Ui::SettingsDialog) | ||||
| { | ||||
|     ui->setupUi(this); | ||||
| } | ||||
|  | ||||
| SettingsDialog::~SettingsDialog() | ||||
| { | ||||
|     delete ui; | ||||
| } | ||||
|  | ||||
| void SettingsDialog::setExtraArgs(QString extraArgs) | ||||
| { | ||||
|     ui->extraArgsInput->setPlainText(extraArgs); | ||||
| } | ||||
|  | ||||
| QString SettingsDialog::extraArgs() | ||||
| { | ||||
|     return ui->extraArgsInput->toPlainText().trimmed(); | ||||
| } | ||||
|  | ||||
| void SettingsDialog::setClientos(QString clientos) | ||||
| { | ||||
|     ui->clientosInput->setText(clientos); | ||||
| } | ||||
|  | ||||
| QString SettingsDialog::clientos() | ||||
| { | ||||
|     return ui->clientosInput->text(); | ||||
| } | ||||
|  | ||||
| void SettingsDialog::setOsVersion(QString osVersion) { | ||||
|     ui->osVersionInput->setText(osVersion); | ||||
| } | ||||
|  | ||||
| QString SettingsDialog::osVersion() { | ||||
|     return ui->osVersionInput->text(); | ||||
| } | ||||
| @@ -1,31 +0,0 @@ | ||||
| #ifndef SETTINGSDIALOG_H | ||||
| #define SETTINGSDIALOG_H | ||||
|  | ||||
| #include <QtWidgets/QDialog> | ||||
|  | ||||
| namespace Ui { | ||||
| class SettingsDialog; | ||||
| } | ||||
|  | ||||
| class SettingsDialog : public QDialog | ||||
| { | ||||
|     Q_OBJECT | ||||
|  | ||||
| public: | ||||
|     explicit SettingsDialog(QWidget *parent = nullptr); | ||||
|     ~SettingsDialog(); | ||||
|  | ||||
|     void setExtraArgs(QString extraArgs); | ||||
|     QString extraArgs(); | ||||
|  | ||||
|     void setClientos(QString clientos); | ||||
|     QString clientos(); | ||||
|  | ||||
|     void setOsVersion(QString osVersion); | ||||
|     QString osVersion(); | ||||
|  | ||||
| private: | ||||
|     Ui::SettingsDialog *ui; | ||||
| }; | ||||
|  | ||||
| #endif // SETTINGSDIALOG_H | ||||
| @@ -1,117 +0,0 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <ui version="4.0"> | ||||
|  <class>SettingsDialog</class> | ||||
|  <widget class="QDialog" name="SettingsDialog"> | ||||
|   <property name="geometry"> | ||||
|    <rect> | ||||
|     <x>0</x> | ||||
|     <y>0</y> | ||||
|     <width>488</width> | ||||
|     <height>220</height> | ||||
|    </rect> | ||||
|   </property> | ||||
|   <property name="sizePolicy"> | ||||
|    <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> | ||||
|     <horstretch>0</horstretch> | ||||
|     <verstretch>0</verstretch> | ||||
|    </sizepolicy> | ||||
|   </property> | ||||
|   <property name="windowTitle"> | ||||
|    <string>Settings</string> | ||||
|   </property> | ||||
|   <property name="windowIcon"> | ||||
|    <iconset resource="resources.qrc"> | ||||
|     <normaloff>:/images/connected.png</normaloff>:/images/connected.png</iconset> | ||||
|   </property> | ||||
|   <layout class="QFormLayout" name="formLayout_3"> | ||||
|    <item row="0" column="0"> | ||||
|     <widget class="QLabel" name="label"> | ||||
|      <property name="text"> | ||||
|       <string>Custom Parameters:</string> | ||||
|      </property> | ||||
|     </widget> | ||||
|    </item> | ||||
|    <item row="0" column="1"> | ||||
|     <widget class="QPlainTextEdit" name="extraArgsInput"> | ||||
|      <property name="readOnly"> | ||||
|       <bool>true</bool> | ||||
|      </property> | ||||
|      <property name="placeholderText"> | ||||
|       <string>The configuration has been moved to "/etc/gpservice/gp.conf"</string> | ||||
|      </property> | ||||
|     </widget> | ||||
|    </item> | ||||
|    <item row="1" column="0"> | ||||
|     <widget class="QLabel" name="label_2"> | ||||
|      <property name="text"> | ||||
|       <string>clientos:</string> | ||||
|      </property> | ||||
|     </widget> | ||||
|    </item> | ||||
|    <item row="1" column="1"> | ||||
|     <widget class="QLineEdit" name="clientosInput"> | ||||
|      <property name="placeholderText"> | ||||
|       <string>e.g., Windows</string> | ||||
|      </property> | ||||
|     </widget> | ||||
|    </item> | ||||
|    <item row="3" column="1"> | ||||
|     <widget class="QDialogButtonBox" name="buttonBox"> | ||||
|      <property name="orientation"> | ||||
|       <enum>Qt::Horizontal</enum> | ||||
|      </property> | ||||
|      <property name="standardButtons"> | ||||
|       <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> | ||||
|      </property> | ||||
|     </widget> | ||||
|    </item> | ||||
|    <item row="2" column="1"> | ||||
|     <widget class="QLineEdit" name="osVersionInput"/> | ||||
|    </item> | ||||
|    <item row="2" column="0"> | ||||
|     <widget class="QLabel" name="label_3"> | ||||
|      <property name="text"> | ||||
|       <string>os-version:</string> | ||||
|      </property> | ||||
|     </widget> | ||||
|    </item> | ||||
|   </layout> | ||||
|  </widget> | ||||
|  <resources> | ||||
|   <include location="resources.qrc"/> | ||||
|  </resources> | ||||
|  <connections> | ||||
|   <connection> | ||||
|    <sender>buttonBox</sender> | ||||
|    <signal>accepted()</signal> | ||||
|    <receiver>SettingsDialog</receiver> | ||||
|    <slot>accept()</slot> | ||||
|    <hints> | ||||
|     <hint type="sourcelabel"> | ||||
|      <x>248</x> | ||||
|      <y>254</y> | ||||
|     </hint> | ||||
|     <hint type="destinationlabel"> | ||||
|      <x>157</x> | ||||
|      <y>274</y> | ||||
|     </hint> | ||||
|    </hints> | ||||
|   </connection> | ||||
|   <connection> | ||||
|    <sender>buttonBox</sender> | ||||
|    <signal>rejected()</signal> | ||||
|    <receiver>SettingsDialog</receiver> | ||||
|    <slot>reject()</slot> | ||||
|    <hints> | ||||
|     <hint type="sourcelabel"> | ||||
|      <x>316</x> | ||||
|      <y>260</y> | ||||
|     </hint> | ||||
|     <hint type="destinationlabel"> | ||||
|      <x>286</x> | ||||
|      <y>274</y> | ||||
|     </hint> | ||||
|    </hints> | ||||
|   </connection> | ||||
|  </connections> | ||||
| </ui> | ||||
| @@ -1,60 +0,0 @@ | ||||
| #include <QtGui/QCloseEvent> | ||||
|  | ||||
| #include "standardloginwindow.h" | ||||
| #include "ui_standardloginwindow.h" | ||||
| #include "gphelper.h" | ||||
|  | ||||
| using namespace gpclient::helper; | ||||
|  | ||||
| StandardLoginWindow::StandardLoginWindow(const QString &portalAddress, const QString &labelUsername, | ||||
|                                          const QString &labelPassword, const QString &authMessage) : | ||||
|         QDialog(nullptr), | ||||
|         ui(new Ui::StandardLoginWindow) { | ||||
|     ui->setupUi(this); | ||||
|     ui->portalAddress->setText(portalAddress); | ||||
|     ui->username->setPlaceholderText(labelUsername); | ||||
|     ui->password->setPlaceholderText(labelPassword); | ||||
|     ui->authMessage->setText(authMessage); | ||||
|  | ||||
|     autocomplete(); | ||||
|  | ||||
|     setWindowTitle("GlobalProtect Login"); | ||||
|     setFixedSize(width(), height()); | ||||
|     setModal(true); | ||||
| } | ||||
|  | ||||
| void StandardLoginWindow::autocomplete() { | ||||
|     QString username, password; | ||||
|     settings::secureGet("username", username); | ||||
|     settings::secureGet("password", password); | ||||
|  | ||||
|     if (!username.isEmpty() && !password.isEmpty()) { | ||||
|         ui->username->setText(username); | ||||
|         ui->password->setText(password); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void StandardLoginWindow::setProcessing(bool isProcessing) { | ||||
|     ui->username->setReadOnly(isProcessing); | ||||
|     ui->password->setReadOnly(isProcessing); | ||||
|     ui->loginButton->setDisabled(isProcessing); | ||||
| } | ||||
|  | ||||
| void StandardLoginWindow::on_loginButton_clicked() { | ||||
|     const QString username = ui->username->text().trimmed(); | ||||
|     const QString password = ui->password->text().trimmed(); | ||||
|  | ||||
|     if (username.isEmpty() || password.isEmpty()) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     settings::secureSave("username", username); | ||||
|     settings::secureSave("password", password); | ||||
|  | ||||
|     emit performLogin(username, password); | ||||
| } | ||||
|  | ||||
| void StandardLoginWindow::closeEvent(QCloseEvent *event) { | ||||
|     event->accept(); | ||||
|     reject(); | ||||
| } | ||||
| @@ -1,34 +0,0 @@ | ||||
| #ifndef STANDARDLOGINWINDOW_H | ||||
| #define STANDARDLOGINWINDOW_H | ||||
|  | ||||
| #include <QtWidgets/QDialog> | ||||
|  | ||||
| namespace Ui { | ||||
|     class StandardLoginWindow; | ||||
| } | ||||
|  | ||||
| class StandardLoginWindow : public QDialog { | ||||
| Q_OBJECT | ||||
|  | ||||
| public: | ||||
|     explicit StandardLoginWindow(const QString &portalAddress, const QString &labelUsername, | ||||
|                                  const QString &labelPassword, const QString &authMessage); | ||||
|  | ||||
|     void setProcessing(bool isProcessing); | ||||
|  | ||||
| private slots: | ||||
|  | ||||
|     void on_loginButton_clicked(); | ||||
|  | ||||
| signals: | ||||
|  | ||||
|     void performLogin(QString username, QString password); | ||||
|  | ||||
| private: | ||||
|     Ui::StandardLoginWindow *ui; | ||||
|  | ||||
|     void closeEvent(QCloseEvent *event); | ||||
|     void autocomplete(); | ||||
| }; | ||||
|  | ||||
| #endif // STANDARDLOGINWINDOW_H | ||||
| @@ -1,148 +0,0 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <ui version="4.0"> | ||||
|  <class>StandardLoginWindow</class> | ||||
|  <widget class="QDialog" name="StandardLoginWindow"> | ||||
|   <property name="geometry"> | ||||
|    <rect> | ||||
|     <x>0</x> | ||||
|     <y>0</y> | ||||
|     <width>255</width> | ||||
|     <height>269</height> | ||||
|    </rect> | ||||
|   </property> | ||||
|   <property name="sizePolicy"> | ||||
|    <sizepolicy hsizetype="Preferred" vsizetype="Minimum"> | ||||
|     <horstretch>0</horstretch> | ||||
|     <verstretch>0</verstretch> | ||||
|    </sizepolicy> | ||||
|   </property> | ||||
|   <property name="cursor"> | ||||
|    <cursorShape>ArrowCursor</cursorShape> | ||||
|   </property> | ||||
|   <property name="windowTitle"> | ||||
|    <string>Login</string> | ||||
|   </property> | ||||
|   <property name="modal"> | ||||
|    <bool>true</bool> | ||||
|   </property> | ||||
|   <layout class="QVBoxLayout" name="verticalLayout_5"> | ||||
|    <item> | ||||
|     <layout class="QVBoxLayout" name="verticalLayout_4" stretch="1,0,0"> | ||||
|      <item> | ||||
|       <layout class="QVBoxLayout" name="verticalLayout"> | ||||
|        <item> | ||||
|         <widget class="QLabel" name="label"> | ||||
|          <property name="font"> | ||||
|           <font> | ||||
|            <pointsize>20</pointsize> | ||||
|           </font> | ||||
|          </property> | ||||
|          <property name="text"> | ||||
|           <string>Login</string> | ||||
|          </property> | ||||
|          <property name="alignment"> | ||||
|           <set>Qt::AlignCenter</set> | ||||
|          </property> | ||||
|         </widget> | ||||
|        </item> | ||||
|        <item> | ||||
|         <widget class="QLabel" name="authMessage"> | ||||
|          <property name="enabled"> | ||||
|           <bool>true</bool> | ||||
|          </property> | ||||
|          <property name="sizePolicy"> | ||||
|           <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> | ||||
|            <horstretch>0</horstretch> | ||||
|            <verstretch>2</verstretch> | ||||
|           </sizepolicy> | ||||
|          </property> | ||||
|          <property name="text"> | ||||
|           <string>Please enter the login credentials</string> | ||||
|          </property> | ||||
|          <property name="alignment"> | ||||
|           <set>Qt::AlignCenter</set> | ||||
|          </property> | ||||
|         </widget> | ||||
|        </item> | ||||
|       </layout> | ||||
|      </item> | ||||
|      <item> | ||||
|       <layout class="QVBoxLayout" name="verticalLayout_2"> | ||||
|        <property name="spacing"> | ||||
|         <number>0</number> | ||||
|        </property> | ||||
|        <property name="leftMargin"> | ||||
|         <number>6</number> | ||||
|        </property> | ||||
|        <item> | ||||
|         <widget class="QLabel" name="portalLabel"> | ||||
|          <property name="sizePolicy"> | ||||
|           <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> | ||||
|            <horstretch>0</horstretch> | ||||
|            <verstretch>0</verstretch> | ||||
|           </sizepolicy> | ||||
|          </property> | ||||
|          <property name="text"> | ||||
|           <string>Portal:</string> | ||||
|          </property> | ||||
|          <property name="margin"> | ||||
|           <number>0</number> | ||||
|          </property> | ||||
|         </widget> | ||||
|        </item> | ||||
|        <item> | ||||
|         <widget class="QLabel" name="portalAddress"> | ||||
|          <property name="sizePolicy"> | ||||
|           <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> | ||||
|            <horstretch>0</horstretch> | ||||
|            <verstretch>0</verstretch> | ||||
|           </sizepolicy> | ||||
|          </property> | ||||
|          <property name="text"> | ||||
|           <string>vpn.example.com</string> | ||||
|          </property> | ||||
|         </widget> | ||||
|        </item> | ||||
|       </layout> | ||||
|      </item> | ||||
|      <item> | ||||
|       <layout class="QVBoxLayout" name="verticalLayout_3"> | ||||
|        <item> | ||||
|         <widget class="QLineEdit" name="username"> | ||||
|          <property name="placeholderText"> | ||||
|           <string>Username</string> | ||||
|          </property> | ||||
|         </widget> | ||||
|        </item> | ||||
|        <item> | ||||
|         <widget class="QLineEdit" name="password"> | ||||
|          <property name="text"> | ||||
|           <string/> | ||||
|          </property> | ||||
|          <property name="echoMode"> | ||||
|           <enum>QLineEdit::Password</enum> | ||||
|          </property> | ||||
|          <property name="placeholderText"> | ||||
|           <string>Password</string> | ||||
|          </property> | ||||
|          <property name="clearButtonEnabled"> | ||||
|           <bool>false</bool> | ||||
|          </property> | ||||
|         </widget> | ||||
|        </item> | ||||
|        <item> | ||||
|         <widget class="QPushButton" name="loginButton"> | ||||
|          <property name="text"> | ||||
|           <string>Login</string> | ||||
|          </property> | ||||
|         </widget> | ||||
|        </item> | ||||
|       </layout> | ||||
|      </item> | ||||
|     </layout> | ||||
|    </item> | ||||
|   </layout> | ||||
|  </widget> | ||||
|  <resources/> | ||||
|  <connections/> | ||||
| </ui> | ||||
| @@ -1,24 +0,0 @@ | ||||
| #ifndef VPN_H | ||||
| #define VPN_H | ||||
| #include <QtCore/QObject> | ||||
| #include <QtCore/QString> | ||||
|  | ||||
| class IVpn | ||||
| { | ||||
| public: | ||||
|     virtual ~IVpn() = default; | ||||
|  | ||||
|     virtual void connect(const QString &preferredServer, const QList<QString> &servers, const QString &username, const QString &passwd) = 0; | ||||
|     virtual void disconnect() = 0; | ||||
|     virtual int status() = 0; | ||||
|  | ||||
| // signals: // SIGNALS | ||||
| //     virtual void connected(); | ||||
| //     virtual void disconnected(); | ||||
| //     virtual void error(const QString &errorMessage); | ||||
| //     virtual void logAvailable(const QString &log); | ||||
| }; | ||||
|  | ||||
| Q_DECLARE_INTERFACE(IVpn, "IVpn") // define this out of namespace scope | ||||
|  | ||||
| #endif | ||||
| @@ -1,13 +0,0 @@ | ||||
| #include "vpn_dbus.h" | ||||
|  | ||||
| void VpnDbus::connect(const QString &preferredServer, const QList<QString> &servers, const QString &username, const QString &passwd) { | ||||
|     inner->connect(preferredServer, username, passwd); | ||||
| } | ||||
|  | ||||
| void VpnDbus::disconnect() { | ||||
|     inner->disconnect(); | ||||
| } | ||||
|  | ||||
| int VpnDbus::status() { | ||||
|     return inner->status(); | ||||
| } | ||||
| @@ -1,33 +0,0 @@ | ||||
| #ifndef VPN_DBUS_H | ||||
| #define VPN_DBUS_H | ||||
| #include "vpn.h" | ||||
| #include "gpserviceinterface.h" | ||||
|  | ||||
| class VpnDbus : public QObject, public IVpn | ||||
| { | ||||
|   Q_OBJECT | ||||
|   Q_INTERFACES(IVpn) | ||||
|  | ||||
| private: | ||||
|   com::yuezk::qt::GPService *inner; | ||||
|  | ||||
| public: | ||||
|   VpnDbus(QObject *parent) : QObject(parent) { | ||||
|     inner = new com::yuezk::qt::GPService("com.yuezk.qt.GPService", "/", QDBusConnection::systemBus(), this); | ||||
|     QObject::connect(inner, &com::yuezk::qt::GPService::connected, this, &VpnDbus::connected); | ||||
|     QObject::connect(inner, &com::yuezk::qt::GPService::disconnected, this, &VpnDbus::disconnected); | ||||
|     QObject::connect(inner, &com::yuezk::qt::GPService::error, this, &VpnDbus::error); | ||||
|     QObject::connect(inner, &com::yuezk::qt::GPService::logAvailable, this, &VpnDbus::logAvailable); | ||||
|   } | ||||
|  | ||||
|   void connect(const QString &preferredServer, const QList<QString> &servers, const QString &username, const QString &passwd); | ||||
|   void disconnect(); | ||||
|   int status(); | ||||
|  | ||||
| signals: // SIGNALS | ||||
|   void connected(); | ||||
|   void disconnected(); | ||||
|   void error(QString errorMessage); | ||||
|   void logAvailable(QString log); | ||||
| }; | ||||
| #endif | ||||
| @@ -1,24 +0,0 @@ | ||||
| #include "vpn_json.h" | ||||
| #include <QTextStream> | ||||
| #include <QJsonDocument> | ||||
| #include <QJsonObject> | ||||
| #include <QJsonArray> | ||||
|  | ||||
| void VpnJson::connect(const QString &preferredServer, const QList<QString> &servers, const QString &username, const QString &passwd) { | ||||
|     QJsonArray sl; | ||||
|     for (const QString &srv : servers) { | ||||
|       sl.push_back(QJsonValue(srv)); | ||||
|     } | ||||
|     QJsonObject j; | ||||
|     j["server"] = preferredServer; | ||||
|     j["availableServers"] = sl; | ||||
|     j["cookie"] = passwd; | ||||
|     QTextStream(stdout) << QJsonDocument(j).toJson(QJsonDocument::Compact) << "\n"; | ||||
|     emit connected(); | ||||
| } | ||||
|  | ||||
| void VpnJson::disconnect() { /* nop */ } | ||||
|  | ||||
| int VpnJson::status() { | ||||
|     return 4; // disconnected | ||||
| } | ||||
| @@ -1,23 +0,0 @@ | ||||
| #ifndef VPN_JSON_H | ||||
| #define VPN_JSON_H | ||||
| #include "vpn.h" | ||||
|  | ||||
| class VpnJson : public QObject, public IVpn | ||||
| { | ||||
|   Q_OBJECT | ||||
|   Q_INTERFACES(IVpn) | ||||
|  | ||||
| public: | ||||
|   VpnJson(QObject *parent) : QObject(parent) {} | ||||
|  | ||||
|   void connect(const QString &preferredServer, const QList<QString> &servers, const QString &username, const QString &passwd); | ||||
|   void disconnect(); | ||||
|   int status(); | ||||
|  | ||||
| signals: // SIGNALS | ||||
|   void connected(); | ||||
|   void disconnected(); | ||||
|   void error(const QString &errorMessage); | ||||
|   void logAvailable(const QString &log); | ||||
| }; | ||||
| #endif | ||||
| @@ -1,83 +0,0 @@ | ||||
| include("${CMAKE_SOURCE_DIR}/cmake/Add3rdParty.cmake") | ||||
|  | ||||
| project(GPService) | ||||
|  | ||||
| set(gpservice_GENERATED_SOURCES) | ||||
|  | ||||
| execute_process(COMMAND logname OUTPUT_VARIABLE CMAKE_LOGNAME) | ||||
| string(STRIP "${CMAKE_LOGNAME}" CMAKE_LOGNAME) | ||||
|  | ||||
| message(STATUS "CMAKE_LOGNAME: ${CMAKE_LOGNAME}") | ||||
|  | ||||
| configure_file(dbus/com.yuezk.qt.GPService.conf.in dbus/com.yuezk.qt.GPService.conf) | ||||
| configure_file(dbus/com.yuezk.qt.GPService.service.in dbus/com.yuezk.qt.GPService.service) | ||||
| configure_file(systemd/gpservice.service.in systemd/gpservice.service) | ||||
|  | ||||
| # generate the dbus xml definition | ||||
| qt5_generate_dbus_interface( | ||||
|     gpservice.h | ||||
|     ${CMAKE_BINARY_DIR}/com.yuezk.qt.GPService.xml | ||||
| ) | ||||
|  | ||||
| # generate dbus adaptor | ||||
| qt5_add_dbus_adaptor( | ||||
|     gpservice_GENERATED_SOURCES | ||||
|     ${CMAKE_BINARY_DIR}/com.yuezk.qt.GPService.xml | ||||
|     gpservice.h | ||||
|     GPService | ||||
| ) | ||||
|  | ||||
| add_executable(gpservice | ||||
|     gpservice.h | ||||
|     gpservice.cpp | ||||
|     main.cpp | ||||
|     ${gpservice_GENERATED_SOURCES} | ||||
| ) | ||||
|  | ||||
| add_3rdparty( | ||||
|     SingleApplication | ||||
|     GIT_REPOSITORY https://github.com/itay-grudev/SingleApplication.git | ||||
|     GIT_TAG v3.3.0 | ||||
|     CMAKE_ARGS | ||||
|         -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} | ||||
|         -DCMAKE_CXX_FLAGS_RELEASE=${CMAKE_CXX_FLAGS_RELEASE} | ||||
|         -DCMAKE_FIND_ROOT_PATH=${CMAKE_FIND_ROOT_PATH} | ||||
|         -DCMAKE_PREFIX_PATH=$ENV{CMAKE_PREFIX_PATH} | ||||
|         -DQAPPLICATION_CLASS=QCoreApplication | ||||
| ) | ||||
|  | ||||
| ExternalProject_Get_Property(SingleApplication-${PROJECT_NAME} SOURCE_DIR BINARY_DIR) | ||||
|  | ||||
| set(SingleApplication_INCLUDE_DIR ${SOURCE_DIR}) | ||||
| set(SingleApplication_LIBRARY ${BINARY_DIR}/libSingleApplication.a) | ||||
|  | ||||
| add_dependencies(gpservice SingleApplication-${PROJECT_NAME}) | ||||
|  | ||||
| target_include_directories(gpservice PRIVATE | ||||
|     ${CMAKE_CURRENT_SOURCE_DIR} | ||||
|     ${CMAKE_CURRENT_BINARY_DIR} | ||||
|     ${SingleApplication_INCLUDE_DIR} | ||||
| ) | ||||
|  | ||||
| target_link_libraries(gpservice | ||||
|     ${SingleApplication_LIBRARY} | ||||
|     Qt5::Core | ||||
|     Qt5::Network | ||||
|     Qt5::DBus | ||||
|     QtSignals | ||||
|     inih | ||||
| ) | ||||
|  | ||||
| target_compile_definitions(gpservice PUBLIC QAPPLICATION_CLASS=QCoreApplication) | ||||
|  | ||||
| install(TARGETS gpservice DESTINATION bin) | ||||
| install(FILES "${CMAKE_CURRENT_BINARY_DIR}/dbus/com.yuezk.qt.GPService.conf" DESTINATION share/dbus-1/system.d ) | ||||
| install(FILES "${CMAKE_CURRENT_BINARY_DIR}/dbus/com.yuezk.qt.GPService.service" DESTINATION share/dbus-1/system-services) | ||||
| install(FILES "gp.conf" DESTINATION /etc/gpservice) | ||||
|  | ||||
| if("$ENV{DEBIAN_PACKAGE}") | ||||
|     # Install the systemd unit files to /lib/systemd/system for debian package | ||||
|     install(FILES "${CMAKE_CURRENT_BINARY_DIR}/systemd/gpservice.service" DESTINATION /lib/systemd/system) | ||||
| else() | ||||
|     install(FILES "${CMAKE_CURRENT_BINARY_DIR}/systemd/gpservice.service" DESTINATION lib/systemd/system) | ||||
| endif() | ||||
| @@ -1,18 +0,0 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <!DOCTYPE busconfig PUBLIC | ||||
| "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN" | ||||
| "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd"> | ||||
| <busconfig> | ||||
|         <policy user="root"> | ||||
|                 <allow own="com.yuezk.qt.GPService"/> | ||||
|         </policy> | ||||
|  | ||||
|         <policy context="default"> | ||||
|                 <allow send_destination="com.yuezk.qt.GPService" | ||||
|                         send_interface="com.yuezk.qt.GPService" | ||||
|                         /> | ||||
|                 <allow send_destination="com.yuezk.qt.GPService" | ||||
|                         send_interface="org.freedesktop.DBus.Introspectable" | ||||
|                         /> | ||||
|         </policy> | ||||
| </busconfig> | ||||
| @@ -1,5 +0,0 @@ | ||||
| [D-BUS Service] | ||||
| Name=com.yuezk.qt.GPService | ||||
| Exec=@CMAKE_INSTALL_PREFIX@/bin/gpservice | ||||
| User=root | ||||
| SystemdService=gpservice.service | ||||
| @@ -1,17 +0,0 @@ | ||||
| # Configuration file for GlobalProtect-openconnect | ||||
| # | ||||
| # Description: | ||||
| # | ||||
| # Each section is a VPN gateway address, and [*] is a special section that defines the default configuration. | ||||
| # See https://github.com/yuezk/GlobalProtect-openconnect/wiki/Configuration for more details. | ||||
| # | ||||
| # Example: | ||||
| # | ||||
| # [*] | ||||
| # openconnect-args=<value> | ||||
| # | ||||
| # [vpn1.company.com] | ||||
| # openconnect-args=--script=/path/to/vpnc-script | ||||
|  | ||||
| [*] | ||||
| openconnect-args= | ||||
| @@ -1,229 +0,0 @@ | ||||
| #include <QtCore/QFileInfo> | ||||
| #include <QtCore/QDateTime> | ||||
| #include <QtCore/QVariant> | ||||
| #include <QtCore/QRegularExpression> | ||||
| #include <QtCore/QRegularExpressionMatch> | ||||
| #include <QtDBus/QtDBus> | ||||
|  | ||||
| #include "INIReader.h" | ||||
| #include "gpservice.h" | ||||
| #include "gpserviceadaptor.h" | ||||
|  | ||||
| GPService::GPService(QObject *parent) | ||||
|     : QObject(parent) | ||||
|     , openconnect(new QProcess) | ||||
| { | ||||
|     // Register the DBus service | ||||
|     new GPServiceAdaptor(this); | ||||
|     QDBusConnection dbus = QDBusConnection::systemBus(); | ||||
|     dbus.registerObject("/", this); | ||||
|     dbus.registerService("com.yuezk.qt.GPService"); | ||||
|  | ||||
|     // Setup the openconnect process | ||||
|     QObject::connect(openconnect, &QProcess::started, this, &GPService::onProcessStarted); | ||||
|     QObject::connect(openconnect, &QProcess::errorOccurred, this, &GPService::onProcessError); | ||||
|     QObject::connect(openconnect, &QProcess::readyReadStandardOutput, this, &GPService::onProcessStdout); | ||||
|     QObject::connect(openconnect, &QProcess::readyReadStandardError, this, &GPService::onProcessStderr); | ||||
|     QObject::connect(openconnect, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, &GPService::onProcessFinished); | ||||
| } | ||||
|  | ||||
| GPService::~GPService() | ||||
| { | ||||
|     delete openconnect; | ||||
| } | ||||
|  | ||||
| QString GPService::findBinary() | ||||
| { | ||||
|     for (auto& binaryPath : binaryPaths) { | ||||
|         if (QFileInfo::exists(binaryPath)) { | ||||
|             return binaryPath; | ||||
|         } | ||||
|     } | ||||
|     return nullptr; | ||||
| } | ||||
|  | ||||
| QString GPService::extraOpenconnectArgs(const QString &gateway) | ||||
| { | ||||
|     INIReader reader("/etc/gpservice/gp.conf"); | ||||
|  | ||||
|     if (reader.ParseError() < 0) { | ||||
|         return ""; | ||||
|     } | ||||
|  | ||||
|     std::string defaultArgs = reader.Get("*", "openconnect-args", ""); | ||||
|     std::string extraArgs = reader.Get(gateway.toStdString(), "openconnect-args", defaultArgs); | ||||
|  | ||||
|     return QString::fromStdString(extraArgs); | ||||
| } | ||||
|  | ||||
| /* Port from https://github.com/qt/qtbase/blob/11d1dcc6e263c5059f34b44d531c9ccdf7c0b1d6/src/corelib/io/qprocess.cpp#L2115 */ | ||||
| QStringList GPService::splitCommand(const QString &command) | ||||
| { | ||||
|     QStringList args; | ||||
|     QString tmp; | ||||
|     int quoteCount = 0; | ||||
|     bool inQuote = false; | ||||
|  | ||||
|     // handle quoting. tokens can be surrounded by double quotes | ||||
|     // "hello world". three consecutive double quotes represent | ||||
|     // the quote character itself. | ||||
|     for (int i = 0; i < command.size(); ++i) { | ||||
|         if (command.at(i) == QLatin1Char('"')) { | ||||
|             ++quoteCount; | ||||
|             if (quoteCount == 3) { | ||||
|                 // third consecutive quote | ||||
|                 quoteCount = 0; | ||||
|                 tmp += command.at(i); | ||||
|             } | ||||
|             continue; | ||||
|         } | ||||
|         if (quoteCount) { | ||||
|             if (quoteCount == 1) | ||||
|                 inQuote = !inQuote; | ||||
|             quoteCount = 0; | ||||
|         } | ||||
|         if (!inQuote && command.at(i).isSpace()) { | ||||
|             if (!tmp.isEmpty()) { | ||||
|                 args += tmp; | ||||
|                 tmp.clear(); | ||||
|             } | ||||
|         } else { | ||||
|             tmp += command.at(i); | ||||
|         } | ||||
|     } | ||||
|     if (!tmp.isEmpty()) | ||||
|         args += tmp; | ||||
|  | ||||
|     return args; | ||||
| } | ||||
|  | ||||
| void GPService::quit() | ||||
| { | ||||
|     if (openconnect->state() == QProcess::NotRunning) { | ||||
|         exit(0); | ||||
|     } else { | ||||
|         aboutToQuit = true; | ||||
|         openconnect->terminate(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void GPService::connect(QString server, QString username, QString passwd) | ||||
| { | ||||
|     if (vpnStatus != GPService::VpnNotConnected) { | ||||
|         log("VPN status is: " + QVariant::fromValue(vpnStatus).toString()); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     QString bin = findBinary(); | ||||
|     if (bin == nullptr) { | ||||
|         log("Could not find openconnect binary, make sure openconnect is installed, exiting."); | ||||
|         emit error("The OpenConect CLI was not found, make sure it has been installed!"); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     if (!isValidVersion(bin)) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     const QString extraArgs = extraOpenconnectArgs(server); | ||||
|     log(QString("Got extra OpenConnect args for server: %1, %2").arg(server, extraArgs.isEmpty() ? "<empty>" : extraArgs)); | ||||
|  | ||||
|     QStringList args; | ||||
|     args << QCoreApplication::arguments().mid(1) | ||||
|          << "--protocol=gp" | ||||
|          << splitCommand(extraArgs) | ||||
|          << "-u" << username | ||||
|          << "--cookie-on-stdin" | ||||
|          << server; | ||||
|  | ||||
|     log("Start process with arguments: " + args.join(", ")); | ||||
|  | ||||
|     openconnect->start(bin, args); | ||||
|     openconnect->write((passwd + "\n").toUtf8()); | ||||
| } | ||||
|  | ||||
| bool GPService::isValidVersion(QString &bin) { | ||||
|     QProcess p; | ||||
|     p.start(bin, QStringList("--version")); | ||||
|     p.waitForFinished(); | ||||
|     QString output = p.readAllStandardError() + p.readAllStandardOutput(); | ||||
|  | ||||
|     QRegularExpression re("v(\\d+).*?(\\s|\\n)"); | ||||
|     QRegularExpressionMatch match = re.match(output); | ||||
|  | ||||
|     if (match.hasMatch()) { | ||||
|         log("Output of `openconnect --version`: " + output); | ||||
|  | ||||
|         QString fullVersion = match.captured(0); | ||||
|         QString majorVersion = match.captured(1); | ||||
|  | ||||
|         if (majorVersion.toInt() < 8) { | ||||
|             emit error("The OpenConnect version must greater than v8.0.0, got " + fullVersion); | ||||
|             return false; | ||||
|         } | ||||
|     } else { | ||||
|         log("Failed to parse the OpenConnect version from " + output); | ||||
|     } | ||||
|  | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| void GPService::disconnect() | ||||
| { | ||||
|     if (openconnect->state() != QProcess::NotRunning) { | ||||
|         vpnStatus = GPService::VpnDisconnecting; | ||||
|         openconnect->terminate(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| int GPService::status() | ||||
| { | ||||
|     return vpnStatus; | ||||
| } | ||||
|  | ||||
| void GPService::onProcessStarted() | ||||
| { | ||||
|     log("Openconnect started successfully, PID=" + QString::number(openconnect->processId())); | ||||
|     vpnStatus = GPService::VpnConnecting; | ||||
| } | ||||
|  | ||||
| void GPService::onProcessError(QProcess::ProcessError error) | ||||
| { | ||||
|     log("Error occurred: " + QVariant::fromValue(error).toString()); | ||||
|     vpnStatus = GPService::VpnNotConnected; | ||||
|     emit disconnected(); | ||||
| } | ||||
|  | ||||
| void GPService::onProcessStdout() | ||||
| { | ||||
|     QString output = openconnect->readAllStandardOutput(); | ||||
|  | ||||
|     log(output); | ||||
|     if (output.indexOf("Connected as") >= 0 || | ||||
|         output.indexOf("Configured as") >= 0 || | ||||
|         output.indexOf("Configurado como") >= 0) { | ||||
|         vpnStatus = GPService::VpnConnected; | ||||
|         emit connected(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void GPService::onProcessStderr() | ||||
| { | ||||
|     log(openconnect->readAllStandardError()); | ||||
| } | ||||
|  | ||||
| void GPService::onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus) | ||||
| { | ||||
|     log("Openconnect process exited with code " + QString::number(exitCode) + " and exit status " + QVariant::fromValue(exitStatus).toString()); | ||||
|     vpnStatus = GPService::VpnNotConnected; | ||||
|     emit disconnected(); | ||||
|  | ||||
|     if (aboutToQuit) { | ||||
|         exit(0); | ||||
|     }; | ||||
| } | ||||
|  | ||||
| void GPService::log(QString msg) | ||||
| { | ||||
|     emit logAvailable(msg); | ||||
| } | ||||
| @@ -1,62 +0,0 @@ | ||||
| #ifndef GLOBALPROTECTSERVICE_H | ||||
| #define GLOBALPROTECTSERVICE_H | ||||
|  | ||||
| #include <QtCore/QObject> | ||||
| #include <QtCore/QProcess> | ||||
|  | ||||
| static QList<QString> binaryPaths = QList<QString>() << | ||||
|     "/usr/local/bin/openconnect" << | ||||
|      "/usr/local/sbin/openconnect" << | ||||
|      "/usr/bin/openconnect" << | ||||
|      "/usr/sbin/openconnect" << | ||||
|      "/opt/bin/openconnect" << | ||||
|      "/opt/sbin/openconnect"; | ||||
|  | ||||
| class GPService : public QObject | ||||
| { | ||||
|     Q_OBJECT | ||||
|     Q_CLASSINFO("D-Bus Interface", "com.yuezk.qt.GPService") | ||||
| public: | ||||
|     explicit GPService(QObject *parent = nullptr); | ||||
|     ~GPService(); | ||||
|  | ||||
|     void quit(); | ||||
|  | ||||
|     enum VpnStatus { | ||||
|         VpnNotConnected, | ||||
|         VpnConnecting, | ||||
|         VpnConnected, | ||||
|         VpnDisconnecting, | ||||
|     }; | ||||
|  | ||||
| signals: | ||||
|     void connected(); | ||||
|     void disconnected(); | ||||
|     void error(QString errorMessage); | ||||
|     void logAvailable(QString log); | ||||
|  | ||||
| public slots: | ||||
|     void connect(QString server, QString username, QString passwd); | ||||
|     void disconnect(); | ||||
|     int status(); | ||||
|  | ||||
| private slots: | ||||
|     void onProcessStarted(); | ||||
|     void onProcessError(QProcess::ProcessError error); | ||||
|     void onProcessStdout(); | ||||
|     void onProcessStderr(); | ||||
|     void onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus); | ||||
|  | ||||
| private: | ||||
|     QProcess *openconnect; | ||||
|     bool aboutToQuit = false; | ||||
|     int vpnStatus = GPService::VpnNotConnected; | ||||
|  | ||||
|     void log(QString msg); | ||||
|     bool isValidVersion(QString &bin); | ||||
|     static QString findBinary(); | ||||
|     static QString extraOpenconnectArgs(const QString &gateway); | ||||
|     static QStringList splitCommand(const QString &command); | ||||
| }; | ||||
|  | ||||
| #endif // GLOBALPROTECTSERVICE_H | ||||
| @@ -1,27 +0,0 @@ | ||||
| #include <QtDBus/QtDBus> | ||||
|  | ||||
| #include "gpservice.h" | ||||
| #include "singleapplication.h" | ||||
| #include "sigwatch.h" | ||||
|  | ||||
| int main(int argc, char *argv[]) | ||||
| { | ||||
|     SingleApplication app(argc, argv); | ||||
|  | ||||
|     if (!QDBusConnection::systemBus().isConnected()) { | ||||
|         qWarning("Cannot connect to the D-Bus session bus.\n" | ||||
|                  "Please check your system settings and try again.\n"); | ||||
|         return 1; | ||||
|     } | ||||
|  | ||||
|     GPService service; | ||||
|  | ||||
|     UnixSignalWatcher sigwatch; | ||||
|     sigwatch.watchForSignal(SIGINT); | ||||
|     sigwatch.watchForSignal(SIGTERM); | ||||
|     sigwatch.watchForSignal(SIGQUIT); | ||||
|     sigwatch.watchForSignal(SIGHUP); | ||||
|     QObject::connect(&sigwatch, &UnixSignalWatcher::unixSignal, &service, &GPService::quit); | ||||
|  | ||||
|     return app.exec(); | ||||
| } | ||||
| @@ -1,11 +0,0 @@ | ||||
| [Unit] | ||||
| Description=GlobalProtect openconnect DBus service | ||||
|  | ||||
| [Service] | ||||
| Environment="LANG=en_US.utf8" | ||||
| Type=dbus | ||||
| BusName=com.yuezk.qt.GPService | ||||
| ExecStart=@CMAKE_INSTALL_PREFIX@/bin/gpservice | ||||
|  | ||||
| [Install] | ||||
| WantedBy=multi-user.target | ||||
| @@ -77,9 +77,9 @@ sudo dnf install globalprotect-openconnect | ||||
|  | ||||
| - openSUSE Leap | ||||
|  | ||||
|   ```sh   | ||||
|   ```sh | ||||
|   sudo zypper ar https://download.opensuse.org/repositories/home:/yuezk/15.4/home:yuezk.repo | ||||
|    | ||||
|  | ||||
|   sudo zypper ref | ||||
|   sudo zypper install globalprotect-openconnect | ||||
|   ``` | ||||
|   | ||||
							
								
								
									
										23
									
								
								apps/gpauth/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,23 @@ | ||||
| [package] | ||||
| name = "gpauth" | ||||
| version.workspace = true | ||||
| edition.workspace = true | ||||
| license.workspace = true | ||||
|  | ||||
| [build-dependencies] | ||||
| tauri-build = { version = "1.5", features = [] } | ||||
|  | ||||
| [dependencies] | ||||
| gpapi = { path = "../../crates/gpapi", features = ["tauri"] } | ||||
| anyhow.workspace = true | ||||
| clap.workspace = true | ||||
| env_logger.workspace = true | ||||
| log.workspace = true | ||||
| regex.workspace = true | ||||
| serde_json.workspace = true | ||||
| tokio.workspace = true | ||||
| tokio-util.workspace = true | ||||
| tempfile.workspace = true | ||||
| webkit2gtk = "0.18.2" | ||||
| tauri = { workspace = true, features = ["http-all"] } | ||||
| compile-time.workspace = true | ||||
							
								
								
									
										3
									
								
								apps/gpauth/build.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,3 @@ | ||||
| fn main() { | ||||
|   tauri_build::build() | ||||
| } | ||||
							
								
								
									
										
											BIN
										
									
								
								apps/gpauth/icons/128x128.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 3.4 KiB | 
							
								
								
									
										
											BIN
										
									
								
								apps/gpauth/icons/128x128@2x.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 6.8 KiB | 
							
								
								
									
										
											BIN
										
									
								
								apps/gpauth/icons/32x32.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 974 B | 
							
								
								
									
										
											BIN
										
									
								
								apps/gpauth/icons/icon.icns
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								apps/gpauth/icons/icon.ico
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 85 KiB | 
							
								
								
									
										
											BIN
										
									
								
								apps/gpauth/icons/icon.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 14 KiB | 
							
								
								
									
										11
									
								
								apps/gpauth/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,11 @@ | ||||
| <!DOCTYPE html> | ||||
| <html lang="en"> | ||||
| <head> | ||||
|   <meta charset="UTF-8"> | ||||
|   <meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||||
|   <title>GlobalProtect Login</title> | ||||
| </head> | ||||
| <body> | ||||
|   <p>Redirecting to GlobalProtect Login...</p> | ||||
| </body> | ||||
| </html> | ||||