Compare commits
	
		
			3 Commits
		
	
	
		
			snapshot
			...
			e8259b841b
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | e8259b841b | ||
|  | 99342372d2 | ||
|  | cd8d794655 | 
| @@ -1,62 +0,0 @@ | |||||||
| 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; |  | ||||||
| @@ -1,10 +0,0 @@ | |||||||
| { |  | ||||||
|   "build": { |  | ||||||
|     "dockerfile": "Dockerfile" |  | ||||||
|   }, |  | ||||||
|   "runArgs": [ |  | ||||||
|     "--privileged", |  | ||||||
|     "--cap-add=NET_ADMIN", |  | ||||||
|     "--device=/dev/net/tun" |  | ||||||
|   ] |  | ||||||
| } |  | ||||||
| @@ -1,12 +1,13 @@ | |||||||
|  | # top-most EditorConfig file | ||||||
| root = true | root = true | ||||||
|  |  | ||||||
|  | # Unix-style newlines with a newline ending every file | ||||||
| [*] | [*] | ||||||
| charset = utf-8 |  | ||||||
| indent_style = space |  | ||||||
| indent_size = 2 |  | ||||||
| end_of_line = lf | end_of_line = lf | ||||||
| insert_final_newline = true | insert_final_newline = false | ||||||
| trim_trailing_whitespace = true | trim_trailing_whitespace=true | ||||||
|  | indent_style = space | ||||||
|  | indent_size = 4 | ||||||
|  |  | ||||||
| [{Makefile,Makefile.in}] | [*.sh] | ||||||
| indent_style = tab | indent_style = tab | ||||||
|   | |||||||
							
								
								
									
										30
									
								
								.github/ISSUE_TEMPLATE/bug_report.md
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,30 +0,0 @@ | |||||||
| --- |  | ||||||
| name: Bug report |  | ||||||
| about: Create a report to help us improve |  | ||||||
| title: '' |  | ||||||
| labels: '' |  | ||||||
| assignees: '' |  | ||||||
|  |  | ||||||
| --- |  | ||||||
|  |  | ||||||
| **Describe the bug** |  | ||||||
| A clear and concise description of what the bug is. |  | ||||||
|  |  | ||||||
| **Expected behavior** |  | ||||||
| A clear and concise description of what you expected to happen. |  | ||||||
|  |  | ||||||
| **Screenshots** |  | ||||||
| If applicable, add screenshots to help explain your problem. |  | ||||||
|  |  | ||||||
| **Logs** |  | ||||||
| - For the GUI version, you can find the logs at `~/.local/share/gpclient/gpclient.log` |  | ||||||
| - For the CLI version, copy the output of the `gpclient` command. |  | ||||||
|  |  | ||||||
| **Environment:** |  | ||||||
|  - OS: [e.g. Ubuntu 22.04] |  | ||||||
|  - Desktop Environment: [e.g. GNOME or KDE] |  | ||||||
|  - Output of `ps aux | grep 'gnome-keyring\|kwalletd5' | grep -v grep`: [Required for secure store error] |  | ||||||
|  - Is remote SSH? [Yes/No] |  | ||||||
|  |  | ||||||
| **Additional context** |  | ||||||
| Add any other context about the problem here. |  | ||||||
							
								
								
									
										189
									
								
								.github/workflows/build.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,189 +0,0 @@ | |||||||
| name: Build |  | ||||||
| on: |  | ||||||
|   push: |  | ||||||
|     paths-ignore: |  | ||||||
|       - LICENSE |  | ||||||
|       - "*.md" |  | ||||||
|       - .vscode |  | ||||||
|       - .devcontainer |  | ||||||
|     branches: |  | ||||||
|       - main |  | ||||||
|       - dev |  | ||||||
|       - hotfix/* |  | ||||||
|       - feature/* |  | ||||||
|       - release/* |  | ||||||
|     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=[{"runner": "ubuntu-latest", "arch": "amd64"}, {"runner": "arm64", "arch": "arm64"}]' >> $GITHUB_OUTPUT |  | ||||||
|           else |  | ||||||
|             echo 'matrix=[{"runner": "ubuntu-latest", "arch": "amd64"}]' >> $GITHUB_OUTPUT |  | ||||||
|           fi |  | ||||||
|  |  | ||||||
|   tarball: |  | ||||||
|     runs-on: ubuntu-latest |  | ||||||
|     needs: [setup-matrix] |  | ||||||
|     steps: |  | ||||||
|     - uses: pnpm/action-setup@v2 |  | ||||||
|       with: |  | ||||||
|         version: 8 |  | ||||||
|     - name: Prepare workspace |  | ||||||
|       run: rm -rf source && mkdir source |  | ||||||
|     - name: Checkout GlobalProtect-openconnect |  | ||||||
|       uses: actions/checkout@v3 |  | ||||||
|       with: |  | ||||||
|         token: ${{ secrets.GH_PAT }} |  | ||||||
|         repository: yuezk/GlobalProtect-openconnect |  | ||||||
|         ref: ${{ github.ref }} |  | ||||||
|         path: source/gp |  | ||||||
|     - name: Create tarball |  | ||||||
|       run: | |  | ||||||
|         cd source/gp |  | ||||||
|         # Generate the SNAPSHOT file for non-tagged commits |  | ||||||
|         if [[ "${{ github.ref }}" != "refs/tags/"* ]]; then |  | ||||||
|           touch SNAPSHOT |  | ||||||
|         fi |  | ||||||
|         make tarball |  | ||||||
|     - name: Upload tarball |  | ||||||
|       uses: actions/upload-artifact@v3 |  | ||||||
|       with: |  | ||||||
|         name: artifact-source |  | ||||||
|         if-no-files-found: error |  | ||||||
|         path: | |  | ||||||
|           source/gp/.build/tarball/*.tar.gz |  | ||||||
|  |  | ||||||
|   build-gp: |  | ||||||
|     needs: |  | ||||||
|     - setup-matrix |  | ||||||
|     - tarball |  | ||||||
|     strategy: |  | ||||||
|       matrix: |  | ||||||
|         # Only build gp on amd64, as the arm64 package will be built in release.yaml |  | ||||||
|         os: [{runner: ubuntu-latest, arch: amd64}] |  | ||||||
|         package: [deb, rpm, pkg, binary] |  | ||||||
|     runs-on: ${{ matrix.os.runner }} |  | ||||||
|     name: build-gp (${{ matrix.package }}, ${{ matrix.os.arch }}) |  | ||||||
|     steps: |  | ||||||
|     - name: Prepare workspace |  | ||||||
|       run: | |  | ||||||
|         rm -rf build-gp-${{ matrix.package }} |  | ||||||
|         mkdir -p build-gp-${{ matrix.package }} |  | ||||||
|     - name: Download tarball |  | ||||||
|       uses: actions/download-artifact@v3 |  | ||||||
|       with: |  | ||||||
|         name: artifact-source |  | ||||||
|         path: build-gp-${{ matrix.package }} |  | ||||||
|     - name: Docker Login |  | ||||||
|       run: echo ${{ secrets.DOCKER_HUB_TOKEN }} | docker login -u ${{ secrets.DOCKER_HUB_USERNAME }} --password-stdin |  | ||||||
|     - name: Build ${{ matrix.package }} package in Docker |  | ||||||
|       run: | |  | ||||||
|         docker run --rm \ |  | ||||||
|           -v $(pwd)/build-gp-${{ matrix.package }}:/${{ matrix.package }} \ |  | ||||||
|           yuezk/gpdev:${{ matrix.package }}-builder |  | ||||||
|     - name: Install ${{ matrix.package }} package in Docker |  | ||||||
|       run: | |  | ||||||
|         docker run --rm \ |  | ||||||
|           -e GPGUI_INSTALLED=0 \ |  | ||||||
|           -v $(pwd)/build-gp-${{ matrix.package }}:/${{ matrix.package }} \ |  | ||||||
|           yuezk/gpdev:${{ matrix.package }}-builder \ |  | ||||||
|           bash install.sh |  | ||||||
|     - name: Upload ${{ matrix.package }} package |  | ||||||
|       uses: actions/upload-artifact@v3 |  | ||||||
|       with: |  | ||||||
|         name: artifact-gp-${{ matrix.package }}-${{ matrix.os.arch }} |  | ||||||
|         if-no-files-found: error |  | ||||||
|         path: | |  | ||||||
|           build-gp-${{ matrix.package }}/artifacts/* |  | ||||||
|  |  | ||||||
|   build-gpgui: |  | ||||||
|     needs: |  | ||||||
|     - setup-matrix |  | ||||||
|     strategy: |  | ||||||
|       matrix: |  | ||||||
|         os: ${{fromJson(needs.setup-matrix.outputs.matrix)}} |  | ||||||
|     runs-on: ${{ matrix.os.runner }} |  | ||||||
|     name: build-gpgui (${{ matrix.os.arch }}) |  | ||||||
|     steps: |  | ||||||
|     - uses: pnpm/action-setup@v2 |  | ||||||
|       with: |  | ||||||
|         version: 8 |  | ||||||
|     - name: Prepare workspace |  | ||||||
|       run: rm -rf gpgui-source && mkdir gpgui-source |  | ||||||
|     - name: Checkout GlobalProtect-openconnect |  | ||||||
|       uses: actions/checkout@v3 |  | ||||||
|       with: |  | ||||||
|         token: ${{ secrets.GH_PAT }} |  | ||||||
|         repository: yuezk/GlobalProtect-openconnect |  | ||||||
|         ref: ${{ github.ref }} |  | ||||||
|         path: gpgui-source/gp |  | ||||||
|     - name: Checkout gpgui@${{ github.ref_name }} |  | ||||||
|       uses: actions/checkout@v3 |  | ||||||
|       with: |  | ||||||
|         token: ${{ secrets.GH_PAT }} |  | ||||||
|         repository: yuezk/gpgui |  | ||||||
|         ref: ${{ github.ref_name }} |  | ||||||
|         path: gpgui-source/gpgui |  | ||||||
|     - name: Tarball |  | ||||||
|       run: | |  | ||||||
|         cd gpgui-source |  | ||||||
|         tar -czf gpgui.tar.gz gpgui gp |  | ||||||
|     - name: Docker Login |  | ||||||
|       run: echo ${{ secrets.DOCKER_HUB_TOKEN }} | docker login -u ${{ secrets.DOCKER_HUB_USERNAME }} --password-stdin |  | ||||||
|     - name: Build gpgui in Docker |  | ||||||
|       run: | |  | ||||||
|         docker run --rm -v $(pwd)/gpgui-source:/gpgui yuezk/gpdev:gpgui-builder |  | ||||||
|     - name: Install gpgui in Docker |  | ||||||
|       run: | |  | ||||||
|         cd gpgui-source |  | ||||||
|         tar -xJf *.bin.tar.xz |  | ||||||
|         docker run --rm -v $(pwd):/gpgui yuezk/gpdev:gpgui-builder \ |  | ||||||
|           bash -c "cd /gpgui/gpgui_*/ && ./gpgui --version" |  | ||||||
|     - name: Upload gpgui |  | ||||||
|       uses: actions/upload-artifact@v3 |  | ||||||
|       with: |  | ||||||
|         name: artifact-gpgui-${{ matrix.os.arch }} |  | ||||||
|         if-no-files-found: error |  | ||||||
|         path: | |  | ||||||
|           gpgui-source/*.bin.tar.xz |  | ||||||
|           gpgui-source/*.bin.tar.xz.sha256 |  | ||||||
|  |  | ||||||
|   gh-release: |  | ||||||
|     if: ${{ github.ref == 'refs/heads/dev' || startsWith(github.ref, 'refs/tags/') }} |  | ||||||
|     runs-on: ubuntu-latest |  | ||||||
|     needs: |  | ||||||
|       - tarball |  | ||||||
|       - build-gp |  | ||||||
|       - build-gpgui |  | ||||||
|  |  | ||||||
|     steps: |  | ||||||
|     - name: Prepare workspace |  | ||||||
|       run: rm -rf gh-release && mkdir gh-release |  | ||||||
|     - name: Download all artifacts |  | ||||||
|       uses: actions/download-artifact@v3 |  | ||||||
|       with: |  | ||||||
|         path: gh-release |  | ||||||
|     - name: Create GH release |  | ||||||
|       env: |  | ||||||
|         GH_TOKEN: ${{ secrets.GH_PAT }} |  | ||||||
|         RELEASE_TAG: ${{ github.ref == 'refs/heads/dev' && 'snapshot' || github.ref_name }} |  | ||||||
|         REPO: ${{ github.repository }} |  | ||||||
|         NOTES: ${{ github.ref == 'refs/heads/dev' && '**!!! DO NOT USE THIS RELEASE IN PRODUCTION !!!**' || format('Release {0}', github.ref_name) }} |  | ||||||
|       run: | |  | ||||||
|         gh -R "$REPO" release delete $RELEASE_TAG --yes --cleanup-tag || true |  | ||||||
|         gh -R "$REPO" release create $RELEASE_TAG \ |  | ||||||
|           --title "$RELEASE_TAG" \ |  | ||||||
|           --notes "$NOTES" \ |  | ||||||
|           ${{ github.ref == 'refs/heads/dev' && '--target dev' || '' }} \ |  | ||||||
|           ${{ github.ref == 'refs/heads/dev' && '--prerelease' || '' }} \ |  | ||||||
|           gh-release/artifact-source/* \ |  | ||||||
|           gh-release/artifact-gpgui-*/* |  | ||||||
							
								
								
									
										275
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,275 @@ | |||||||
|  | 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
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,33 @@ | |||||||
|  | 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 | ||||||
							
								
								
									
										89
									
								
								.github/workflows/publish.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,89 +0,0 @@ | |||||||
| name: Publish Packages |  | ||||||
|  |  | ||||||
| on: |  | ||||||
|   workflow_dispatch: |  | ||||||
|     inputs: |  | ||||||
|       tag: |  | ||||||
|         description: 'Tag to publish' |  | ||||||
|         required: true |  | ||||||
|       revision: |  | ||||||
|         description: 'Package revision' |  | ||||||
|         required: true |  | ||||||
|         default: "1" |  | ||||||
|       ppa: |  | ||||||
|         description: 'Publish to PPA' |  | ||||||
|         type: boolean |  | ||||||
|         required: true |  | ||||||
|         default: true |  | ||||||
|       obs: |  | ||||||
|         description: 'Publish to OBS' |  | ||||||
|         type: boolean |  | ||||||
|         required: true |  | ||||||
|         default: true |  | ||||||
|       aur: |  | ||||||
|         description: 'Publish to AUR' |  | ||||||
|         type: boolean |  | ||||||
|         required: true |  | ||||||
|         default: true |  | ||||||
|  |  | ||||||
| jobs: |  | ||||||
|   check: |  | ||||||
|     runs-on: ubuntu-latest |  | ||||||
|     steps: |  | ||||||
|     - name: Check tag exists |  | ||||||
|       uses: mukunku/tag-exists-action@v1.6.0 |  | ||||||
|       id: check-tag |  | ||||||
|       with: |  | ||||||
|         tag: ${{ inputs.tag }} |  | ||||||
|     - name: Exit if tag does not exist |  | ||||||
|       run: | |  | ||||||
|         if [[ "${{ steps.check-tag.outputs.exists }}" == "false" ]]; then |  | ||||||
|           echo "Tag ${{ inputs.tag }} does not exist" |  | ||||||
|           exit 1 |  | ||||||
|         fi |  | ||||||
|  |  | ||||||
|   publish-ppa: |  | ||||||
|     needs: check |  | ||||||
|     if: ${{ inputs.ppa }} |  | ||||||
|     runs-on: ubuntu-latest |  | ||||||
|     steps: |  | ||||||
|     - uses: pnpm/action-setup@v2 |  | ||||||
|       with: |  | ||||||
|         version: 8 |  | ||||||
|     - name: Prepare workspace |  | ||||||
|       run: rm -rf publish-ppa && mkdir publish-ppa |  | ||||||
|     - name: Download ${{ inputs.tag }} source code |  | ||||||
|       uses: robinraju/release-downloader@v1.9 |  | ||||||
|       with: |  | ||||||
|         token: ${{ secrets.GH_PAT }} |  | ||||||
|         tag: ${{ inputs.tag }} |  | ||||||
|         fileName: globalprotect-openconnect-*.tar.gz |  | ||||||
|         tarBall: false |  | ||||||
|         zipBall: false |  | ||||||
|         out-file-path: publish-ppa |  | ||||||
|     - name: Make the offline tarball |  | ||||||
|       run: | |  | ||||||
|         cd publish-ppa |  | ||||||
|         tar -xf globalprotect-openconnect-*.tar.gz |  | ||||||
|         cd globalprotect-openconnect-*/ |  | ||||||
|  |  | ||||||
|         make tarball OFFLINE=1 |  | ||||||
|  |  | ||||||
|         # Prepare the debian directory with custom files |  | ||||||
|         mkdir -p .build/debian |  | ||||||
|         sed 's/@RUST@/rust-all(>=1.70)/g' packaging/deb/control.in > .build/debian/control |  | ||||||
|         sed 's/@OFFLINE@/1/g' packaging/deb/rules.in > .build/debian/rules |  | ||||||
|         cp packaging/deb/postrm .build/debian/postrm |  | ||||||
|  |  | ||||||
|     - name: Publish to PPA |  | ||||||
|       uses: yuezk/publish-ppa-package@dev |  | ||||||
|       with: |  | ||||||
|         repository: "yuezk/globalprotect-openconnect" |  | ||||||
|         gpg_private_key: ${{ secrets.PPA_GPG_PRIVATE_KEY }} |  | ||||||
|         gpg_passphrase: ${{ secrets.PPA_GPG_PASSPHRASE }} |  | ||||||
|         tarball: publish-ppa/globalprotect-openconnect-*/.build/tarball/*.tar.gz |  | ||||||
|         debian_dir: publish-ppa/globalprotect-openconnect-*/.build/debian |  | ||||||
|         deb_email: "k3vinyue@gmail.com" |  | ||||||
|         deb_fullname: "Kevin Yue" |  | ||||||
|         extra_ppa: "liushuyu-011/rust-bpo-1.75" |  | ||||||
|         revision: ${{ inputs.revision }} |  | ||||||
							
								
								
									
										153
									
								
								.github/workflows/release.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,153 +0,0 @@ | |||||||
| name: Release Packages |  | ||||||
|  |  | ||||||
| on: |  | ||||||
|   workflow_dispatch: |  | ||||||
|     inputs: |  | ||||||
|       tag: |  | ||||||
|         description: 'Tag to release' |  | ||||||
|         required: true |  | ||||||
|       arch: |  | ||||||
|         type: choice |  | ||||||
|         description: 'Architecture to build' |  | ||||||
|         required: true |  | ||||||
|         default: all |  | ||||||
|         options: |  | ||||||
|           - all |  | ||||||
|           - x86_64 |  | ||||||
|           - arm64 |  | ||||||
|       release-deb: |  | ||||||
|         type: boolean |  | ||||||
|         description: 'Build DEB package' |  | ||||||
|         required: true |  | ||||||
|         default: true |  | ||||||
|       release-rpm: |  | ||||||
|         type: boolean |  | ||||||
|         description: 'Build RPM package' |  | ||||||
|         required: true |  | ||||||
|         default: true |  | ||||||
|       release-pkg: |  | ||||||
|         type: boolean |  | ||||||
|         description: 'Build PKG package' |  | ||||||
|         required: true |  | ||||||
|         default: true |  | ||||||
|       release-binary: |  | ||||||
|         type: boolean |  | ||||||
|         description: 'Build binary package' |  | ||||||
|         required: true |  | ||||||
|         default: true |  | ||||||
|       gh-release: |  | ||||||
|         type: boolean |  | ||||||
|         description: 'Update GitHub release' |  | ||||||
|         required: true |  | ||||||
|         default: true |  | ||||||
|  |  | ||||||
| jobs: |  | ||||||
|   check: |  | ||||||
|     runs-on: ubuntu-latest |  | ||||||
|     steps: |  | ||||||
|     - name: Check tag exists |  | ||||||
|       uses: mukunku/tag-exists-action@v1.6.0 |  | ||||||
|       id: check-tag |  | ||||||
|       with: |  | ||||||
|         tag: ${{ inputs.tag }} |  | ||||||
|     - name: Exit if tag does not exist |  | ||||||
|       run: | |  | ||||||
|         if [[ "${{ steps.check-tag.outputs.exists }}" == "false" ]]; then |  | ||||||
|           echo "Tag ${{ inputs.tag }} does not exist" |  | ||||||
|           exit 1 |  | ||||||
|         fi |  | ||||||
|  |  | ||||||
|   setup-matrix: |  | ||||||
|     needs: |  | ||||||
|     - check |  | ||||||
|     runs-on: ubuntu-latest |  | ||||||
|     outputs: |  | ||||||
|       matrix: ${{ steps.set-matrix.outputs.result }} |  | ||||||
|     steps: |  | ||||||
|     - name: Set up matrix |  | ||||||
|       id: set-matrix |  | ||||||
|       uses: actions/github-script@v7 |  | ||||||
|       with: |  | ||||||
|         result-encoding: string |  | ||||||
|         script: | |  | ||||||
|           const inputs = ${{ toJson(inputs) }} |  | ||||||
|           const { arch } = inputs |  | ||||||
|           const osMap = { |  | ||||||
|             "all": ["ubuntu-latest", "arm64"], |  | ||||||
|             "x86_64": ["ubuntu-latest"], |  | ||||||
|             "arm64": ["arm64"] |  | ||||||
|           } |  | ||||||
|  |  | ||||||
|           const package = Object.entries(inputs) |  | ||||||
|             .filter(([key, value]) => key.startsWith('release-') && value) |  | ||||||
|             .map(([key, value]) => key.replace('release-', '')) |  | ||||||
|  |  | ||||||
|           return JSON.stringify({ |  | ||||||
|             os: osMap[arch], |  | ||||||
|             package, |  | ||||||
|           }) |  | ||||||
|  |  | ||||||
|   build: |  | ||||||
|     needs: |  | ||||||
|     - setup-matrix |  | ||||||
|     strategy: |  | ||||||
|       matrix: ${{ fromJson(needs.setup-matrix.outputs.matrix) }} |  | ||||||
|     runs-on: ${{ matrix.os }} |  | ||||||
|     steps: |  | ||||||
|     - name: Prepare workspace |  | ||||||
|       run: rm -rf build-${{ matrix.package }} && mkdir -p build-${{ matrix.package }} |  | ||||||
|     - name: Download ${{ inputs.tag }} source code |  | ||||||
|       uses: robinraju/release-downloader@v1.9 |  | ||||||
|       with: |  | ||||||
|         token: ${{ secrets.GH_PAT }} |  | ||||||
|         tag: ${{ inputs.tag }} |  | ||||||
|         fileName: globalprotect-openconnect-*.tar.gz |  | ||||||
|         tarBall: false |  | ||||||
|         zipBall: false |  | ||||||
|         out-file-path: build-${{ matrix.package }} |  | ||||||
|     - name: Docker Login |  | ||||||
|       run: echo ${{ secrets.DOCKER_HUB_TOKEN }} | docker login -u ${{ secrets.DOCKER_HUB_USERNAME }} --password-stdin |  | ||||||
|     - name: Build ${{ matrix.package }} package in Docker |  | ||||||
|       run: | |  | ||||||
|         docker run --rm \ |  | ||||||
|           -v $(pwd)/build-${{ matrix.package }}:/${{ matrix.package }} \ |  | ||||||
|           -e INCLUDE_GUI=1 \ |  | ||||||
|           yuezk/gpdev:${{ matrix.package }}-builder |  | ||||||
|  |  | ||||||
|     - name: Install ${{ matrix.package }} package in Docker |  | ||||||
|       run: | |  | ||||||
|         docker run --rm \ |  | ||||||
|           -v $(pwd)/build-${{ matrix.package }}:/${{ matrix.package }} \ |  | ||||||
|           yuezk/gpdev:${{ matrix.package }}-builder \ |  | ||||||
|           bash install.sh |  | ||||||
|  |  | ||||||
|     - name: Upload ${{ matrix.package }} package |  | ||||||
|       uses: actions/upload-artifact@v3 |  | ||||||
|       with: |  | ||||||
|         name: artifact-${{ matrix.os }}-${{ matrix.package }} |  | ||||||
|         if-no-files-found: error |  | ||||||
|         path: | |  | ||||||
|           build-${{ matrix.package }}/artifacts/* |  | ||||||
|  |  | ||||||
|   gh-release: |  | ||||||
|     needs: |  | ||||||
|     - build |  | ||||||
|     runs-on: ubuntu-latest |  | ||||||
|     if: ${{ inputs.gh-release }} |  | ||||||
|     steps: |  | ||||||
|     - name: Prepare workspace |  | ||||||
|       run: rm -rf gh-release && mkdir gh-release |  | ||||||
|     - name: Download artifact |  | ||||||
|       uses: actions/download-artifact@v3 |  | ||||||
|       with: |  | ||||||
|         path: gh-release |  | ||||||
|     - name: Update release |  | ||||||
|       uses: softprops/action-gh-release@v1 |  | ||||||
|       with: |  | ||||||
|         token: ${{ secrets.GH_PAT }} |  | ||||||
|         prerelease: ${{ contains(github.ref, 'snapshot') }} |  | ||||||
|         fail_on_unmatched_files: true |  | ||||||
|         tag_name: ${{ inputs.tag }} |  | ||||||
|         files: | |  | ||||||
|           gh-release/artifact-*/* |  | ||||||
|  |  | ||||||
							
								
								
									
										78
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,10 +1,70 @@ | |||||||
| .idea | # Binaries | ||||||
| /target | *.rpm | ||||||
| .pnpm-store | *.gz | ||||||
| .env | *.snap | ||||||
| .vendor | .DS_Store | ||||||
| *.tar.xz | build-debian | ||||||
|  | build | ||||||
|  | artifacts | ||||||
|  |  | ||||||
| .cargo | .cmake | ||||||
| .build | .idea | ||||||
| SNAPSHOT |  | ||||||
|  | # 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* | ||||||
|   | |||||||
							
								
								
									
										10
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,10 @@ | |||||||
|  | [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
									
									
								
							
							
						
						| @@ -1,9 +0,0 @@ | |||||||
| { |  | ||||||
|     "recommendations": [ |  | ||||||
|         "rust-lang.rust-analyzer", |  | ||||||
|         "tamasfe.even-better-toml", |  | ||||||
|         "eamodio.gitlens", |  | ||||||
|         "EditorConfig.EditorConfig", |  | ||||||
|         "streetsidesoftware.code-spell-checker", |  | ||||||
|     ] |  | ||||||
| } |  | ||||||
							
								
								
									
										84
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,62 +1,26 @@ | |||||||
| { | { | ||||||
|     "cSpell.words": [ |     "files.watcherExclude": { | ||||||
|         "authcookie", |         "**/artifacts/**": true, | ||||||
|         "badssl", |     }, | ||||||
|         "bincode", |     "files.associations": { | ||||||
|         "chacha", |         "qregularexpression": "cpp", | ||||||
|         "clientos", |         "qfileinfo": "cpp", | ||||||
|         "cstring", |         "qregularexpressionmatch": "cpp", | ||||||
|         "datetime", |         "qdatetime": "cpp", | ||||||
|         "disconnectable", |         "qprocess": "cpp", | ||||||
|         "distro", |         "qobject": "cpp", | ||||||
|         "dotenv", |         "qstandardpaths": "cpp", | ||||||
|         "dotenvy", |         "qmainwindow": "cpp", | ||||||
|         "getconfig", |         "qsystemtrayicon": "cpp", | ||||||
|         "globalprotect", |         "qpushbutton": "cpp", | ||||||
|         "globalprotectcallback", |         "qmenu": "cpp", | ||||||
|         "gpapi", |         "qjsondocument": "cpp", | ||||||
|         "gpauth", |         "qnetworkaccessmanager": "cpp", | ||||||
|         "gpcallback", |         "qwebengineview": "cpp", | ||||||
|         "gpclient", |         "qprocessenvironment": "cpp", | ||||||
|         "gpcommon", |         "qnetworkreply": "cpp", | ||||||
|         "gpgui", |         "qicon": "cpp", | ||||||
|         "gpservice", |         "qsslsocket": "cpp", | ||||||
|         "hidpi", |         "qapplication": "cpp" | ||||||
|         "jnlp", |     } | ||||||
|         "LOGNAME", |  | ||||||
|         "oneshot", |  | ||||||
|         "openconnect", |  | ||||||
|         "pkcs", |  | ||||||
|         "pkexec", |  | ||||||
|         "pkey", |  | ||||||
|         "Prelogin", |  | ||||||
|         "prelogon", |  | ||||||
|         "prelogonuserauthcookie", |  | ||||||
|         "repr", |  | ||||||
|         "reqwest", |  | ||||||
|         "roxmltree", |  | ||||||
|         "rspc", |  | ||||||
|         "servercert", |  | ||||||
|         "specta", |  | ||||||
|         "sslkey", |  | ||||||
|         "sysinfo", |  | ||||||
|         "tanstack", |  | ||||||
|         "tauri", |  | ||||||
|         "tempfile", |  | ||||||
|         "thiserror", |  | ||||||
|         "tungstenite", |  | ||||||
|         "unistd", |  | ||||||
|         "unlisten", |  | ||||||
|         "urlencoding", |  | ||||||
|         "userauthcookie", |  | ||||||
|         "utsbuf", |  | ||||||
|         "uzers", |  | ||||||
|         "Vite", |  | ||||||
|         "vpnc", |  | ||||||
|         "vpninfo", |  | ||||||
|         "wmctrl", |  | ||||||
|         "XAUTHORITY", |  | ||||||
|         "yuezk" |  | ||||||
|     ], |  | ||||||
|     "rust-analyzer.cargo.features": "all", |  | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										1
									
								
								3rdparty/SingleApplication
									
									
									
									
										vendored
									
									
										Submodule
									
								
							
							
								
								
								
								
								
							
						
						
							
								
								
									
										12
									
								
								3rdparty/inih/CMakeLists.txt
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,12 @@ | |||||||
|  | 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
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,27 @@ | |||||||
|  |  | ||||||
|  | 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
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,116 @@ | |||||||
|  | // 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
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,94 @@ | |||||||
|  | // 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
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,298 @@ | |||||||
|  | /* 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
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,178 @@ | |||||||
|  | /* 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
									
									
										Submodule
									
								
							
							
								
								
								
								
								
							
						
						
							
								
								
									
										14
									
								
								3rdparty/qt-unix-signals/CMakeLists.txt
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,14 @@ | |||||||
|  | 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
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,21 @@ | |||||||
|  | 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
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,176 @@ | |||||||
|  | /* | ||||||
|  |  * 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
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,59 @@ | |||||||
|  | /* | ||||||
|  |  * 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
									
									
										Submodule
									
								
							
							
								
								
								
								
								
							
						
						
							
								
								
									
										39
									
								
								CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,39 @@ | |||||||
|  | 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) | ||||||
							
								
								
									
										5202
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
							
								
								
									
										61
									
								
								Cargo.toml
									
									
									
									
									
								
							
							
						
						| @@ -1,61 +0,0 @@ | |||||||
| [workspace] |  | ||||||
| resolver = "2" |  | ||||||
|  |  | ||||||
| members = ["crates/*", "apps/gpclient", "apps/gpservice", "apps/gpauth", "apps/gpgui-helper/src-tauri"] |  | ||||||
|  |  | ||||||
| [workspace.package] |  | ||||||
| rust-version = "1.70" |  | ||||||
| version = "2.3.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"] } |  | ||||||
| openssl = "0.10" |  | ||||||
| pem = "3" |  | ||||||
| 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" |  | ||||||
| uzers = "0.11" |  | ||||||
| whoami = "1" |  | ||||||
| thiserror = "1" |  | ||||||
| redact-engine = "0.1" |  | ||||||
| dotenvy_macro = "0.15" |  | ||||||
| compile-time = "0.2" |  | ||||||
| serde_urlencoded = "0.7" |  | ||||||
| md5="0.7" |  | ||||||
| sha256="1" |  | ||||||
|  |  | ||||||
| # Tauri dependencies |  | ||||||
| tauri = { version = "1.5" } |  | ||||||
| specta = "=2.0.0-rc.1" |  | ||||||
| specta-macros = "=2.0.0-rc.1" |  | ||||||
| rspc = { version = "1.0.0-rc.5", features = ["tauri"] } |  | ||||||
|  |  | ||||||
| [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* |  | ||||||
							
								
								
									
										111
									
								
								GPClient/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,111 @@ | |||||||
|  | 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} | ||||||
|  |     inih | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | 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) | ||||||
							
								
								
									
										30
									
								
								GPClient/cdpcommand.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,30 @@ | |||||||
|  | #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(); | ||||||
|  | } | ||||||
							
								
								
									
										24
									
								
								GPClient/cdpcommand.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,24 @@ | |||||||
|  | #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 | ||||||
							
								
								
									
										87
									
								
								GPClient/cdpcommandmanager.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,87 @@ | |||||||
|  | #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; | ||||||
|  | } | ||||||
							
								
								
									
										40
									
								
								GPClient/cdpcommandmanager.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,40 @@ | |||||||
|  | #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 | ||||||
							
								
								
									
										38
									
								
								GPClient/challengedialog.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,38 @@ | |||||||
|  | #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); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										28
									
								
								GPClient/challengedialog.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,28 @@ | |||||||
|  | #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 | ||||||
							
								
								
									
										111
									
								
								GPClient/challengedialog.ui
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,111 @@ | |||||||
|  | <?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> | ||||||
							
								
								
									
										12
									
								
								GPClient/com.yuezk.qt.gpclient.desktop.in
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,12 @@ | |||||||
|  | [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 | ||||||
							
								
								
									
										43
									
								
								GPClient/com.yuezk.qt.gpclient.metainfo.xml.in
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,43 @@ | |||||||
|  | <?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> | ||||||
| Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 6.7 KiB | 
							
								
								
									
										
											BIN
										
									
								
								GPClient/connected.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 18 KiB | 
							
								
								
									
										8
									
								
								GPClient/enhancedwebpage.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,8 @@ | |||||||
|  | #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(); | ||||||
|  | }; | ||||||
							
								
								
									
										12
									
								
								GPClient/enhancedwebpage.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,12 @@ | |||||||
|  | #ifndef ENHANCEDWEBPAGE_H | ||||||
|  | #define ENHANCEDWEBPAGE_H | ||||||
|  |  | ||||||
|  | #include <QtWebEngineWidgets/qwebenginepage.h> | ||||||
|  |  | ||||||
|  | class EnhancedWebPage : public QWebEnginePage | ||||||
|  | { | ||||||
|  | protected: | ||||||
|  |     bool certificateError(const QWebEngineCertificateError &certificateError) override; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | #endif // !ECHANCEDWEBPAG | ||||||
							
								
								
									
										33
									
								
								GPClient/enhancedwebview.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,33 @@ | |||||||
|  | #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); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										29
									
								
								GPClient/enhancedwebview.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,29 @@ | |||||||
|  | #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 | ||||||
							
								
								
									
										226
									
								
								GPClient/gatewayauthenticator.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,226 @@ | |||||||
|  | #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(gateway); | ||||||
|  |  | ||||||
|  |     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(); | ||||||
|  | } | ||||||
							
								
								
									
										48
									
								
								GPClient/gatewayauthenticator.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,48 @@ | |||||||
|  | #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 | ||||||
							
								
								
									
										66
									
								
								GPClient/gatewayauthenticatorparams.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,66 @@ | |||||||
|  | #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; | ||||||
|  | } | ||||||
							
								
								
									
										38
									
								
								GPClient/gatewayauthenticatorparams.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,38 @@ | |||||||
|  | #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 | ||||||
							
								
								
									
										519
									
								
								GPClient/gpclient.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,519 @@ | |||||||
|  | #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; | ||||||
|  | } | ||||||
							
								
								
									
										104
									
								
								GPClient/gpclient.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,104 @@ | |||||||
|  | #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 | ||||||
							
								
								
									
										143
									
								
								GPClient/gpclient.ui
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,143 @@ | |||||||
|  | <?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> | ||||||
							
								
								
									
										97
									
								
								GPClient/gpgateway.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,97 @@ | |||||||
|  | #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; | ||||||
|  | } | ||||||
							
								
								
									
										33
									
								
								GPClient/gpgateway.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,33 @@ | |||||||
|  | #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 | ||||||
							
								
								
									
										178
									
								
								GPClient/gphelper.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,178 @@ | |||||||
|  | #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; | ||||||
|  | } | ||||||
							
								
								
									
										47
									
								
								GPClient/gphelper.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,47 @@ | |||||||
|  | #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 | ||||||
							
								
								
									
										88
									
								
								GPClient/loginparams.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,88 @@ | |||||||
|  | #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)); | ||||||
|  | } | ||||||
							
								
								
									
										28
									
								
								GPClient/loginparams.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,28 @@ | |||||||
|  | #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 | ||||||
							
								
								
									
										96
									
								
								GPClient/main.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,96 @@ | |||||||
|  | #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(); | ||||||
|  | } | ||||||
							
								
								
									
										
											BIN
										
									
								
								GPClient/not_connected.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 16 KiB | 
							
								
								
									
										
											BIN
										
									
								
								GPClient/pending.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 16 KiB | 
							
								
								
									
										219
									
								
								GPClient/portalauthenticator.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,219 @@ | |||||||
|  | #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(this->portal); | ||||||
|  |  | ||||||
|  |     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); | ||||||
|  | } | ||||||
							
								
								
									
										60
									
								
								GPClient/portalauthenticator.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,60 @@ | |||||||
|  | #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 | ||||||
							
								
								
									
										174
									
								
								GPClient/portalconfigresponse.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,174 @@ | |||||||
|  | #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; | ||||||
|  | } | ||||||
							
								
								
									
										51
									
								
								GPClient/portalconfigresponse.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,51 @@ | |||||||
|  | #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 | ||||||
							
								
								
									
										100
									
								
								GPClient/preloginresponse.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,100 @@ | |||||||
|  | #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); | ||||||
|  | } | ||||||
							
								
								
									
										41
									
								
								GPClient/preloginresponse.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,41 @@ | |||||||
|  | #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 | ||||||
							
								
								
									
										
											BIN
										
									
								
								GPClient/radio_selected.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.2 KiB | 
							
								
								
									
										
											BIN
										
									
								
								GPClient/radio_unselected.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 993 B | 
							
								
								
									
										11
									
								
								GPClient/resources.qrc
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,11 @@ | |||||||
|  | <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> | ||||||
							
								
								
									
										165
									
								
								GPClient/samlloginwindow.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,165 @@ | |||||||
|  | #include <QtWidgets/QVBoxLayout> | ||||||
|  | #include <QtWebEngineWidgets/QWebEngineProfile> | ||||||
|  | #include <QtWebEngineWidgets/QWebEngineView> | ||||||
|  | #include <QWebEngineCookieStore> | ||||||
|  | #include <plog/Log.h> | ||||||
|  |  | ||||||
|  | #include "INIReader.h" | ||||||
|  | #include "samlloginwindow.h" | ||||||
|  |  | ||||||
|  | SAMLLoginWindow::SAMLLoginWindow(QString portal, 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); | ||||||
|  |  | ||||||
|  |     // Portal | ||||||
|  |     this->portal = portal; | ||||||
|  |  | ||||||
|  |     // 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); }); | ||||||
|  |      QMap<QString, QString> credentials = this->loadCredentials(); | ||||||
|  |      webView->page()->runJavaScript("document.getElementById('username').value='" + credentials["username"] + "';"); | ||||||
|  |      webView->page()->runJavaScript("document.getElementById('password').value='" + credentials["password"] + "';"); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | 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); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | QMap<QString, QString> SAMLLoginWindow::loadCredentials() | ||||||
|  | { | ||||||
|  |     std::string home = getenv("HOME"); | ||||||
|  |     std::string iniFile = home + "/.gpclient-credentials"; | ||||||
|  |     INIReader reader(iniFile); | ||||||
|  |  | ||||||
|  |     QMap<QString, QString> credentials; | ||||||
|  |     if (reader.ParseError() < 0) { | ||||||
|  |         LOGE << "File '" << iniFile << "' not found."; | ||||||
|  |         return credentials; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (reader.HasSection(this->portal.toStdString())) { | ||||||
|  |         credentials.insert(QString("username"), QString::fromStdString(reader.Get(this->portal.toStdString(), "username", ""))); | ||||||
|  |         credentials.insert(QString("password"), QString::fromStdString(reader.Get(this->portal.toStdString(), "password", ""))); | ||||||
|  |     } else { | ||||||
|  |         LOGE << "No credentials found for '" << this->portal.toStdString() << "' in '" << iniFile << "'"; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return credentials; | ||||||
|  | } | ||||||
							
								
								
									
										43
									
								
								GPClient/samlloginwindow.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,43 @@ | |||||||
|  | #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(QString portal, QWidget *parent = nullptr); | ||||||
|  |  | ||||||
|  |     void login(const QString samlMethod, const QString samlRequest, const QString preloginUrl); | ||||||
|  |     QMap<QString, QString> loadCredentials(); | ||||||
|  |  | ||||||
|  | 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; | ||||||
|  |     QString portal; | ||||||
|  |  | ||||||
|  |     void closeEvent(QCloseEvent *event); | ||||||
|  |     void handleHtml(const QString &html); | ||||||
|  |  | ||||||
|  |     static QString parseTag(const QString &tag, const QString &html); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | #endif // SAMLLOGINWINDOW_H | ||||||
							
								
								
									
										
											BIN
										
									
								
								GPClient/settings_icon.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.1 KiB | 
							
								
								
									
										42
									
								
								GPClient/settingsdialog.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,42 @@ | |||||||
|  | #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(); | ||||||
|  | } | ||||||
							
								
								
									
										31
									
								
								GPClient/settingsdialog.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,31 @@ | |||||||
|  | #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 | ||||||
							
								
								
									
										117
									
								
								GPClient/settingsdialog.ui
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,117 @@ | |||||||
|  | <?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> | ||||||
							
								
								
									
										60
									
								
								GPClient/standardloginwindow.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,60 @@ | |||||||
|  | #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(); | ||||||
|  | } | ||||||
							
								
								
									
										34
									
								
								GPClient/standardloginwindow.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,34 @@ | |||||||
|  | #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 | ||||||
							
								
								
									
										148
									
								
								GPClient/standardloginwindow.ui
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,148 @@ | |||||||
|  | <?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> | ||||||
							
								
								
									
										24
									
								
								GPClient/vpn.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,24 @@ | |||||||
|  | #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 | ||||||
							
								
								
									
										13
									
								
								GPClient/vpn_dbus.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,13 @@ | |||||||
|  | #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(); | ||||||
|  | } | ||||||
							
								
								
									
										33
									
								
								GPClient/vpn_dbus.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,33 @@ | |||||||
|  | #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 | ||||||
							
								
								
									
										24
									
								
								GPClient/vpn_json.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,24 @@ | |||||||
|  | #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 | ||||||
|  | } | ||||||
							
								
								
									
										23
									
								
								GPClient/vpn_json.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,23 @@ | |||||||
|  | #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 | ||||||
							
								
								
									
										83
									
								
								GPService/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,83 @@ | |||||||
|  | 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() | ||||||
							
								
								
									
										18
									
								
								GPService/dbus/com.yuezk.qt.GPService.conf.in
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,18 @@ | |||||||
|  | <?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> | ||||||
							
								
								
									
										5
									
								
								GPService/dbus/com.yuezk.qt.GPService.service.in
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,5 @@ | |||||||
|  | [D-BUS Service] | ||||||
|  | Name=com.yuezk.qt.GPService | ||||||
|  | Exec=@CMAKE_INSTALL_PREFIX@/bin/gpservice | ||||||
|  | User=root | ||||||
|  | SystemdService=gpservice.service | ||||||
							
								
								
									
										17
									
								
								GPService/gp.conf
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,17 @@ | |||||||
|  | # 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= | ||||||
							
								
								
									
										229
									
								
								GPService/gpservice.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,229 @@ | |||||||
|  | #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); | ||||||
|  | } | ||||||
							
								
								
									
										62
									
								
								GPService/gpservice.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,62 @@ | |||||||
|  | #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 | ||||||
							
								
								
									
										27
									
								
								GPService/main.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,27 @@ | |||||||
|  | #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(); | ||||||
|  | } | ||||||
							
								
								
									
										11
									
								
								GPService/systemd/gpservice.service.in
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,11 @@ | |||||||
|  | [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 | ||||||
							
								
								
									
										263
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						| @@ -1,263 +0,0 @@ | |||||||
| .SHELLFLAGS += -e |  | ||||||
|  |  | ||||||
| OFFLINE ?= 0 |  | ||||||
| BUILD_FE ?= 1 |  | ||||||
| INCLUDE_GUI ?= 0 |  | ||||||
| CARGO ?= cargo |  | ||||||
|  |  | ||||||
| VERSION = $(shell $(CARGO) metadata --no-deps --format-version 1 | jq -r '.packages[0].version') |  | ||||||
| REVISION ?= 1 |  | ||||||
| PPA_REVISION ?= 1 |  | ||||||
| PKG_NAME = globalprotect-openconnect |  | ||||||
| PKG = $(PKG_NAME)-$(VERSION) |  | ||||||
| SERIES ?= $(shell lsb_release -cs) |  | ||||||
| PUBLISH ?= 0 |  | ||||||
|  |  | ||||||
| export DEBEMAIL = k3vinyue@gmail.com |  | ||||||
| export DEBFULLNAME = Kevin Yue |  | ||||||
| export SNAPSHOT = $(shell test -f SNAPSHOT && echo "true" || echo "false") |  | ||||||
|  |  | ||||||
| ifeq ($(SNAPSHOT), true) |  | ||||||
| 	RELEASE_TAG = snapshot |  | ||||||
| else |  | ||||||
| 	RELEASE_TAG = v$(VERSION) |  | ||||||
| endif |  | ||||||
|  |  | ||||||
| CARGO_BUILD_ARGS = --release |  | ||||||
|  |  | ||||||
| ifeq ($(OFFLINE), 1) |  | ||||||
| 	CARGO_BUILD_ARGS += --frozen |  | ||||||
| endif |  | ||||||
|  |  | ||||||
| default: build |  | ||||||
|  |  | ||||||
| version: |  | ||||||
| 	@echo $(VERSION) |  | ||||||
|  |  | ||||||
| clean-tarball: |  | ||||||
| 	rm -rf .build/tarball |  | ||||||
| 	rm -rf .vendor |  | ||||||
| 	rm -rf vendor.tar.xz |  | ||||||
| 	rm -rf .cargo |  | ||||||
|  |  | ||||||
| # Create a tarball, include the cargo dependencies if OFFLINE is set to 1 |  | ||||||
| tarball: clean-tarball |  | ||||||
| 	if [ $(BUILD_FE) -eq 1 ]; then \ |  | ||||||
| 		echo "Building frontend..."; \ |  | ||||||
| 		cd apps/gpgui-helper && pnpm install && pnpm build; \ |  | ||||||
| 	fi |  | ||||||
|  |  | ||||||
| 	# Remove node_modules to reduce the tarball size |  | ||||||
| 	rm -rf apps/gpgui-helper/node_modules |  | ||||||
|  |  | ||||||
| 	mkdir -p .cargo |  | ||||||
| 	mkdir -p .build/tarball |  | ||||||
|  |  | ||||||
| 	# If OFFLINE is set to 1, vendor all cargo dependencies |  | ||||||
| 	if [ $(OFFLINE) -eq 1 ]; then \ |  | ||||||
| 		$(CARGO) vendor .vendor > .cargo/config.toml; \ |  | ||||||
| 		tar -cJf vendor.tar.xz .vendor; \ |  | ||||||
| 	fi |  | ||||||
|  |  | ||||||
| 	@echo "Creating tarball..." |  | ||||||
| 	tar --exclude .vendor --exclude target --transform 's,^,${PKG}/,' -czf .build/tarball/${PKG}.tar.gz * .cargo |  | ||||||
|  |  | ||||||
| download-gui: |  | ||||||
| 	rm -rf .build/gpgui |  | ||||||
|  |  | ||||||
| 	if [ $(INCLUDE_GUI) -eq 1 ]; then \ |  | ||||||
| 		echo "Downloading GlobalProtect GUI..."; \ |  | ||||||
| 		mkdir -p .build/gpgui; \ |  | ||||||
| 		curl -sSL https://github.com/yuezk/GlobalProtect-openconnect/releases/download/$(RELEASE_TAG)/gpgui_$(shell uname -m).bin.tar.xz \ |  | ||||||
| 			-o .build/gpgui/gpgui_$(shell uname -m).bin.tar.xz; \ |  | ||||||
| 		tar -xJf .build/gpgui/*.tar.xz -C .build/gpgui; \ |  | ||||||
| 	else \ |  | ||||||
| 		echo "Skipping GlobalProtect GUI download (INCLUDE_GUI=0)"; \ |  | ||||||
| 	fi |  | ||||||
|  |  | ||||||
| build: download-gui build-fe build-rs |  | ||||||
|  |  | ||||||
| # Install and build the frontend |  | ||||||
| # If OFFLINE is set to 1, skip it |  | ||||||
| build-fe: |  | ||||||
| 	if [ $(OFFLINE) -eq 1 ] || [ $(BUILD_FE) -eq 0 ]; then \ |  | ||||||
| 		echo "Skipping frontend build (OFFLINE=1 or BUILD_FE=0)"; \ |  | ||||||
| 	else \ |  | ||||||
| 		cd apps/gpgui-helper && pnpm install && pnpm build; \ |  | ||||||
| 	fi |  | ||||||
|  |  | ||||||
| 	if [ ! -d apps/gpgui-helper/dist ]; then \ |  | ||||||
| 		echo "Error: frontend build failed"; \ |  | ||||||
| 		exit 1; \ |  | ||||||
| 	fi |  | ||||||
|  |  | ||||||
| build-rs: |  | ||||||
| 	if [ $(OFFLINE) -eq 1 ]; then \ |  | ||||||
| 		tar -xJf vendor.tar.xz; \ |  | ||||||
| 	fi |  | ||||||
|  |  | ||||||
| 	$(CARGO) build $(CARGO_BUILD_ARGS) -p gpclient -p gpservice -p gpauth |  | ||||||
| 	$(CARGO) build $(CARGO_BUILD_ARGS) -p gpgui-helper --features "tauri/custom-protocol" |  | ||||||
|  |  | ||||||
| clean: |  | ||||||
| 	$(CARGO) clean |  | ||||||
| 	rm -rf .build |  | ||||||
| 	rm -rf .vendor |  | ||||||
| 	rm -rf apps/gpgui-helper/node_modules |  | ||||||
|  |  | ||||||
| install: |  | ||||||
| 	@echo "Installing $(PKG_NAME)..." |  | ||||||
|  |  | ||||||
| 	install -Dm755 target/release/gpclient $(DESTDIR)/usr/bin/gpclient |  | ||||||
| 	install -Dm755 target/release/gpauth $(DESTDIR)/usr/bin/gpauth |  | ||||||
| 	install -Dm755 target/release/gpservice $(DESTDIR)/usr/bin/gpservice |  | ||||||
| 	install -Dm755 target/release/gpgui-helper $(DESTDIR)/usr/bin/gpgui-helper |  | ||||||
|  |  | ||||||
| 	if [ -f .build/gpgui/gpgui_*/gpgui ]; then \ |  | ||||||
| 		install -Dm755 .build/gpgui/gpgui_*/gpgui $(DESTDIR)/usr/bin/gpgui; \ |  | ||||||
| 	fi |  | ||||||
|  |  | ||||||
| 	install -Dm644 packaging/files/usr/share/applications/gpgui.desktop $(DESTDIR)/usr/share/applications/gpgui.desktop |  | ||||||
| 	install -Dm644 packaging/files/usr/share/icons/hicolor/scalable/apps/gpgui.svg $(DESTDIR)/usr/share/icons/hicolor/scalable/apps/gpgui.svg |  | ||||||
| 	install -Dm644 packaging/files/usr/share/icons/hicolor/32x32/apps/gpgui.png $(DESTDIR)/usr/share/icons/hicolor/32x32/apps/gpgui.png |  | ||||||
| 	install -Dm644 packaging/files/usr/share/icons/hicolor/128x128/apps/gpgui.png $(DESTDIR)/usr/share/icons/hicolor/128x128/apps/gpgui.png |  | ||||||
| 	install -Dm644 packaging/files/usr/share/icons/hicolor/256x256@2/apps/gpgui.png $(DESTDIR)/usr/share/icons/hicolor/256x256@2/apps/gpgui.png |  | ||||||
| 	install -Dm644 packaging/files/usr/share/polkit-1/actions/com.yuezk.gpgui.policy $(DESTDIR)/usr/share/polkit-1/actions/com.yuezk.gpgui.policy |  | ||||||
|  |  | ||||||
| uninstall: |  | ||||||
| 	@echo "Uninstalling $(PKG_NAME)..." |  | ||||||
|  |  | ||||||
| 	rm -f $(DESTDIR)/usr/bin/gpclient |  | ||||||
| 	rm -f $(DESTDIR)/usr/bin/gpauth |  | ||||||
| 	rm -f $(DESTDIR)/usr/bin/gpservice |  | ||||||
| 	rm -f $(DESTDIR)/usr/bin/gpgui-helper |  | ||||||
| 	rm -f $(DESTDIR)/usr/bin/gpgui |  | ||||||
|  |  | ||||||
| 	rm -f $(DESTDIR)/usr/share/applications/gpgui.desktop |  | ||||||
| 	rm -f $(DESTDIR)/usr/share/icons/hicolor/scalable/apps/gpgui.svg |  | ||||||
| 	rm -f $(DESTDIR)/usr/share/icons/hicolor/32x32/apps/gpgui.png |  | ||||||
| 	rm -f $(DESTDIR)/usr/share/icons/hicolor/128x128/apps/gpgui.png |  | ||||||
| 	rm -f $(DESTDIR)/usr/share/icons/hicolor/256x256@2/apps/gpgui.png |  | ||||||
| 	rm -f $(DESTDIR)/usr/share/polkit-1/actions/com.yuezk.gpgui.policy |  | ||||||
|  |  | ||||||
| clean-debian: |  | ||||||
| 	rm -rf .build/deb |  | ||||||
|  |  | ||||||
| # Generate the debian package structure, without the changelog |  | ||||||
| init-debian: clean-debian tarball |  | ||||||
| 	mkdir -p .build/deb |  | ||||||
| 	cp .build/tarball/${PKG}.tar.gz .build/deb |  | ||||||
|  |  | ||||||
| 	tar -xzf .build/deb/${PKG}.tar.gz -C .build/deb |  | ||||||
| 	cd .build/deb/${PKG} && debmake |  | ||||||
|  |  | ||||||
| 	cp -f packaging/deb/control.in .build/deb/$(PKG)/debian/control |  | ||||||
| 	cp -f packaging/deb/rules.in .build/deb/$(PKG)/debian/rules |  | ||||||
| 	cp -f packaging/deb/postrm .build/deb/$(PKG)/debian/postrm |  | ||||||
|  |  | ||||||
| 	sed -i "s/@OFFLINE@/$(OFFLINE)/g" .build/deb/$(PKG)/debian/rules |  | ||||||
|  |  | ||||||
| 	rm -f .build/deb/$(PKG)/debian/changelog |  | ||||||
|  |  | ||||||
| deb: init-debian |  | ||||||
| 	# Remove the rust build depdency from the control file |  | ||||||
| 	sed -i "s/@RUST@//g" .build/deb/$(PKG)/debian/control |  | ||||||
|  |  | ||||||
| 	cd .build/deb/$(PKG) && dch --create --distribution unstable --package $(PKG_NAME) --newversion $(VERSION)-$(REVISION) "Bugfix and improvements." |  | ||||||
|  |  | ||||||
| 	cd .build/deb/$(PKG) && debuild --preserve-env -e PATH -us -uc -b |  | ||||||
|  |  | ||||||
| check-ppa: |  | ||||||
| 	if [ $(OFFLINE) -eq 0 ]; then \ |  | ||||||
| 		echo "Error: ppa build requires offline mode (OFFLINE=1)"; \ |  | ||||||
| 	fi |  | ||||||
|  |  | ||||||
| # Usage: make ppa SERIES=focal OFFLINE=1 PUBLISH=1 |  | ||||||
| ppa: check-ppa init-debian |  | ||||||
| 	sed -i "s/@RUST@/rust-all(>=1.70)/g" .build/deb/$(PKG)/debian/control |  | ||||||
|  |  | ||||||
| 	$(eval SERIES_VER = $(shell distro-info --series $(SERIES) -r | cut -d' ' -f1)) |  | ||||||
| 	@echo "Building for $(SERIES) $(SERIES_VER)" |  | ||||||
|  |  | ||||||
| 	rm -rf .build/deb/$(PKG)/debian/changelog |  | ||||||
| 	cd .build/deb/$(PKG) && dch --create --distribution $(SERIES) --package $(PKG_NAME) --newversion $(VERSION)-$(REVISION)ppa$(PPA_REVISION)~ubuntu$(SERIES_VER) "Bugfix and improvements." |  | ||||||
|  |  | ||||||
| 	cd .build/deb/$(PKG) && echo "y" | debuild -e PATH -S -sa -k"$(GPG_KEY_ID)" -p"gpg --batch --passphrase $(GPG_KEY_PASS) --pinentry-mode loopback" |  | ||||||
|  |  | ||||||
| 	if [ $(PUBLISH) -eq 1 ]; then \ |  | ||||||
| 		cd .build/deb/$(PKG) && dput ppa:yuezk/globalprotect-openconnect ../*.changes; \ |  | ||||||
| 	else \ |  | ||||||
| 		echo "Skipping ppa publish (PUBLISH=0)"; \ |  | ||||||
| 	fi |  | ||||||
|  |  | ||||||
| clean-rpm: |  | ||||||
| 	rm -rf .build/rpm |  | ||||||
|  |  | ||||||
| # Generate RPM sepc file |  | ||||||
| init-rpm: clean-rpm |  | ||||||
| 	mkdir -p .build/rpm |  | ||||||
|  |  | ||||||
| 	cp packaging/rpm/globalprotect-openconnect.spec.in .build/rpm/globalprotect-openconnect.spec |  | ||||||
| 	cp packaging/rpm/globalprotect-openconnect.changes.in .build/rpm/globalprotect-openconnect.changes |  | ||||||
|  |  | ||||||
| 	sed -i "s/@VERSION@/$(VERSION)/g" .build/rpm/globalprotect-openconnect.spec |  | ||||||
| 	sed -i "s/@REVISION@/$(REVISION)/g" .build/rpm/globalprotect-openconnect.spec |  | ||||||
| 	sed -i "s/@OFFLINE@/$(OFFLINE)/g" .build/rpm/globalprotect-openconnect.spec |  | ||||||
| 	sed -i "s/@DATE@/$(shell LC_ALL=en.US date "+%a %b %d %Y")/g" .build/rpm/globalprotect-openconnect.spec |  | ||||||
|  |  | ||||||
| 	sed -i "s/@VERSION@/$(VERSION)/g" .build/rpm/globalprotect-openconnect.changes |  | ||||||
| 	sed -i "s/@DATE@/$(shell LC_ALL=en.US date -u "+%a %b %e %T %Z %Y")/g" .build/rpm/globalprotect-openconnect.changes |  | ||||||
|  |  | ||||||
| rpm: init-rpm tarball |  | ||||||
| 	rm -rf $(HOME)/rpmbuild |  | ||||||
| 	rpmdev-setuptree |  | ||||||
|  |  | ||||||
| 	cp .build/tarball/${PKG}.tar.gz $(HOME)/rpmbuild/SOURCES/${PKG_NAME}.tar.gz |  | ||||||
| 	rpmbuild -ba .build/rpm/globalprotect-openconnect.spec |  | ||||||
|  |  | ||||||
| 	# Copy RPM package from build directory |  | ||||||
| 	cp $(HOME)/rpmbuild/RPMS/$(shell uname -m)/$(PKG_NAME)*.rpm .build/rpm |  | ||||||
|  |  | ||||||
| 	# Copy the SRPM only for x86_64. |  | ||||||
| 	if [ "$(shell uname -m)" = "x86_64" ]; then \ |  | ||||||
| 		cp $(HOME)/rpmbuild/SRPMS/$(PKG_NAME)*.rpm .build/rpm; \ |  | ||||||
| 	fi |  | ||||||
|  |  | ||||||
| clean-pkgbuild: |  | ||||||
| 	rm -rf .build/pkgbuild |  | ||||||
|  |  | ||||||
| init-pkgbuild: clean-pkgbuild tarball |  | ||||||
| 	mkdir -p .build/pkgbuild |  | ||||||
|  |  | ||||||
| 	cp .build/tarball/${PKG}.tar.gz .build/pkgbuild |  | ||||||
| 	cp packaging/pkgbuild/PKGBUILD.in .build/pkgbuild/PKGBUILD |  | ||||||
|  |  | ||||||
| 	sed -i "s/@PKG_NAME@/$(PKG_NAME)/g" .build/pkgbuild/PKGBUILD |  | ||||||
| 	sed -i "s/@VERSION@/$(VERSION)/g" .build/pkgbuild/PKGBUILD |  | ||||||
| 	sed -i "s/@REVISION@/$(REVISION)/g" .build/pkgbuild/PKGBUILD |  | ||||||
| 	sed -i "s/@OFFLINE@/$(OFFLINE)/g" .build/pkgbuild/PKGBUILD |  | ||||||
|  |  | ||||||
| pkgbuild: init-pkgbuild |  | ||||||
| 	cd .build/pkgbuild && makepkg -s --noconfirm |  | ||||||
|  |  | ||||||
| clean-binary: |  | ||||||
| 	rm -rf .build/binary |  | ||||||
|  |  | ||||||
| binary: clean-binary tarball |  | ||||||
| 	mkdir -p .build/binary |  | ||||||
|  |  | ||||||
| 	cp .build/tarball/${PKG}.tar.gz .build/binary |  | ||||||
| 	tar -xzf .build/binary/${PKG}.tar.gz -C .build/binary |  | ||||||
|  |  | ||||||
| 	mkdir -p .build/binary/$(PKG_NAME)_$(VERSION)/artifacts |  | ||||||
|  |  | ||||||
| 	make -C .build/binary/${PKG} build OFFLINE=$(OFFLINE) BUILD_FE=0 INCLUDE_GUI=$(INCLUDE_GUI) |  | ||||||
| 	make -C .build/binary/${PKG} install DESTDIR=$(PWD)/.build/binary/$(PKG_NAME)_$(VERSION)/artifacts |  | ||||||
|  |  | ||||||
| 	cp packaging/binary/Makefile.in .build/binary/$(PKG_NAME)_$(VERSION)/Makefile |  | ||||||
|  |  | ||||||
| 	# Create a tarball for the binary package |  | ||||||
| 	tar -cJf .build/binary/$(PKG_NAME)_$(VERSION)_$(shell uname -m).bin.tar.xz -C .build/binary $(PKG_NAME)_$(VERSION) |  | ||||||
|  |  | ||||||
| 	# Generate sha256sum |  | ||||||
| 	cd .build/binary && sha256sum $(PKG_NAME)_$(VERSION)_$(shell uname -m).bin.tar.xz | cut -d' ' -f1 > $(PKG_NAME)_$(VERSION)_$(shell uname -m).bin.tar.xz.sha256 |  | ||||||
							
								
								
									
										287
									
								
								README.md
									
									
									
									
									
								
							
							
						
						| @@ -1,219 +1,194 @@ | |||||||
| # GlobalProtect-openconnect | # GlobalProtect-openconnect | ||||||
|  | A GlobalProtect VPN client (GUI) for Linux based on Openconnect and built with Qt5, supports SAML auth mode, inspired by [gp-saml-gui](https://github.com/dlenski/gp-saml-gui). | ||||||
| A GUI for GlobalProtect VPN, based on OpenConnect, supports the SSO authentication method. Inspired by [gp-saml-gui](https://github.com/dlenski/gp-saml-gui). |  | ||||||
|  |  | ||||||
| <p align="center"> | <p align="center"> | ||||||
|   <img width="300" src="https://github.com/yuezk/GlobalProtect-openconnect/assets/3297602/9242df9c-217d-42ab-8c21-8f9f69cd4eb5"> |   <img src="https://user-images.githubusercontent.com/3297602/133869036-5c02b0d9-c2d9-4f87-8c81-e44f68cfd6ac.png"> | ||||||
| </p> | </p> | ||||||
|  |  | ||||||
|  | <a href="https://paypal.me/zongkun" target="_blank"><img src="https://cdn.jsdelivr.net/gh/everdrone/coolbadge@5ea5937cabca5ecbfc45d6b30592bd81f219bc8d/badges/Paypal/Coffee/Blue/Small.png" alt="Buy me a coffee via Paypal" style="height: 32px; width: 268px;" ></a> | ||||||
|  | <a href="https://ko-fi.com/M4M75PYKZ" target="_blank"><img src="https://ko-fi.com/img/githubbutton_sm.svg" alt="Support me on Ko-fi" style="height: 32px; width: 238px;"></a> | ||||||
|  | <a href="https://www.buymeacoffee.com/yuezk" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" style="height: 32px; width: 114px;" ></a> | ||||||
|  |  | ||||||
|  |  | ||||||
| ## Features | ## Features | ||||||
|  |  | ||||||
| - [x] Better Linux support | - Similar user experience as the official client in macOS. | ||||||
| - [x] Support both CLI and GUI | - Supports both SAML and non-SAML authentication modes. | ||||||
| - [x] Support both SSO and non-SSO authentication | - Supports automatically selecting the preferred gateway from the multiple gateways. | ||||||
| - [x] Support the FIDO2 authentication (e.g., YubiKey) | - Supports switching gateway from the system tray menu manually. | ||||||
| - [x] Support authentication using default browser |  | ||||||
| - [x] Support client certificate authentication |  | ||||||
| - [x] Support multiple portals |  | ||||||
| - [x] Support gateway selection |  | ||||||
| - [x] Support connect gateway directly |  | ||||||
| - [x] Support auto-connect on startup |  | ||||||
| - [x] Support system tray icon |  | ||||||
|  |  | ||||||
| ## Usage |  | ||||||
|  |  | ||||||
| ### CLI | ## Install | ||||||
|  |  | ||||||
| The CLI version is always free and open source in this repo. It has almost the same features as the GUI version. | |OS|Stable version | Development version| | ||||||
|  | |---|--------------|--------------------| | ||||||
|  | |Linux Mint, Ubuntu 18.04 or later|[ppa:yuezk/globalprotect-openconnect](https://launchpad.net/~yuezk/+archive/ubuntu/globalprotect-openconnect)|[ppa:yuezk/globalprotect-openconnect-snapshot](https://launchpad.net/~yuezk/+archive/ubuntu/globalprotect-openconnect-snapshot)| | ||||||
|  | |Arch, Manjaro|[globalprotect-openconnect](https://archlinux.org/packages/community/x86_64/globalprotect-openconnect/)|[AUR: globalprotect-openconnect-git](https://aur.archlinux.org/packages/globalprotect-openconnect-git/)| | ||||||
|  | |Fedora|[copr: yuezk/globalprotect-openconnect](https://copr.fedorainfracloud.org/coprs/yuezk/globalprotect-openconnect/)|[copr: yuezk/globalprotect-openconnect](https://copr.fedorainfracloud.org/coprs/yuezk/globalprotect-openconnect/)| | ||||||
|  | |openSUSE, CentOS 8|[OBS: globalprotect-openconnect](https://build.opensuse.org/package/show/home:yuezk/globalprotect-openconnect)|[OBS: globalprotect-openconnect-snapshot](https://build.opensuse.org/package/show/home:yuezk/globalprotect-openconnect-snapshot)| | ||||||
|  |  | ||||||
| ``` | Add the repository in the above table and install it with your favorite package manager tool. | ||||||
| Usage: gpclient [OPTIONS] <COMMAND> |  | ||||||
|  |  | ||||||
| Commands: | [](https://repology.org/project/globalprotect-openconnect/versions) | ||||||
|   connect     Connect to a portal server | [](https://repology.org/project/globalprotect-openconnect/versions) | ||||||
|   disconnect  Disconnect from the server | [](https://repology.org/project/globalprotect-openconnect/versions) | ||||||
|   launch-gui  Launch the GUI | [](https://repology.org/project/globalprotect-openconnect/versions) | ||||||
|   help        Print this message or the help of the given subcommand(s) | [](https://repology.org/project/globalprotect-openconnect/versions) | ||||||
|  | [](https://repology.org/project/globalprotect-openconnect/versions) | ||||||
|  | [](https://repology.org/project/globalprotect-openconnect/versions) | ||||||
|  |  | ||||||
| Options: | ### Linux Mint, Ubuntu 18.04 or later | ||||||
|       --fix-openssl        Get around the OpenSSL `unsafe legacy renegotiation` error |  | ||||||
|       --ignore-tls-errors  Ignore the TLS errors |  | ||||||
|   -h, --help               Print help |  | ||||||
|   -V, --version            Print version |  | ||||||
|  |  | ||||||
| See 'gpclient help <command>' for more information on a specific command. | ```sh | ||||||
| ``` |  | ||||||
|  |  | ||||||
| To use the default browser for authentication with the CLI version, you need to use the following command: |  | ||||||
|  |  | ||||||
| ```bash |  | ||||||
| sudo -E gpclient connect --default-browser <portal> |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| ### GUI |  | ||||||
|  |  | ||||||
| The GUI version is also available after you installed it. You can launch it from the application menu or run `gpclient launch-gui` in the terminal. |  | ||||||
|  |  | ||||||
| > [!Note] |  | ||||||
| > |  | ||||||
| > The GUI version is partially open source. Its background service is open sourced in this repo as [gpservice](./apps/gpservice/). The GUI part is a wrapper of the background service, which is not open sourced. |  | ||||||
|  |  | ||||||
| ## Installation |  | ||||||
|  |  | ||||||
| ### Debian/Ubuntu based distributions |  | ||||||
|  |  | ||||||
| #### Install from PPA (Ubuntu 18.04 and later, except 24.04) |  | ||||||
|  |  | ||||||
| ``` |  | ||||||
| sudo apt-get install gir1.2-gtk-3.0 gir1.2-webkit2-4.0 |  | ||||||
| sudo add-apt-repository ppa:yuezk/globalprotect-openconnect | sudo add-apt-repository ppa:yuezk/globalprotect-openconnect | ||||||
| sudo apt-get update | sudo apt-get update | ||||||
| sudo apt-get install globalprotect-openconnect | sudo apt-get install globalprotect-openconnect | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| > [!Note] |  | ||||||
| > |  | ||||||
| > For Linux Mint, you might need to import the GPG key with: `sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 7937C393082992E5D6E4A60453FC26B43838D761` if you encountered an error `gpg: keyserver receive failed: General error`. | > For Linux Mint, you might need to import the GPG key with: `sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 7937C393082992E5D6E4A60453FC26B43838D761` if you encountered an error `gpg: keyserver receive failed: General error`. | ||||||
|  |  | ||||||
| #### **Ubuntu 24.04 and later** |  | ||||||
|  |  | ||||||
| The `libwebkit2gtk-4.0-37` package was [removed](https://bugs.launchpad.net/ubuntu/+source/webkit2gtk/+bug/2061914) from its repo, before [the issue](https://github.com/yuezk/GlobalProtect-openconnect/issues/351) gets resolved, you need to install them manually: |  | ||||||
|  |  | ||||||
| ```bash |  | ||||||
| wget http://launchpadlibrarian.net/704701349/libwebkit2gtk-4.0-37_2.43.3-1_amd64.deb |  | ||||||
| wget http://launchpadlibrarian.net/704701345/libjavascriptcoregtk-4.0-18_2.43.3-1_amd64.deb |  | ||||||
|  |  | ||||||
| sudo dpkg --install *.deb |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| And the latest package is not available in the PPA, you can follow the [Install from deb package](#install-from-deb-package) section to install the latest package. |  | ||||||
|  |  | ||||||
| #### **Ubuntu 18.04** |  | ||||||
|  |  | ||||||
| The latest package is not available in the PPA either, but you still needs to add the `ppa:yuezk/globalprotect-openconnect` repo beforehand to use the required `openconnect` package. Then you can follow the [Install from deb package](#install-from-deb-package) section to install the latest package. |  | ||||||
|  |  | ||||||
| #### Install from deb package |  | ||||||
|  |  | ||||||
| Download the latest deb package from [releases](https://github.com/yuezk/GlobalProtect-openconnect/releases) page. Then install it with `apt`: |  | ||||||
|  |  | ||||||
| ```bash |  | ||||||
| sudo apt install --fix-broken globalprotect-openconnect_*.deb |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| ### Arch Linux / Manjaro | ### Arch Linux / Manjaro | ||||||
|  |  | ||||||
| #### Install from AUR | ```sh | ||||||
|  | sudo pacman -S globalprotect-openconnect | ||||||
| Install from AUR: [globalprotect-openconnect-git](https://aur.archlinux.org/packages/globalprotect-openconnect-git/) |  | ||||||
|  |  | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
|  | ### AUR snapshot version | ||||||
|  |  | ||||||
|  | ```sh | ||||||
| yay -S globalprotect-openconnect-git | yay -S globalprotect-openconnect-git | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| #### Install from package | ### Fedora | ||||||
|  |  | ||||||
| Download the latest package from [releases](https://github.com/yuezk/GlobalProtect-openconnect/releases) page. Then install it with `pacman`: | ```sh | ||||||
|  |  | ||||||
| ```bash |  | ||||||
| sudo pacman -U globalprotect-openconnect-*.pkg.tar.zst |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| ### Fedora 38 and later / Fedora Rawhide |  | ||||||
|  |  | ||||||
| #### Install from COPR |  | ||||||
|  |  | ||||||
| The package is available on [COPR](https://copr.fedorainfracloud.org/coprs/yuezk/globalprotect-openconnect/) for various RPM-based distributions. You can install it with the following commands: |  | ||||||
|  |  | ||||||
| ``` |  | ||||||
| sudo dnf copr enable yuezk/globalprotect-openconnect | sudo dnf copr enable yuezk/globalprotect-openconnect | ||||||
| sudo dnf install globalprotect-openconnect | sudo dnf install globalprotect-openconnect | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| ### openSUSE Leap 15.6 / openSUSE Tumbleweed | ### openSUSE | ||||||
|  |  | ||||||
| #### Install from OBS (openSUSE Build Service) | - openSUSE Tumbleweed | ||||||
|  |   ```sh | ||||||
|  |   sudo zypper ar https://download.opensuse.org/repositories/home:/yuezk/openSUSE_Tumbleweed/home:yuezk.repo | ||||||
|  |   sudo zypper ref | ||||||
|  |   sudo zypper install globalprotect-openconnect | ||||||
|  |   ``` | ||||||
|  |  | ||||||
| The package is also available on [OBS](https://build.opensuse.org/package/show/home:yuezk/globalprotect-openconnect) for various RPM-based distributions. You can follow the instructions [on this page](https://software.opensuse.org//download.html?project=home%3Ayuezk&package=globalprotect-openconnect) to install it. | - openSUSE Leap | ||||||
|  |  | ||||||
| ### Other RPM-based distributions |   ```sh   | ||||||
|  |   sudo zypper ar https://download.opensuse.org/repositories/home:/yuezk/15.4/home:yuezk.repo | ||||||
|    |    | ||||||
| #### Install from RPM package |   sudo zypper ref | ||||||
|  |   sudo zypper install globalprotect-openconnect | ||||||
|  |   ``` | ||||||
|  | ### CentOS 8 | ||||||
|  |  | ||||||
| Download the latest RPM package from [releases](https://github.com/yuezk/GlobalProtect-openconnect/releases) page. | 1. Add the repository: `https://download.opensuse.org/repositories/home:/yuezk/CentOS_8/home:yuezk.repo` | ||||||
|  | 1. Install `globalprotect-openconnect` | ||||||
|  |  | ||||||
| ```bash |  | ||||||
| sudo rpm -i globalprotect-openconnect-*.rpm |  | ||||||
| ``` |  | ||||||
| ### Gentoo |  | ||||||
|  |  | ||||||
| Install from the ```rios``` or ```slonko``` overlays.  Example using rios: | ## Build & Install from source code | ||||||
|  |  | ||||||
| #### 1. Enable the overlay | Clone this repo with: | ||||||
| ``` |  | ||||||
| sudo eselect repository enable rios | ```sh | ||||||
|  | git clone https://github.com/yuezk/GlobalProtect-openconnect.git | ||||||
|  | cd GlobalProtect-openconnect | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| #### 2. Sync with the repository | ### MX Linux | ||||||
|  | The following instructions are for **MX-21.2.1_x64 KDE**. | ||||||
|  |  | ||||||
|   - If you have eix installed, use it: | ```sh | ||||||
| ``` | sudo apt install qttools5-dev libsecret-1-dev libqt5keychain1 | ||||||
| sudo eix-sync | ./scripts/install-debian.sh | ||||||
| ``` |  | ||||||
|   - Otherwise, use: |  | ||||||
| ``` |  | ||||||
| sudo emerge --sync |  | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| #### 3. Install | ### Ubuntu/Mint | ||||||
|  |  | ||||||
| ```sudo emerge globalprotect-openconnect``` | > **⚠️ REQUIRED for Ubuntu 18.04 ⚠️** | ||||||
|  | > | ||||||
|  | > Add this [dwmw2/openconnect](https://launchpad.net/~dwmw2/+archive/ubuntu/openconnect) PPA first to install the latest openconnect. | ||||||
|  | > | ||||||
|  | > ```sh | ||||||
|  | > sudo add-apt-repository ppa:dwmw2/openconnect | ||||||
|  | > sudo apt-get update | ||||||
|  | > ``` | ||||||
|  |  | ||||||
|  | Build and install with: | ||||||
|  |  | ||||||
|  | ```sh | ||||||
|  | ./scripts/install-ubuntu.sh | ||||||
|  | ``` | ||||||
|  | ### openSUSE | ||||||
|  |  | ||||||
|  | Build and install with: | ||||||
|  |  | ||||||
|  | ```sh | ||||||
|  | ./scripts/install-opensuse.sh | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ### Fedora | ||||||
|  |  | ||||||
|  | Build and install with: | ||||||
|  |  | ||||||
|  | ```sh | ||||||
|  | ./scripts/install-fedora.sh | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ### Other Linux | ||||||
|  |  | ||||||
|  | Install the Qt5 dependencies and OpenConnect: | ||||||
|  |  | ||||||
|  | - QtCore | ||||||
|  | - QtWebEngine | ||||||
|  | - QtWebSockets | ||||||
|  | - QtDBus | ||||||
|  | - openconnect v8.x | ||||||
|  | - qtkeychain | ||||||
|  |  | ||||||
|  | ...then build and install with: | ||||||
|  |  | ||||||
|  | ```sh | ||||||
|  | ./scripts/install.sh | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  |  | ||||||
| ### Other distributions | ### NixOS | ||||||
|  |   In `configuration.nix`: | ||||||
|  |  | ||||||
| - Install `openconnect >= 8.20`, `webkit2gtk`, `libsecret`, `libayatana-appindicator` or `libappindicator-gtk3`. |   ``` | ||||||
| - Download `globalprotect-openconnect_${version}_${arch}.bin.tar.xz` from [releases](https://github.com/yuezk/GlobalProtect-openconnect/releases) page. |   services.globalprotect = { | ||||||
| - Extract the tarball with `tar -xJf globalprotect-openconnect_${version}_${arch}.bin.tar.xz`. |     enable = true; | ||||||
| - Run `sudo make install` to install the client. |     # if you need a Host Integrity Protection report | ||||||
|  |     csdWrapper = "${pkgs.openconnect}/libexec/openconnect/hipreport.sh"; | ||||||
|  |   }; | ||||||
|  |  | ||||||
| ## Build from source |   environment.systemPackages = [ globalprotect-openconnect ]; | ||||||
|  |   ``` | ||||||
|  |  | ||||||
| You can also build the client from source, steps are as follows: | ## Run | ||||||
|  |  | ||||||
| ### Prerequisites | Once the software is installed, you can run `gpclient` to start the UI. | ||||||
|  |  | ||||||
| - [Install Rust](https://www.rust-lang.org/tools/install) | ## Passing the Custom Parameters to `OpenConnect` CLI | ||||||
| - Install Tauri dependencies: https://tauri.app/v1/guides/getting-started/prerequisites/#setting-up-linux |  | ||||||
| - Install `perl` |  | ||||||
| - Install `openconnect >= 8.20` and `libopenconnect-dev` (or `openconnect-devel` on RPM-based distributions) |  | ||||||
| - Install `pkexec`, `gnome-keyring` (or `pam_kwallet` on KDE) |  | ||||||
|  |  | ||||||
| ### Build | See [Configuration](https://github.com/yuezk/GlobalProtect-openconnect/wiki/Configuration) | ||||||
|  |  | ||||||
| 1. Download the source code tarball from [releases](https://github.com/yuezk/GlobalProtect-openconnect/releases) page. Choose `globalprotect-openconnect-${version}.tar.gz`. | ## Display the system tray icon on Gnome 40 | ||||||
| 2. Extract the tarball with `tar -xzf globalprotect-openconnect-${version}.tar.gz`. |  | ||||||
| 3. Enter the source directory and run `make build BUILD_FE=0` to build the client. |  | ||||||
| 3. Run `sudo make install` to install the client. (Note, `DESTDIR` is not supported) |  | ||||||
|  |  | ||||||
| ## FAQ | Install the [AppIndicator and KStatusNotifierItem Support](https://extensions.gnome.org/extension/615/appindicator-support/) extension and you will see the system try icon (Restart the system after the installation). | ||||||
|  |  | ||||||
| 1. How to deal with error `Secure Storage not ready` | <p align="center"> | ||||||
|  |   <img src="https://user-images.githubusercontent.com/3297602/130831022-b93492fd-46dd-4a8e-94a4-13b5747120b7.png" /> | ||||||
|  | <p> | ||||||
|  |  | ||||||
|    Try upgrade the client to `2.2.0` or later, which will use a file-based storage as a fallback. |  | ||||||
|  |  | ||||||
|    You need to install the `gnome-keyring` package, and restart the system (See [#321](https://github.com/yuezk/GlobalProtect-openconnect/issues/321), [#316](https://github.com/yuezk/GlobalProtect-openconnect/issues/316)). | ## Troubleshooting | ||||||
|  |  | ||||||
| 2. How to deal with error `(gpauth:18869): Gtk-WARNING **: 10:33:37.566: cannot open display:` | Run `gpclient` in the Terminal and collect the logs. | ||||||
|  |  | ||||||
|    If you encounter this error when using the CLI version, try to run the command with `sudo -E` (See [#316](https://github.com/yuezk/GlobalProtect-openconnect/issues/316)). |  | ||||||
|  |  | ||||||
| ## About Trial |  | ||||||
|  |  | ||||||
| The CLI version is always free, while the GUI version is paid. There are two trial modes for the GUI version: |  | ||||||
|  |  | ||||||
| 1. 10-day trial: You can use the GUI stable release for 10 days after the installation. |  | ||||||
| 2. 14-day trial: Each beta release has a fresh trial period (at most 14 days) after released. |  | ||||||
|  |  | ||||||
| ## [License](./LICENSE) | ## [License](./LICENSE) | ||||||
|  |  | ||||||
| GPLv3 | GPLv3 | ||||||
|   | |||||||
| @@ -1,28 +0,0 @@ | |||||||
| [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", |  | ||||||
|   "clap", |  | ||||||
|   "browser-auth", |  | ||||||
| ] } |  | ||||||
| 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 |  | ||||||
| html-escape = "0.2.13" |  | ||||||
| webkit2gtk = "0.18.2" |  | ||||||
| tauri = { workspace = true, features = ["http-all"] } |  | ||||||
| compile-time.workspace = true |  | ||||||
| @@ -1,3 +0,0 @@ | |||||||
| fn main() { |  | ||||||
|   tauri_build::build() |  | ||||||
| } |  | ||||||
| Before Width: | Height: | Size: 13 KiB | 
| Before Width: | Height: | Size: 28 KiB | 
| Before Width: | Height: | Size: 2.5 KiB |