Compare commits
	
		
			39 Commits
		
	
	
		
			snapshot
			...
			3674a28dee
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 3674a28dee | ||
|  | 3098d1170f | ||
|  | acf7ef28ab | ||
|  | 11a374765c | ||
|  | f6ceb5ac0a | ||
|  | 94a2cd2886 | ||
|  | 867ea71144 | ||
|  | c9484248a5 | ||
|  | a10539e9c3 | ||
|  | 601f422863 | ||
|  | bf96a88e21 | ||
|  | 963b7d5407 | ||
|  | f91f0bcd17 | ||
|  | 5603157679 | ||
|  | 4191e9a19f | ||
|  | 5bc3da99ac | ||
|  | e8db014336 | ||
|  | e5fd2ba280 | ||
|  | 15e798c1e7 | ||
|  | 1af21432d4 | ||
|  | c07e232ec2 | ||
|  | d975f981cc | ||
|  | c74ce52c2d | ||
|  | da3dc10569 | ||
|  | 75db9e162f | ||
|  | 89eb42ceac | ||
|  | 54d3bb8a92 | ||
|  | a1b49fde47 | ||
|  | f42f0d248e | ||
|  | ec0bff1e36 | ||
|  | 77fda0f527 | ||
|  | 8de183a53d | ||
|  | 462428f99a | ||
|  | cbd8b0f144 | ||
|  | 5a84c99cd9 | ||
|  | d5af0e58c2 | ||
|  | 16696e3840 | ||
|  | 6df9877895 | ||
|  | 19b9b757f4 | 
| @@ -1,13 +1,21 @@ | |||||||
| # top-most EditorConfig file |  | ||||||
| root = true | root = true | ||||||
|  |  | ||||||
| # Unix-style newlines with a newline ending every file |  | ||||||
| [*] | [*] | ||||||
| end_of_line = lf | charset = utf-8 | ||||||
| insert_final_newline = false |  | ||||||
| trim_trailing_whitespace=true |  | ||||||
| indent_style = space | indent_style = space | ||||||
|  | indent_size = 2 | ||||||
|  | end_of_line = lf | ||||||
|  | insert_final_newline = true | ||||||
|  | trim_trailing_whitespace = true | ||||||
|  |  | ||||||
|  | [*.{rs,toml}] | ||||||
| indent_size = 4 | indent_size = 4 | ||||||
|  |  | ||||||
| [*.sh] | [*.{c,h}] | ||||||
| indent_style = tab | indent_size = 4 | ||||||
|  |  | ||||||
|  | [*.{js,jsx,ts,tsx}] | ||||||
|  | indent_size = 2 | ||||||
|  |  | ||||||
|  | [*.md] | ||||||
|  | trim_trailing_whitespace = false | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								.github/FUNDING.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,2 +0,0 @@ | |||||||
| ko_fi: yuezk |  | ||||||
| custom: ["https://buymeacoffee.com/yuezk", "https://paypal.me/zongkun"] |  | ||||||
							
								
								
									
										275
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,275 +0,0 @@ | |||||||
| name: Build |  | ||||||
|  |  | ||||||
| on: |  | ||||||
|   push: |  | ||||||
|     branches: |  | ||||||
|       - master |  | ||||||
|       - develop |  | ||||||
|     tags: |  | ||||||
|       - "v*.*.*" |  | ||||||
|     paths-ignore: |  | ||||||
|       - LICENSE |  | ||||||
|       - "*.md" |  | ||||||
|       - .vscode |  | ||||||
|   workflow_dispatch: |  | ||||||
|  |  | ||||||
| # A workflow run is made up of one or more jobs that can run sequentially or in parallel |  | ||||||
| jobs: |  | ||||||
|   build: |  | ||||||
|     strategy: |  | ||||||
|       matrix: |  | ||||||
|         os: [ubuntu-18.04, ubuntu-20.04, ubuntu-22.04] |  | ||||||
|  |  | ||||||
|     runs-on: ${{ matrix.os }} |  | ||||||
|  |  | ||||||
|     steps: |  | ||||||
|       # Checkout repository and submodules |  | ||||||
|       - uses: actions/checkout@v2 |  | ||||||
|         with: |  | ||||||
|           submodules: recursive |  | ||||||
|  |  | ||||||
|       - name: Build |  | ||||||
|         run: | |  | ||||||
|           ./scripts/install-ubuntu.sh |  | ||||||
|           # assert no library missing |  | ||||||
|           test $(ldd $(which gpclient) | grep 'not found' | wc -l) -eq 0 |  | ||||||
|  |  | ||||||
|   snapshot-archive-all: |  | ||||||
|     if: ${{ github.event_name != 'pull_request' && github.ref == 'refs/heads/develop' }} |  | ||||||
|     needs: build |  | ||||||
|     runs-on: ubuntu-latest |  | ||||||
|  |  | ||||||
|     steps: |  | ||||||
|       - uses: actions/checkout@v2 |  | ||||||
|         with: |  | ||||||
|           submodules: recursive |  | ||||||
|           fetch-depth: 0 |  | ||||||
|  |  | ||||||
|       - name: Install dependencies |  | ||||||
|         run: | |  | ||||||
|           python -m pip install --upgrade pip |  | ||||||
|           pip install git-archive-all |  | ||||||
|  |  | ||||||
|       - name: Archive all |  | ||||||
|         run: | |  | ||||||
|           ./scripts/snapshot-archive-all.sh |  | ||||||
|  |  | ||||||
|       - name: Verify debian package |  | ||||||
|         run: | |  | ||||||
|           ./scripts/verify-debian-package.sh |  | ||||||
|  |  | ||||||
|       - uses: actions/upload-artifact@v2 |  | ||||||
|         with: |  | ||||||
|           name: snapshot-source-code |  | ||||||
|           path: ./artifacts/* |  | ||||||
|  |  | ||||||
|   snapshot-ppa: |  | ||||||
|     if: ${{ github.event_name != 'pull_request' && github.ref == 'refs/heads/develop' }} |  | ||||||
|     needs: snapshot-archive-all |  | ||||||
|     runs-on: ubuntu-latest |  | ||||||
|  |  | ||||||
|     steps: |  | ||||||
|       - uses: actions/download-artifact@v2 |  | ||||||
|         with: |  | ||||||
|           name: snapshot-source-code |  | ||||||
|           path: artifacts |  | ||||||
|  |  | ||||||
|       - name: Extract source code |  | ||||||
|         run: | |  | ||||||
|           cd $GITHUB_WORKSPACE/artifacts |  | ||||||
|           mkdir deb-build && cp *.tar.gz deb-build && cd deb-build |  | ||||||
|           tar xf *.tar.gz |  | ||||||
|  |  | ||||||
|       - name: Publish PPA |  | ||||||
|         uses: yuezk/publish-ppa-package@develop |  | ||||||
|         with: |  | ||||||
|           repository: 'ppa:yuezk/globalprotect-openconnect-snapshot' |  | ||||||
|           gpg_private_key: ${{ secrets.PPA_GPG_PRIVATE_KEY }} |  | ||||||
|           gpg_passphrase: ${{ secrets.PPA_GPG_PASSPHRASE }} |  | ||||||
|           pkgdir: '${{ github.workspace }}/artifacts/deb-build/globalprotect-openconnect*/' |  | ||||||
|  |  | ||||||
|   snapshot-aur: |  | ||||||
|     if: ${{ github.event_name != 'pull_request' && github.ref == 'refs/heads/develop' }} |  | ||||||
|     needs: snapshot-archive-all |  | ||||||
|     runs-on: ubuntu-latest |  | ||||||
|  |  | ||||||
|     steps: |  | ||||||
|       - uses: actions/download-artifact@v2 |  | ||||||
|         with: |  | ||||||
|           name: snapshot-source-code |  | ||||||
|           path: artifacts |  | ||||||
|  |  | ||||||
|       - name: Publish AUR package |  | ||||||
|         uses: yuezk/github-actions-deploy-aur@update-pkgver |  | ||||||
|         with: |  | ||||||
|           pkgname: globalprotect-openconnect-git |  | ||||||
|           pkgbuild: ./artifacts/aur/PKGBUILD |  | ||||||
|           assets: ./artifacts/aur/gp.install |  | ||||||
|           update_pkgver: true |  | ||||||
|           commit_username: ${{ secrets.AUR_USERNAME }} |  | ||||||
|           commit_email: ${{ secrets.AUR_EMAIL }} |  | ||||||
|           ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }} |  | ||||||
|           commit_message: 'Snapshot release: git#${{ github.sha }}' |  | ||||||
|  |  | ||||||
|   snapshot-obs: |  | ||||||
|     if: ${{ github.event_name != 'pull_request' && github.ref == 'refs/heads/develop' }} |  | ||||||
|     needs: snapshot-archive-all |  | ||||||
|     runs-on: ubuntu-latest |  | ||||||
|  |  | ||||||
|     steps: |  | ||||||
|       - uses: actions/download-artifact@v2 |  | ||||||
|         with: |  | ||||||
|           name: snapshot-source-code |  | ||||||
|           path: artifacts |  | ||||||
|  |  | ||||||
|       - uses: yuezk/publish-obs-package@main |  | ||||||
|         with: |  | ||||||
|           project: home:yuezk |  | ||||||
|           package: globalprotect-openconnect-snapshot |  | ||||||
|           username: yuezk |  | ||||||
|           password: ${{ secrets.OBS_PASSWORD }} |  | ||||||
|           files: ./artifacts/obs/* |  | ||||||
|  |  | ||||||
|   snapshot-snap: |  | ||||||
|     # if: ${{ github.event_name != 'pull_request' && github.ref == 'refs/heads/develop' }} |  | ||||||
|     if: ${{ false }} |  | ||||||
|     needs: snapshot-archive-all |  | ||||||
|     runs-on: ubuntu-latest |  | ||||||
|  |  | ||||||
|     steps: |  | ||||||
|       - uses: actions/download-artifact@v2 |  | ||||||
|         with: |  | ||||||
|           name: snapshot-source-code |  | ||||||
|           path: artifacts |  | ||||||
|  |  | ||||||
|       - name: Extract source code |  | ||||||
|         run: | |  | ||||||
|           mkdir snap-source |  | ||||||
|           tar xvf ./artifacts/globalprotect-openconnect-*tar.gz \ |  | ||||||
|             --directory snap-source \ |  | ||||||
|             --strip 1 |  | ||||||
|  |  | ||||||
|       - uses: snapcore/action-build@v1 |  | ||||||
|         id: build |  | ||||||
|         with: |  | ||||||
|           path: ./snap-source |  | ||||||
|  |  | ||||||
|       - uses: snapcore/action-publish@v1 |  | ||||||
|         with: |  | ||||||
|           store_login: ${{ secrets.SNAPSTORE_LOGIN }} |  | ||||||
|           snap: ${{ steps.build.outputs.snap }} |  | ||||||
|           release: edge |  | ||||||
|  |  | ||||||
|   release-archive-all: |  | ||||||
|     if: startsWith(github.ref, 'refs/tags/v') |  | ||||||
|     needs: build |  | ||||||
|     runs-on: ubuntu-latest |  | ||||||
|  |  | ||||||
|     steps: |  | ||||||
|       - uses: actions/checkout@v2 |  | ||||||
|         with: |  | ||||||
|           submodules: recursive |  | ||||||
|           fetch-depth: 0 |  | ||||||
|  |  | ||||||
|       - name: Install dependencies |  | ||||||
|         run: | |  | ||||||
|           python -m pip install --upgrade pip |  | ||||||
|           pip install git-archive-all |  | ||||||
|  |  | ||||||
|       - name: Archive all |  | ||||||
|         run: | |  | ||||||
|           ./scripts/release-archive-all.sh |  | ||||||
|  |  | ||||||
|       - name: Verify debian package |  | ||||||
|         run: | |  | ||||||
|           ./scripts/verify-debian-package.sh |  | ||||||
|  |  | ||||||
|       - uses: actions/upload-artifact@v2 |  | ||||||
|         with: |  | ||||||
|           name: release-source-code |  | ||||||
|           path: ./artifacts/* |  | ||||||
|  |  | ||||||
|   release-ppa: |  | ||||||
|     if: startsWith(github.ref, 'refs/tags/v') |  | ||||||
|     needs: release-archive-all |  | ||||||
|     runs-on: ubuntu-latest |  | ||||||
|  |  | ||||||
|     steps: |  | ||||||
|       - uses: actions/download-artifact@v2 |  | ||||||
|         with: |  | ||||||
|           name: release-source-code |  | ||||||
|           path: artifacts |  | ||||||
|  |  | ||||||
|       - name: Extract source code |  | ||||||
|         run: | |  | ||||||
|           cd $GITHUB_WORKSPACE/artifacts |  | ||||||
|           mkdir deb-build && cp *.tar.gz deb-build && cd deb-build |  | ||||||
|           tar xf *.tar.gz |  | ||||||
|  |  | ||||||
|       - name: Publish PPA |  | ||||||
|         uses: yuezk/publish-ppa-package@develop |  | ||||||
|         with: |  | ||||||
|           repository: 'ppa:yuezk/globalprotect-openconnect' |  | ||||||
|           gpg_private_key: ${{ secrets.PPA_GPG_PRIVATE_KEY }} |  | ||||||
|           gpg_passphrase: ${{ secrets.PPA_GPG_PASSPHRASE }} |  | ||||||
|           pkgdir: '${{ github.workspace }}/artifacts/deb-build/globalprotect-openconnect*/' |  | ||||||
|  |  | ||||||
|   release-aur: |  | ||||||
|     if: startsWith(github.ref, 'refs/tags/v') |  | ||||||
|     needs: release-archive-all |  | ||||||
|     runs-on: ubuntu-latest |  | ||||||
|  |  | ||||||
|     steps: |  | ||||||
|       - uses: actions/download-artifact@v2 |  | ||||||
|         with: |  | ||||||
|           name: release-source-code |  | ||||||
|           path: artifacts |  | ||||||
|  |  | ||||||
|       - name: Publish AUR package |  | ||||||
|         uses: yuezk/github-actions-deploy-aur@update-pkgver |  | ||||||
|         with: |  | ||||||
|           pkgname: globalprotect-openconnect-git |  | ||||||
|           pkgbuild: ./artifacts/aur/PKGBUILD |  | ||||||
|           assets: ./artifacts/aur/gp.install |  | ||||||
|           update_pkgver: true |  | ||||||
|           commit_username: ${{ secrets.AUR_USERNAME }} |  | ||||||
|           commit_email: ${{ secrets.AUR_EMAIL }} |  | ||||||
|           ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }} |  | ||||||
|           commit_message: 'Release ${{ github.ref }}' |  | ||||||
|  |  | ||||||
|   release-obs: |  | ||||||
|     if: startsWith(github.ref, 'refs/tags/v') |  | ||||||
|     needs: release-archive-all |  | ||||||
|     runs-on: ubuntu-latest |  | ||||||
|  |  | ||||||
|     steps: |  | ||||||
|       - uses: actions/download-artifact@v2 |  | ||||||
|         with: |  | ||||||
|           name: release-source-code |  | ||||||
|           path: artifacts |  | ||||||
|  |  | ||||||
|       - uses: yuezk/publish-obs-package@main |  | ||||||
|         with: |  | ||||||
|           project: home:yuezk |  | ||||||
|           package: globalprotect-openconnect |  | ||||||
|           username: yuezk |  | ||||||
|           password: ${{ secrets.OBS_PASSWORD }} |  | ||||||
|           files: ./artifacts/obs/* |  | ||||||
|  |  | ||||||
|   release-github: |  | ||||||
|     if: startsWith(github.ref, 'refs/tags/v') |  | ||||||
|     needs: |  | ||||||
|       - release-ppa |  | ||||||
|       - release-aur |  | ||||||
|       - release-obs |  | ||||||
|     runs-on: ubuntu-latest |  | ||||||
|  |  | ||||||
|     steps: |  | ||||||
|       - uses: actions/download-artifact@v2 |  | ||||||
|         with: |  | ||||||
|           name: release-source-code |  | ||||||
|           path: artifacts |  | ||||||
|       - uses: softprops/action-gh-release@v1 |  | ||||||
|         with: |  | ||||||
|           files: | |  | ||||||
|             ./artifacts/*.tar.gz |  | ||||||
							
								
								
									
										33
									
								
								.github/workflows/pr.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,33 +0,0 @@ | |||||||
| name: PR Build |  | ||||||
|  |  | ||||||
| on: |  | ||||||
|   pull_request: |  | ||||||
|     branches: |  | ||||||
|       - master |  | ||||||
|       - develop |  | ||||||
|     paths-ignore: |  | ||||||
|       - LICENSE |  | ||||||
|       - "*.md" |  | ||||||
|       - .vscode |  | ||||||
|   workflow_dispatch: |  | ||||||
|  |  | ||||||
| # A workflow run is made up of one or more jobs that can run sequentially or in parallel |  | ||||||
| jobs: |  | ||||||
|   build: |  | ||||||
|     strategy: |  | ||||||
|       matrix: |  | ||||||
|         os: [ubuntu-18.04, ubuntu-20.04] |  | ||||||
|  |  | ||||||
|     runs-on: ${{ matrix.os }} |  | ||||||
|  |  | ||||||
|     steps: |  | ||||||
|       # Checkout repository and submodules |  | ||||||
|       - uses: actions/checkout@v2 |  | ||||||
|         with: |  | ||||||
|           submodules: recursive |  | ||||||
|  |  | ||||||
|       - name: Build |  | ||||||
|         run: | |  | ||||||
|           ./scripts/install-ubuntu.sh |  | ||||||
|           # assert no library missing |  | ||||||
|           test $(ldd $(which gpclient) | grep 'not found' | wc -l) -eq 0 |  | ||||||
							
								
								
									
										89
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,70 +1,35 @@ | |||||||
| # Binaries | # Created by https://www.toptal.com/developers/gitignore/api/rust,visualstudiocode | ||||||
| *.rpm | # Edit at https://www.toptal.com/developers/gitignore?templates=rust,visualstudiocode | ||||||
| *.gz |  | ||||||
| *.snap |  | ||||||
| .DS_Store |  | ||||||
| build-debian |  | ||||||
| build |  | ||||||
| artifacts |  | ||||||
|  |  | ||||||
| .cmake | ### Rust ### | ||||||
| .idea | # Generated by Cargo | ||||||
|  | # will have compiled files and executables | ||||||
|  | debug/ | ||||||
|  | target/ | ||||||
|  |  | ||||||
| # Auto generated DBus files | # These are backup files generated by rustfmt | ||||||
| *_adaptor.cpp | **/*.rs.bk | ||||||
| *_adaptor.h |  | ||||||
|  |  | ||||||
| gpservice_interface.* | # MSVC Windows builds of rustc generate these, which store debugging information | ||||||
|  | *.pdb | ||||||
|  |  | ||||||
| # C++ objects and libs | ### VisualStudioCode ### | ||||||
| *.slo | .vscode/* | ||||||
| *.lo | !.vscode/settings.json | ||||||
| *.o | !.vscode/tasks.json | ||||||
| *.a | !.vscode/launch.json | ||||||
| *.la | !.vscode/extensions.json | ||||||
| *.lai | !.vscode/*.code-snippets | ||||||
| *.so |  | ||||||
| *.so.* |  | ||||||
| *.dll |  | ||||||
| *.dylib |  | ||||||
|  |  | ||||||
| # Qt-es | # Local History for Visual Studio Code | ||||||
| object_script.*.Release | .history/ | ||||||
| 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 | # Built Visual Studio Code Extensions | ||||||
| target_wrapper.* | *.vsix | ||||||
|  |  | ||||||
| # QtCreator | ### VisualStudioCode Patch ### | ||||||
| *.autosave | # Ignore all local history of files | ||||||
|  | .history | ||||||
|  | .ionide | ||||||
|  |  | ||||||
| # QtCreator Qml | # End of https://www.toptal.com/developers/gitignore/api/rust,visualstudiocode | ||||||
| *.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
									
									
								
							
							
						
						| @@ -1,10 +0,0 @@ | |||||||
| [submodule "singleapplication"] |  | ||||||
| 	path = 3rdparty/SingleApplication |  | ||||||
| 	url = https://github.com/itay-grudev/SingleApplication.git |  | ||||||
|  |  | ||||||
| [submodule "plog"] |  | ||||||
| 	path = 3rdparty/plog |  | ||||||
| 	url = https://github.com/SergiusTheBest/plog.git |  | ||||||
| [submodule "3rdparty/qtkeychain"] |  | ||||||
| 	path = 3rdparty/qtkeychain |  | ||||||
| 	url = git@github.com:frankosterfeld/qtkeychain.git |  | ||||||
							
								
								
									
										74
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,26 +1,52 @@ | |||||||
| { | { | ||||||
|     "files.watcherExclude": { |   "cSpell.words": [ | ||||||
|         "**/artifacts/**": true, |     "authcookie", | ||||||
|     }, |     "bindgen", | ||||||
|     "files.associations": { |     "clickaway", | ||||||
|         "qregularexpression": "cpp", |     "clientgpversion", | ||||||
|         "qfileinfo": "cpp", |     "clientos", | ||||||
|         "qregularexpressionmatch": "cpp", |     "configparser", | ||||||
|         "qdatetime": "cpp", |     "consts", | ||||||
|         "qprocess": "cpp", |     "devicename", | ||||||
|         "qobject": "cpp", |     "distro", | ||||||
|         "qstandardpaths": "cpp", |     "gpcommon", | ||||||
|         "qmainwindow": "cpp", |     "gpconf", | ||||||
|         "qsystemtrayicon": "cpp", |     "gpgui", | ||||||
|         "qpushbutton": "cpp", |     "gpservice", | ||||||
|         "qmenu": "cpp", |     "humantime", | ||||||
|         "qjsondocument": "cpp", |     "Immer", | ||||||
|         "qnetworkaccessmanager": "cpp", |     "jnlp", | ||||||
|         "qwebengineview": "cpp", |     "lexopt", | ||||||
|         "qprocessenvironment": "cpp", |     "notistack", | ||||||
|         "qnetworkreply": "cpp", |     "oneshot", | ||||||
|         "qicon": "cpp", |     "openconnect", | ||||||
|         "qsslsocket": "cpp", |     "pkexec", | ||||||
|         "qapplication": "cpp" |     "prelogin", | ||||||
|     } |     "prelogon", | ||||||
|  |     "prelogonuserauthcookie", | ||||||
|  |     "repr", | ||||||
|  |     "rustc", | ||||||
|  |     "servercert", | ||||||
|  |     "shlex", | ||||||
|  |     "tauri", | ||||||
|  |     "tempdir", | ||||||
|  |     "tempfile", | ||||||
|  |     "thiserror", | ||||||
|  |     "unlisten", | ||||||
|  |     "userauthcookie", | ||||||
|  |     "vpnc", | ||||||
|  |     "vpninfo" | ||||||
|  |   ], | ||||||
|  |   "files.associations": { | ||||||
|  |     "*.css": "css", | ||||||
|  |     "errno.h": "c", | ||||||
|  |     "stdarg.h": "c", | ||||||
|  |     "wrapper.h": "c", | ||||||
|  |     "cstdlib": "c", | ||||||
|  |     "stdio.h": "c", | ||||||
|  |     "openconnect.h": "c", | ||||||
|  |     "compare": "c", | ||||||
|  |     "stdlib.h": "c", | ||||||
|  |     "vpn.h": "c" | ||||||
|  |   } | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										1
									
								
								3rdparty/SingleApplication
									
									
									
									
										vendored
									
									
								
							
							
								
								
								
								
								
							
						
						
							
								
								
									
										12
									
								
								3rdparty/inih/CMakeLists.txt
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,12 +0,0 @@ | |||||||
| cmake_minimum_required(VERSION 3.10.0) |  | ||||||
|  |  | ||||||
| set(CMAKE_CXX_STANDARD 17) |  | ||||||
| project(inih) |  | ||||||
|  |  | ||||||
| add_library(inih STATIC |  | ||||||
|     ini.h |  | ||||||
|     ini.c |  | ||||||
|     cpp/INIReader.h |  | ||||||
|     cpp/INIReader.cpp |  | ||||||
| ) |  | ||||||
| target_include_directories(inih PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/cpp") |  | ||||||
							
								
								
									
										27
									
								
								3rdparty/inih/LICENSE.txt
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,27 +0,0 @@ | |||||||
|  |  | ||||||
| The "inih" library is distributed under the New BSD license: |  | ||||||
|  |  | ||||||
| Copyright (c) 2009, Ben Hoyt |  | ||||||
| All rights reserved. |  | ||||||
|  |  | ||||||
| Redistribution and use in source and binary forms, with or without |  | ||||||
| modification, are permitted provided that the following conditions are met: |  | ||||||
|     * Redistributions of source code must retain the above copyright |  | ||||||
|       notice, this list of conditions and the following disclaimer. |  | ||||||
|     * Redistributions in binary form must reproduce the above copyright |  | ||||||
|       notice, this list of conditions and the following disclaimer in the |  | ||||||
|       documentation and/or other materials provided with the distribution. |  | ||||||
|     * Neither the name of Ben Hoyt nor the names of its contributors |  | ||||||
|       may be used to endorse or promote products derived from this software |  | ||||||
|       without specific prior written permission. |  | ||||||
|  |  | ||||||
| THIS SOFTWARE IS PROVIDED BY BEN HOYT ''AS IS'' AND ANY |  | ||||||
| EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |  | ||||||
| WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |  | ||||||
| DISCLAIMED. IN NO EVENT SHALL BEN HOYT BE LIABLE FOR ANY |  | ||||||
| DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |  | ||||||
| (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |  | ||||||
| LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |  | ||||||
| ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |  | ||||||
| (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |  | ||||||
| SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |  | ||||||
							
								
								
									
										116
									
								
								3rdparty/inih/cpp/INIReader.cpp
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,116 +0,0 @@ | |||||||
| // Read an INI file into easy-to-access name/value pairs. |  | ||||||
|  |  | ||||||
| // SPDX-License-Identifier: BSD-3-Clause |  | ||||||
|  |  | ||||||
| // Copyright (C) 2009-2020, Ben Hoyt |  | ||||||
|  |  | ||||||
| // inih and INIReader are released under the New BSD license (see LICENSE.txt). |  | ||||||
| // Go to the project home page for more info: |  | ||||||
| // |  | ||||||
| // https://github.com/benhoyt/inih |  | ||||||
|  |  | ||||||
| #include <algorithm> |  | ||||||
| #include <cctype> |  | ||||||
| #include <cstdlib> |  | ||||||
| #include "../ini.h" |  | ||||||
| #include "INIReader.h" |  | ||||||
|  |  | ||||||
| using std::string; |  | ||||||
|  |  | ||||||
| INIReader::INIReader(const string& filename) |  | ||||||
| { |  | ||||||
|     _error = ini_parse(filename.c_str(), ValueHandler, this); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| INIReader::INIReader(const char *buffer, size_t buffer_size) |  | ||||||
| { |  | ||||||
|   string content(buffer, buffer_size); |  | ||||||
|   _error = ini_parse_string(content.c_str(), ValueHandler, this); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| int INIReader::ParseError() const |  | ||||||
| { |  | ||||||
|     return _error; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| string INIReader::Get(const string& section, const string& name, const string& default_value) const |  | ||||||
| { |  | ||||||
|     string key = MakeKey(section, name); |  | ||||||
|     // Use _values.find() here instead of _values.at() to support pre C++11 compilers |  | ||||||
|     return _values.count(key) ? _values.find(key)->second : default_value; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| string INIReader::GetString(const string& section, const string& name, const string& default_value) const |  | ||||||
| { |  | ||||||
|     const string str = Get(section, name, ""); |  | ||||||
|     return str.empty() ? default_value : str; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| long INIReader::GetInteger(const string& section, const string& name, long default_value) const |  | ||||||
| { |  | ||||||
|     string valstr = Get(section, name, ""); |  | ||||||
|     const char* value = valstr.c_str(); |  | ||||||
|     char* end; |  | ||||||
|     // This parses "1234" (decimal) and also "0x4D2" (hex) |  | ||||||
|     long n = strtol(value, &end, 0); |  | ||||||
|     return end > value ? n : default_value; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| double INIReader::GetReal(const string& section, const string& name, double default_value) const |  | ||||||
| { |  | ||||||
|     string valstr = Get(section, name, ""); |  | ||||||
|     const char* value = valstr.c_str(); |  | ||||||
|     char* end; |  | ||||||
|     double n = strtod(value, &end); |  | ||||||
|     return end > value ? n : default_value; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| bool INIReader::GetBoolean(const string& section, const string& name, bool default_value) const |  | ||||||
| { |  | ||||||
|     string valstr = Get(section, name, ""); |  | ||||||
|     // Convert to lower case to make string comparisons case-insensitive |  | ||||||
|     std::transform(valstr.begin(), valstr.end(), valstr.begin(), ::tolower); |  | ||||||
|     if (valstr == "true" || valstr == "yes" || valstr == "on" || valstr == "1") |  | ||||||
|         return true; |  | ||||||
|     else if (valstr == "false" || valstr == "no" || valstr == "off" || valstr == "0") |  | ||||||
|         return false; |  | ||||||
|     else |  | ||||||
|         return default_value; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| bool INIReader::HasSection(const string& section) const |  | ||||||
| { |  | ||||||
|     const string key = MakeKey(section, ""); |  | ||||||
|     std::map<string, string>::const_iterator pos = _values.lower_bound(key); |  | ||||||
|     if (pos == _values.end()) |  | ||||||
|         return false; |  | ||||||
|     // Does the key at the lower_bound pos start with "section"? |  | ||||||
|     return pos->first.compare(0, key.length(), key) == 0; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| bool INIReader::HasValue(const string& section, const string& name) const |  | ||||||
| { |  | ||||||
|     string key = MakeKey(section, name); |  | ||||||
|     return _values.count(key); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| string INIReader::MakeKey(const string& section, const string& name) |  | ||||||
| { |  | ||||||
|     string key = section + "=" + name; |  | ||||||
|     // Convert to lower case to make section/name lookups case-insensitive |  | ||||||
|     std::transform(key.begin(), key.end(), key.begin(), ::tolower); |  | ||||||
|     return key; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| int INIReader::ValueHandler(void* user, const char* section, const char* name, |  | ||||||
|                             const char* value) |  | ||||||
| { |  | ||||||
|     if (!name)  // Happens when INI_CALL_HANDLER_ON_NEW_SECTION enabled |  | ||||||
|         return 1; |  | ||||||
|     INIReader* reader = static_cast<INIReader*>(user); |  | ||||||
|     string key = MakeKey(section, name); |  | ||||||
|     if (reader->_values[key].size() > 0) |  | ||||||
|         reader->_values[key] += "\n"; |  | ||||||
|     reader->_values[key] += value ? value : ""; |  | ||||||
|     return 1; |  | ||||||
| } |  | ||||||
							
								
								
									
										94
									
								
								3rdparty/inih/cpp/INIReader.h
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,94 +0,0 @@ | |||||||
| // Read an INI file into easy-to-access name/value pairs. |  | ||||||
|  |  | ||||||
| // SPDX-License-Identifier: BSD-3-Clause |  | ||||||
|  |  | ||||||
| // Copyright (C) 2009-2020, Ben Hoyt |  | ||||||
|  |  | ||||||
| // inih and INIReader are released under the New BSD license (see LICENSE.txt). |  | ||||||
| // Go to the project home page for more info: |  | ||||||
| // |  | ||||||
| // https://github.com/benhoyt/inih |  | ||||||
|  |  | ||||||
| #ifndef INIREADER_H |  | ||||||
| #define INIREADER_H |  | ||||||
|  |  | ||||||
| #include <map> |  | ||||||
| #include <string> |  | ||||||
|  |  | ||||||
| // Visibility symbols, required for Windows DLLs |  | ||||||
| #ifndef INI_API |  | ||||||
| #if defined _WIN32 || defined __CYGWIN__ |  | ||||||
| #	ifdef INI_SHARED_LIB |  | ||||||
| #		ifdef INI_SHARED_LIB_BUILDING |  | ||||||
| #			define INI_API __declspec(dllexport) |  | ||||||
| #		else |  | ||||||
| #			define INI_API __declspec(dllimport) |  | ||||||
| #		endif |  | ||||||
| #	else |  | ||||||
| #		define INI_API |  | ||||||
| #	endif |  | ||||||
| #else |  | ||||||
| #	if defined(__GNUC__) && __GNUC__ >= 4 |  | ||||||
| #		define INI_API __attribute__ ((visibility ("default"))) |  | ||||||
| #	else |  | ||||||
| #		define INI_API |  | ||||||
| #	endif |  | ||||||
| #endif |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
| // Read an INI file into easy-to-access name/value pairs. (Note that I've gone |  | ||||||
| // for simplicity here rather than speed, but it should be pretty decent.) |  | ||||||
| class INIReader |  | ||||||
| { |  | ||||||
| public: |  | ||||||
|     // Construct INIReader and parse given filename. See ini.h for more info |  | ||||||
|     // about the parsing. |  | ||||||
|     INI_API explicit INIReader(const std::string& filename); |  | ||||||
|  |  | ||||||
|     // Construct INIReader and parse given buffer. See ini.h for more info |  | ||||||
|     // about the parsing. |  | ||||||
|     INI_API explicit INIReader(const char *buffer, size_t buffer_size); |  | ||||||
|  |  | ||||||
|     // Return the result of ini_parse(), i.e., 0 on success, line number of |  | ||||||
|     // first error on parse error, or -1 on file open error. |  | ||||||
|     INI_API int ParseError() const; |  | ||||||
|  |  | ||||||
|     // Get a string value from INI file, returning default_value if not found. |  | ||||||
|     INI_API std::string Get(const std::string& section, const std::string& name, |  | ||||||
|                     const std::string& default_value) const; |  | ||||||
|  |  | ||||||
|     // Get a string value from INI file, returning default_value if not found, |  | ||||||
|     // empty, or contains only whitespace. |  | ||||||
|     INI_API std::string GetString(const std::string& section, const std::string& name, |  | ||||||
|                     const std::string& default_value) const; |  | ||||||
|  |  | ||||||
|     // Get an integer (long) value from INI file, returning default_value if |  | ||||||
|     // not found or not a valid integer (decimal "1234", "-1234", or hex "0x4d2"). |  | ||||||
|     INI_API long GetInteger(const std::string& section, const std::string& name, long default_value) const; |  | ||||||
|  |  | ||||||
|     // Get a real (floating point double) value from INI file, returning |  | ||||||
|     // default_value if not found or not a valid floating point value |  | ||||||
|     // according to strtod(). |  | ||||||
|     INI_API double GetReal(const std::string& section, const std::string& name, double default_value) const; |  | ||||||
|  |  | ||||||
|     // Get a boolean value from INI file, returning default_value if not found or if |  | ||||||
|     // not a valid true/false value. Valid true values are "true", "yes", "on", "1", |  | ||||||
|     // and valid false values are "false", "no", "off", "0" (not case sensitive). |  | ||||||
|     INI_API bool GetBoolean(const std::string& section, const std::string& name, bool default_value) const; |  | ||||||
|  |  | ||||||
|     // Return true if the given section exists (section must contain at least |  | ||||||
|     // one name=value pair). |  | ||||||
|     INI_API bool HasSection(const std::string& section) const; |  | ||||||
|  |  | ||||||
|     // Return true if a value exists with the given section and field names. |  | ||||||
|     INI_API bool HasValue(const std::string& section, const std::string& name) const; |  | ||||||
|  |  | ||||||
| private: |  | ||||||
|     int _error; |  | ||||||
|     std::map<std::string, std::string> _values; |  | ||||||
|     static std::string MakeKey(const std::string& section, const std::string& name); |  | ||||||
|     static int ValueHandler(void* user, const char* section, const char* name, |  | ||||||
|                             const char* value); |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| #endif  // INIREADER_H |  | ||||||
							
								
								
									
										298
									
								
								3rdparty/inih/ini.c
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,298 +0,0 @@ | |||||||
| /* inih -- simple .INI file parser |  | ||||||
|  |  | ||||||
| SPDX-License-Identifier: BSD-3-Clause |  | ||||||
|  |  | ||||||
| Copyright (C) 2009-2020, Ben Hoyt |  | ||||||
|  |  | ||||||
| inih is released under the New BSD license (see LICENSE.txt). Go to the project |  | ||||||
| home page for more info: |  | ||||||
|  |  | ||||||
| https://github.com/benhoyt/inih |  | ||||||
|  |  | ||||||
| */ |  | ||||||
|  |  | ||||||
| #if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) |  | ||||||
| #define _CRT_SECURE_NO_WARNINGS |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
| #include <stdio.h> |  | ||||||
| #include <ctype.h> |  | ||||||
| #include <string.h> |  | ||||||
|  |  | ||||||
| #include "ini.h" |  | ||||||
|  |  | ||||||
| #if !INI_USE_STACK |  | ||||||
| #if INI_CUSTOM_ALLOCATOR |  | ||||||
| #include <stddef.h> |  | ||||||
| void* ini_malloc(size_t size); |  | ||||||
| void ini_free(void* ptr); |  | ||||||
| void* ini_realloc(void* ptr, size_t size); |  | ||||||
| #else |  | ||||||
| #include <stdlib.h> |  | ||||||
| #define ini_malloc malloc |  | ||||||
| #define ini_free free |  | ||||||
| #define ini_realloc realloc |  | ||||||
| #endif |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
| #define MAX_SECTION 50 |  | ||||||
| #define MAX_NAME 50 |  | ||||||
|  |  | ||||||
| /* Used by ini_parse_string() to keep track of string parsing state. */ |  | ||||||
| typedef struct { |  | ||||||
|     const char* ptr; |  | ||||||
|     size_t num_left; |  | ||||||
| } ini_parse_string_ctx; |  | ||||||
|  |  | ||||||
| /* Strip whitespace chars off end of given string, in place. Return s. */ |  | ||||||
| static char* rstrip(char* s) |  | ||||||
| { |  | ||||||
|     char* p = s + strlen(s); |  | ||||||
|     while (p > s && isspace((unsigned char)(*--p))) |  | ||||||
|         *p = '\0'; |  | ||||||
|     return s; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /* Return pointer to first non-whitespace char in given string. */ |  | ||||||
| static char* lskip(const char* s) |  | ||||||
| { |  | ||||||
|     while (*s && isspace((unsigned char)(*s))) |  | ||||||
|         s++; |  | ||||||
|     return (char*)s; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /* Return pointer to first char (of chars) or inline comment in given string, |  | ||||||
|    or pointer to NUL at end of string if neither found. Inline comment must |  | ||||||
|    be prefixed by a whitespace character to register as a comment. */ |  | ||||||
| static char* find_chars_or_comment(const char* s, const char* chars) |  | ||||||
| { |  | ||||||
| #if INI_ALLOW_INLINE_COMMENTS |  | ||||||
|     int was_space = 0; |  | ||||||
|     while (*s && (!chars || !strchr(chars, *s)) && |  | ||||||
|            !(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) { |  | ||||||
|         was_space = isspace((unsigned char)(*s)); |  | ||||||
|         s++; |  | ||||||
|     } |  | ||||||
| #else |  | ||||||
|     while (*s && (!chars || !strchr(chars, *s))) { |  | ||||||
|         s++; |  | ||||||
|     } |  | ||||||
| #endif |  | ||||||
|     return (char*)s; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /* Similar to strncpy, but ensures dest (size bytes) is |  | ||||||
|    NUL-terminated, and doesn't pad with NULs. */ |  | ||||||
| static char* strncpy0(char* dest, const char* src, size_t size) |  | ||||||
| { |  | ||||||
|     /* Could use strncpy internally, but it causes gcc warnings (see issue #91) */ |  | ||||||
|     size_t i; |  | ||||||
|     for (i = 0; i < size - 1 && src[i]; i++) |  | ||||||
|         dest[i] = src[i]; |  | ||||||
|     dest[i] = '\0'; |  | ||||||
|     return dest; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /* See documentation in header file. */ |  | ||||||
| int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler, |  | ||||||
|                      void* user) |  | ||||||
| { |  | ||||||
|     /* Uses a fair bit of stack (use heap instead if you need to) */ |  | ||||||
| #if INI_USE_STACK |  | ||||||
|     char line[INI_MAX_LINE]; |  | ||||||
|     int max_line = INI_MAX_LINE; |  | ||||||
| #else |  | ||||||
|     char* line; |  | ||||||
|     size_t max_line = INI_INITIAL_ALLOC; |  | ||||||
| #endif |  | ||||||
| #if INI_ALLOW_REALLOC && !INI_USE_STACK |  | ||||||
|     char* new_line; |  | ||||||
|     size_t offset; |  | ||||||
| #endif |  | ||||||
|     char section[MAX_SECTION] = ""; |  | ||||||
|     char prev_name[MAX_NAME] = ""; |  | ||||||
|  |  | ||||||
|     char* start; |  | ||||||
|     char* end; |  | ||||||
|     char* name; |  | ||||||
|     char* value; |  | ||||||
|     int lineno = 0; |  | ||||||
|     int error = 0; |  | ||||||
|  |  | ||||||
| #if !INI_USE_STACK |  | ||||||
|     line = (char*)ini_malloc(INI_INITIAL_ALLOC); |  | ||||||
|     if (!line) { |  | ||||||
|         return -2; |  | ||||||
|     } |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
| #if INI_HANDLER_LINENO |  | ||||||
| #define HANDLER(u, s, n, v) handler(u, s, n, v, lineno) |  | ||||||
| #else |  | ||||||
| #define HANDLER(u, s, n, v) handler(u, s, n, v) |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
|     /* Scan through stream line by line */ |  | ||||||
|     while (reader(line, (int)max_line, stream) != NULL) { |  | ||||||
| #if INI_ALLOW_REALLOC && !INI_USE_STACK |  | ||||||
|         offset = strlen(line); |  | ||||||
|         while (offset == max_line - 1 && line[offset - 1] != '\n') { |  | ||||||
|             max_line *= 2; |  | ||||||
|             if (max_line > INI_MAX_LINE) |  | ||||||
|                 max_line = INI_MAX_LINE; |  | ||||||
|             new_line = ini_realloc(line, max_line); |  | ||||||
|             if (!new_line) { |  | ||||||
|                 ini_free(line); |  | ||||||
|                 return -2; |  | ||||||
|             } |  | ||||||
|             line = new_line; |  | ||||||
|             if (reader(line + offset, (int)(max_line - offset), stream) == NULL) |  | ||||||
|                 break; |  | ||||||
|             if (max_line >= INI_MAX_LINE) |  | ||||||
|                 break; |  | ||||||
|             offset += strlen(line + offset); |  | ||||||
|         } |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
|         lineno++; |  | ||||||
|  |  | ||||||
|         start = line; |  | ||||||
| #if INI_ALLOW_BOM |  | ||||||
|         if (lineno == 1 && (unsigned char)start[0] == 0xEF && |  | ||||||
|                            (unsigned char)start[1] == 0xBB && |  | ||||||
|                            (unsigned char)start[2] == 0xBF) { |  | ||||||
|             start += 3; |  | ||||||
|         } |  | ||||||
| #endif |  | ||||||
|         start = lskip(rstrip(start)); |  | ||||||
|  |  | ||||||
|         if (strchr(INI_START_COMMENT_PREFIXES, *start)) { |  | ||||||
|             /* Start-of-line comment */ |  | ||||||
|         } |  | ||||||
| #if INI_ALLOW_MULTILINE |  | ||||||
|         else if (*prev_name && *start && start > line) { |  | ||||||
|             /* Non-blank line with leading whitespace, treat as continuation |  | ||||||
|                of previous name's value (as per Python configparser). */ |  | ||||||
|             if (!HANDLER(user, section, prev_name, start) && !error) |  | ||||||
|                 error = lineno; |  | ||||||
|         } |  | ||||||
| #endif |  | ||||||
|         else if (*start == '[') { |  | ||||||
|             /* A "[section]" line */ |  | ||||||
|             end = find_chars_or_comment(start + 1, "]"); |  | ||||||
|             if (*end == ']') { |  | ||||||
|                 *end = '\0'; |  | ||||||
|                 strncpy0(section, start + 1, sizeof(section)); |  | ||||||
|                 *prev_name = '\0'; |  | ||||||
| #if INI_CALL_HANDLER_ON_NEW_SECTION |  | ||||||
|                 if (!HANDLER(user, section, NULL, NULL) && !error) |  | ||||||
|                     error = lineno; |  | ||||||
| #endif |  | ||||||
|             } |  | ||||||
|             else if (!error) { |  | ||||||
|                 /* No ']' found on section line */ |  | ||||||
|                 error = lineno; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         else if (*start) { |  | ||||||
|             /* Not a comment, must be a name[=:]value pair */ |  | ||||||
|             end = find_chars_or_comment(start, "=:"); |  | ||||||
|             if (*end == '=' || *end == ':') { |  | ||||||
|                 *end = '\0'; |  | ||||||
|                 name = rstrip(start); |  | ||||||
|                 value = end + 1; |  | ||||||
| #if INI_ALLOW_INLINE_COMMENTS |  | ||||||
|                 end = find_chars_or_comment(value, NULL); |  | ||||||
|                 if (*end) |  | ||||||
|                     *end = '\0'; |  | ||||||
| #endif |  | ||||||
|                 value = lskip(value); |  | ||||||
|                 rstrip(value); |  | ||||||
|  |  | ||||||
|                 /* Valid name[=:]value pair found, call handler */ |  | ||||||
|                 strncpy0(prev_name, name, sizeof(prev_name)); |  | ||||||
|                 if (!HANDLER(user, section, name, value) && !error) |  | ||||||
|                     error = lineno; |  | ||||||
|             } |  | ||||||
|             else if (!error) { |  | ||||||
|                 /* No '=' or ':' found on name[=:]value line */ |  | ||||||
| #if INI_ALLOW_NO_VALUE |  | ||||||
|                 *end = '\0'; |  | ||||||
|                 name = rstrip(start); |  | ||||||
|                 if (!HANDLER(user, section, name, NULL) && !error) |  | ||||||
|                     error = lineno; |  | ||||||
| #else |  | ||||||
|                 error = lineno; |  | ||||||
| #endif |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
| #if INI_STOP_ON_FIRST_ERROR |  | ||||||
|         if (error) |  | ||||||
|             break; |  | ||||||
| #endif |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| #if !INI_USE_STACK |  | ||||||
|     ini_free(line); |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
|     return error; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /* See documentation in header file. */ |  | ||||||
| int ini_parse_file(FILE* file, ini_handler handler, void* user) |  | ||||||
| { |  | ||||||
|     return ini_parse_stream((ini_reader)fgets, file, handler, user); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /* See documentation in header file. */ |  | ||||||
| int ini_parse(const char* filename, ini_handler handler, void* user) |  | ||||||
| { |  | ||||||
|     FILE* file; |  | ||||||
|     int error; |  | ||||||
|  |  | ||||||
|     file = fopen(filename, "r"); |  | ||||||
|     if (!file) |  | ||||||
|         return -1; |  | ||||||
|     error = ini_parse_file(file, handler, user); |  | ||||||
|     fclose(file); |  | ||||||
|     return error; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /* An ini_reader function to read the next line from a string buffer. This |  | ||||||
|    is the fgets() equivalent used by ini_parse_string(). */ |  | ||||||
| static char* ini_reader_string(char* str, int num, void* stream) { |  | ||||||
|     ini_parse_string_ctx* ctx = (ini_parse_string_ctx*)stream; |  | ||||||
|     const char* ctx_ptr = ctx->ptr; |  | ||||||
|     size_t ctx_num_left = ctx->num_left; |  | ||||||
|     char* strp = str; |  | ||||||
|     char c; |  | ||||||
|  |  | ||||||
|     if (ctx_num_left == 0 || num < 2) |  | ||||||
|         return NULL; |  | ||||||
|  |  | ||||||
|     while (num > 1 && ctx_num_left != 0) { |  | ||||||
|         c = *ctx_ptr++; |  | ||||||
|         ctx_num_left--; |  | ||||||
|         *strp++ = c; |  | ||||||
|         if (c == '\n') |  | ||||||
|             break; |  | ||||||
|         num--; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     *strp = '\0'; |  | ||||||
|     ctx->ptr = ctx_ptr; |  | ||||||
|     ctx->num_left = ctx_num_left; |  | ||||||
|     return str; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /* See documentation in header file. */ |  | ||||||
| int ini_parse_string(const char* string, ini_handler handler, void* user) { |  | ||||||
|     ini_parse_string_ctx ctx; |  | ||||||
|  |  | ||||||
|     ctx.ptr = string; |  | ||||||
|     ctx.num_left = strlen(string); |  | ||||||
|     return ini_parse_stream((ini_reader)ini_reader_string, &ctx, handler, |  | ||||||
|                             user); |  | ||||||
| } |  | ||||||
							
								
								
									
										178
									
								
								3rdparty/inih/ini.h
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,178 +0,0 @@ | |||||||
| /* inih -- simple .INI file parser |  | ||||||
|  |  | ||||||
| SPDX-License-Identifier: BSD-3-Clause |  | ||||||
|  |  | ||||||
| Copyright (C) 2009-2020, Ben Hoyt |  | ||||||
|  |  | ||||||
| inih is released under the New BSD license (see LICENSE.txt). Go to the project |  | ||||||
| home page for more info: |  | ||||||
|  |  | ||||||
| https://github.com/benhoyt/inih |  | ||||||
|  |  | ||||||
| */ |  | ||||||
|  |  | ||||||
| #ifndef INI_H |  | ||||||
| #define INI_H |  | ||||||
|  |  | ||||||
| /* Make this header file easier to include in C++ code */ |  | ||||||
| #ifdef __cplusplus |  | ||||||
| extern "C" { |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
| #include <stdio.h> |  | ||||||
|  |  | ||||||
| /* Nonzero if ini_handler callback should accept lineno parameter. */ |  | ||||||
| #ifndef INI_HANDLER_LINENO |  | ||||||
| #define INI_HANDLER_LINENO 0 |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
| /* Visibility symbols, required for Windows DLLs */ |  | ||||||
| #ifndef INI_API |  | ||||||
| #if defined _WIN32 || defined __CYGWIN__ |  | ||||||
| #	ifdef INI_SHARED_LIB |  | ||||||
| #		ifdef INI_SHARED_LIB_BUILDING |  | ||||||
| #			define INI_API __declspec(dllexport) |  | ||||||
| #		else |  | ||||||
| #			define INI_API __declspec(dllimport) |  | ||||||
| #		endif |  | ||||||
| #	else |  | ||||||
| #		define INI_API |  | ||||||
| #	endif |  | ||||||
| #else |  | ||||||
| #	if defined(__GNUC__) && __GNUC__ >= 4 |  | ||||||
| #		define INI_API __attribute__ ((visibility ("default"))) |  | ||||||
| #	else |  | ||||||
| #		define INI_API |  | ||||||
| #	endif |  | ||||||
| #endif |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
| /* Typedef for prototype of handler function. */ |  | ||||||
| #if INI_HANDLER_LINENO |  | ||||||
| typedef int (*ini_handler)(void* user, const char* section, |  | ||||||
|                            const char* name, const char* value, |  | ||||||
|                            int lineno); |  | ||||||
| #else |  | ||||||
| typedef int (*ini_handler)(void* user, const char* section, |  | ||||||
|                            const char* name, const char* value); |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
| /* Typedef for prototype of fgets-style reader function. */ |  | ||||||
| typedef char* (*ini_reader)(char* str, int num, void* stream); |  | ||||||
|  |  | ||||||
| /* Parse given INI-style file. May have [section]s, name=value pairs |  | ||||||
|    (whitespace stripped), and comments starting with ';' (semicolon). Section |  | ||||||
|    is "" if name=value pair parsed before any section heading. name:value |  | ||||||
|    pairs are also supported as a concession to Python's configparser. |  | ||||||
|  |  | ||||||
|    For each name=value pair parsed, call handler function with given user |  | ||||||
|    pointer as well as section, name, and value (data only valid for duration |  | ||||||
|    of handler call). Handler should return nonzero on success, zero on error. |  | ||||||
|  |  | ||||||
|    Returns 0 on success, line number of first error on parse error (doesn't |  | ||||||
|    stop on first error), -1 on file open error, or -2 on memory allocation |  | ||||||
|    error (only when INI_USE_STACK is zero). |  | ||||||
| */ |  | ||||||
| INI_API int ini_parse(const char* filename, ini_handler handler, void* user); |  | ||||||
|  |  | ||||||
| /* Same as ini_parse(), but takes a FILE* instead of filename. This doesn't |  | ||||||
|    close the file when it's finished -- the caller must do that. */ |  | ||||||
| INI_API int ini_parse_file(FILE* file, ini_handler handler, void* user); |  | ||||||
|  |  | ||||||
| /* Same as ini_parse(), but takes an ini_reader function pointer instead of |  | ||||||
|    filename. Used for implementing custom or string-based I/O (see also |  | ||||||
|    ini_parse_string). */ |  | ||||||
| INI_API int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler, |  | ||||||
|                      void* user); |  | ||||||
|  |  | ||||||
| /* Same as ini_parse(), but takes a zero-terminated string with the INI data |  | ||||||
| instead of a file. Useful for parsing INI data from a network socket or |  | ||||||
| already in memory. */ |  | ||||||
| INI_API int ini_parse_string(const char* string, ini_handler handler, void* user); |  | ||||||
|  |  | ||||||
| /* Nonzero to allow multi-line value parsing, in the style of Python's |  | ||||||
|    configparser. If allowed, ini_parse() will call the handler with the same |  | ||||||
|    name for each subsequent line parsed. */ |  | ||||||
| #ifndef INI_ALLOW_MULTILINE |  | ||||||
| #define INI_ALLOW_MULTILINE 1 |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
| /* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of |  | ||||||
|    the file. See https://github.com/benhoyt/inih/issues/21 */ |  | ||||||
| #ifndef INI_ALLOW_BOM |  | ||||||
| #define INI_ALLOW_BOM 1 |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
| /* Chars that begin a start-of-line comment. Per Python configparser, allow |  | ||||||
|    both ; and # comments at the start of a line by default. */ |  | ||||||
| #ifndef INI_START_COMMENT_PREFIXES |  | ||||||
| #define INI_START_COMMENT_PREFIXES ";#" |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
| /* Nonzero to allow inline comments (with valid inline comment characters |  | ||||||
|    specified by INI_INLINE_COMMENT_PREFIXES). Set to 0 to turn off and match |  | ||||||
|    Python 3.2+ configparser behaviour. */ |  | ||||||
| #ifndef INI_ALLOW_INLINE_COMMENTS |  | ||||||
| #define INI_ALLOW_INLINE_COMMENTS 1 |  | ||||||
| #endif |  | ||||||
| #ifndef INI_INLINE_COMMENT_PREFIXES |  | ||||||
| #define INI_INLINE_COMMENT_PREFIXES ";" |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
| /* Nonzero to use stack for line buffer, zero to use heap (malloc/free). */ |  | ||||||
| #ifndef INI_USE_STACK |  | ||||||
| #define INI_USE_STACK 1 |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
| /* Maximum line length for any line in INI file (stack or heap). Note that |  | ||||||
|    this must be 3 more than the longest line (due to '\r', '\n', and '\0'). */ |  | ||||||
| #ifndef INI_MAX_LINE |  | ||||||
| #define INI_MAX_LINE 200 |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
| /* Nonzero to allow heap line buffer to grow via realloc(), zero for a |  | ||||||
|    fixed-size buffer of INI_MAX_LINE bytes. Only applies if INI_USE_STACK is |  | ||||||
|    zero. */ |  | ||||||
| #ifndef INI_ALLOW_REALLOC |  | ||||||
| #define INI_ALLOW_REALLOC 0 |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
| /* Initial size in bytes for heap line buffer. Only applies if INI_USE_STACK |  | ||||||
|    is zero. */ |  | ||||||
| #ifndef INI_INITIAL_ALLOC |  | ||||||
| #define INI_INITIAL_ALLOC 200 |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
| /* Stop parsing on first error (default is to keep parsing). */ |  | ||||||
| #ifndef INI_STOP_ON_FIRST_ERROR |  | ||||||
| #define INI_STOP_ON_FIRST_ERROR 0 |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
| /* Nonzero to call the handler at the start of each new section (with |  | ||||||
|    name and value NULL). Default is to only call the handler on |  | ||||||
|    each name=value pair. */ |  | ||||||
| #ifndef INI_CALL_HANDLER_ON_NEW_SECTION |  | ||||||
| #define INI_CALL_HANDLER_ON_NEW_SECTION 0 |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
| /* Nonzero to allow a name without a value (no '=' or ':' on the line) and |  | ||||||
|    call the handler with value NULL in this case. Default is to treat |  | ||||||
|    no-value lines as an error. */ |  | ||||||
| #ifndef INI_ALLOW_NO_VALUE |  | ||||||
| #define INI_ALLOW_NO_VALUE 0 |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
| /* Nonzero to use custom ini_malloc, ini_free, and ini_realloc memory |  | ||||||
|    allocation functions (INI_USE_STACK must also be 0). These functions must |  | ||||||
|    have the same signatures as malloc/free/realloc and behave in a similar |  | ||||||
|    way. ini_realloc is only needed if INI_ALLOW_REALLOC is set. */ |  | ||||||
| #ifndef INI_CUSTOM_ALLOCATOR |  | ||||||
| #define INI_CUSTOM_ALLOCATOR 0 |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
|  |  | ||||||
| #ifdef __cplusplus |  | ||||||
| } |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
| #endif /* INI_H */ |  | ||||||
							
								
								
									
										1
									
								
								3rdparty/plog
									
									
									
									
										vendored
									
									
								
							
							
								
								
								
								
								
							
						
						
							
								
								
									
										14
									
								
								3rdparty/qt-unix-signals/CMakeLists.txt
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,14 +0,0 @@ | |||||||
| cmake_minimum_required(VERSION 3.1.0) |  | ||||||
|  |  | ||||||
| project(QtSignals LANGUAGES CXX) |  | ||||||
|  |  | ||||||
| set(CMAKE_CXX_STANDARD 17) |  | ||||||
| set(CMAKE_CXX_STANDARD_REQUIRED ON) |  | ||||||
| # Instruct CMake to run moc automatically when needed. |  | ||||||
| set(CMAKE_AUTOMOC ON) |  | ||||||
|  |  | ||||||
| find_package(Qt5 REQUIRED COMPONENTS Core) |  | ||||||
|  |  | ||||||
| add_library(QtSignals STATIC sigwatch.cpp) |  | ||||||
| target_include_directories(QtSignals INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) |  | ||||||
| target_link_libraries(QtSignals Qt5::Core) |  | ||||||
							
								
								
									
										21
									
								
								3rdparty/qt-unix-signals/LICENCE
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,21 +0,0 @@ | |||||||
| Unix signal watcher for Qt. |  | ||||||
|  |  | ||||||
| Copyright (C) 2014 Simon Knopp |  | ||||||
|  |  | ||||||
| Permission is hereby granted, free of charge, to any person obtaining a copy |  | ||||||
| of this software and associated documentation files (the "Software"), to deal |  | ||||||
| in the Software without restriction, including without limitation the rights |  | ||||||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |  | ||||||
| copies of the Software, and to permit persons to whom the Software is |  | ||||||
| furnished to do so, subject to the following conditions: |  | ||||||
|  |  | ||||||
| The above copyright notice and this permission notice shall be included in |  | ||||||
| all copies or substantial portions of the Software. |  | ||||||
|  |  | ||||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |  | ||||||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |  | ||||||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |  | ||||||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |  | ||||||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |  | ||||||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |  | ||||||
| SOFTWARE. |  | ||||||
							
								
								
									
										176
									
								
								3rdparty/qt-unix-signals/sigwatch.cpp
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,176 +0,0 @@ | |||||||
| /* |  | ||||||
|  * Unix signal watcher for Qt. |  | ||||||
|  * |  | ||||||
|  * Copyright (C) 2014 Simon Knopp |  | ||||||
|  * |  | ||||||
|  * Permission is hereby granted, free of charge, to any person obtaining a copy |  | ||||||
|  * of this software and associated documentation files (the "Software"), to deal |  | ||||||
|  * in the Software without restriction, including without limitation the rights |  | ||||||
|  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |  | ||||||
|  * copies of the Software, and to permit persons to whom the Software is |  | ||||||
|  * furnished to do so, subject to the following conditions: |  | ||||||
|  * |  | ||||||
|  * The above copyright notice and this permission notice shall be included in |  | ||||||
|  * all copies or substantial portions of the Software. |  | ||||||
|  * |  | ||||||
|  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |  | ||||||
|  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |  | ||||||
|  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |  | ||||||
|  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |  | ||||||
|  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |  | ||||||
|  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |  | ||||||
|  * SOFTWARE. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| #include <sys/socket.h> |  | ||||||
| #include <unistd.h> |  | ||||||
| #include <errno.h> |  | ||||||
| #include <QMap> |  | ||||||
| #include <QSocketNotifier> |  | ||||||
| #include <QDebug> |  | ||||||
| #include "sigwatch.h" |  | ||||||
|  |  | ||||||
|  |  | ||||||
| /*! |  | ||||||
|  * \brief The UnixSignalWatcherPrivate class implements the back-end signal |  | ||||||
|  * handling for the UnixSignalWatcher. |  | ||||||
|  * |  | ||||||
|  * \see http://qt-project.org/doc/qt-5.0/qtdoc/unix-signals.html |  | ||||||
|  */ |  | ||||||
| class UnixSignalWatcherPrivate : public QObject |  | ||||||
| { |  | ||||||
|     UnixSignalWatcher * const q_ptr; |  | ||||||
|     Q_DECLARE_PUBLIC(UnixSignalWatcher) |  | ||||||
|  |  | ||||||
| public: |  | ||||||
|     UnixSignalWatcherPrivate(UnixSignalWatcher *q); |  | ||||||
|     ~UnixSignalWatcherPrivate(); |  | ||||||
|  |  | ||||||
|     void watchForSignal(int signal); |  | ||||||
|     static void signalHandler(int signal); |  | ||||||
|  |  | ||||||
|     void _q_onNotify(int sockfd); |  | ||||||
|  |  | ||||||
| private: |  | ||||||
|     static int sockpair[2]; |  | ||||||
|     QSocketNotifier *notifier; |  | ||||||
|     QList<int> watchedSignals; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
|  |  | ||||||
| int UnixSignalWatcherPrivate::sockpair[2]; |  | ||||||
|  |  | ||||||
| UnixSignalWatcherPrivate::UnixSignalWatcherPrivate(UnixSignalWatcher *q) : |  | ||||||
|     q_ptr(q) |  | ||||||
| { |  | ||||||
|     // Create socket pair |  | ||||||
|     if (::socketpair(AF_UNIX, SOCK_STREAM, 0, sockpair)) { |  | ||||||
|         qDebug() << "UnixSignalWatcher: socketpair: " << ::strerror(errno); |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // Create a notifier for the read end of the pair |  | ||||||
|     notifier = new QSocketNotifier(sockpair[1], QSocketNotifier::Read); |  | ||||||
|     QObject::connect(notifier, SIGNAL(activated(int)), q, SLOT(_q_onNotify(int))); |  | ||||||
|     notifier->setEnabled(true); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| UnixSignalWatcherPrivate::~UnixSignalWatcherPrivate() |  | ||||||
| { |  | ||||||
|     delete notifier; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /*! |  | ||||||
|  * Registers a handler for the given Unix \a signal. The handler will write to |  | ||||||
|  * a socket pair, the other end of which is connected to a QSocketNotifier. |  | ||||||
|  * This provides a way to break out of the asynchronous context from which the |  | ||||||
|  * signal handler is called and back into the Qt event loop. |  | ||||||
|  */ |  | ||||||
| void UnixSignalWatcherPrivate::watchForSignal(int signal) |  | ||||||
| { |  | ||||||
|     if (watchedSignals.contains(signal)) { |  | ||||||
|         qDebug() << "Already watching for signal" << signal; |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // Register a sigaction which will write to the socket pair |  | ||||||
|     struct sigaction sigact; |  | ||||||
|     sigact.sa_handler = UnixSignalWatcherPrivate::signalHandler; |  | ||||||
|     sigact.sa_flags = 0; |  | ||||||
|     ::sigemptyset(&sigact.sa_mask); |  | ||||||
|     sigact.sa_flags |= SA_RESTART; |  | ||||||
|     if (::sigaction(signal, &sigact, NULL)) { |  | ||||||
|         qDebug() << "UnixSignalWatcher: sigaction: " << ::strerror(errno); |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     watchedSignals.append(signal); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /*! |  | ||||||
|  * Called when a Unix \a signal is received. Write to the socket to wake up the |  | ||||||
|  * QSocketNotifier. |  | ||||||
|  */ |  | ||||||
| void UnixSignalWatcherPrivate::signalHandler(int signal) |  | ||||||
| { |  | ||||||
|     ssize_t nBytes = ::write(sockpair[0], &signal, sizeof(signal)); |  | ||||||
|     Q_UNUSED(nBytes); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /*! |  | ||||||
|  * Called when the signal handler has written to the socket pair. Emits the Unix |  | ||||||
|  * signal as a Qt signal. |  | ||||||
|  */ |  | ||||||
| void UnixSignalWatcherPrivate::_q_onNotify(int sockfd) |  | ||||||
| { |  | ||||||
|     Q_Q(UnixSignalWatcher); |  | ||||||
|  |  | ||||||
|     int signal; |  | ||||||
|     ssize_t nBytes = ::read(sockfd, &signal, sizeof(signal)); |  | ||||||
|     Q_UNUSED(nBytes); |  | ||||||
|     qDebug() << "Caught signal:" << ::strsignal(signal); |  | ||||||
|     emit q->unixSignal(signal); |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| /*! |  | ||||||
|  * Create a new UnixSignalWatcher as a child of the given \a parent. |  | ||||||
|  */ |  | ||||||
| UnixSignalWatcher::UnixSignalWatcher(QObject *parent) : |  | ||||||
|     QObject(parent), |  | ||||||
|     d_ptr(new UnixSignalWatcherPrivate(this)) |  | ||||||
| { |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /*! |  | ||||||
|  * Destroy this UnixSignalWatcher. |  | ||||||
|  */ |  | ||||||
| UnixSignalWatcher::~UnixSignalWatcher() |  | ||||||
| { |  | ||||||
|     delete d_ptr; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /*! |  | ||||||
|  * Register a signal handler for the given \a signal. |  | ||||||
|  * |  | ||||||
|  * After calling this method you can \c connect() to the unixSignal() Qt signal |  | ||||||
|  * to be notified when the Unix signal is received. |  | ||||||
|  */ |  | ||||||
| void UnixSignalWatcher::watchForSignal(int signal) |  | ||||||
| { |  | ||||||
|     Q_D(UnixSignalWatcher); |  | ||||||
|     d->watchForSignal(signal); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /*! |  | ||||||
|  * \fn void UnixSignalWatcher::unixSignal(int signal) |  | ||||||
|  * Emitted when the given Unix \a signal is received. |  | ||||||
|  * |  | ||||||
|  * watchForSignal() must be called for each Unix signal that you want to receive |  | ||||||
|  * via the unixSignal() Qt signal. If a watcher is watching multiple signals, |  | ||||||
|  * unixSignal() will be emitted whenever *any* of the watched Unix signals are |  | ||||||
|  * received, and the \a signal argument can be inspected to find out which one |  | ||||||
|  * was actually received. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| #include "moc_sigwatch.cpp" |  | ||||||
							
								
								
									
										59
									
								
								3rdparty/qt-unix-signals/sigwatch.h
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,59 +0,0 @@ | |||||||
| /* |  | ||||||
|  * Unix signal watcher for Qt. |  | ||||||
|  * |  | ||||||
|  * Copyright (C) 2014 Simon Knopp |  | ||||||
|  * |  | ||||||
|  * Permission is hereby granted, free of charge, to any person obtaining a copy |  | ||||||
|  * of this software and associated documentation files (the "Software"), to deal |  | ||||||
|  * in the Software without restriction, including without limitation the rights |  | ||||||
|  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |  | ||||||
|  * copies of the Software, and to permit persons to whom the Software is |  | ||||||
|  * furnished to do so, subject to the following conditions: |  | ||||||
|  * |  | ||||||
|  * The above copyright notice and this permission notice shall be included in |  | ||||||
|  * all copies or substantial portions of the Software. |  | ||||||
|  * |  | ||||||
|  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |  | ||||||
|  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |  | ||||||
|  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |  | ||||||
|  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |  | ||||||
|  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |  | ||||||
|  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |  | ||||||
|  * SOFTWARE. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| #ifndef SIGWATCH_H |  | ||||||
| #define SIGWATCH_H |  | ||||||
|  |  | ||||||
| #include <QObject> |  | ||||||
| #include <signal.h> |  | ||||||
|  |  | ||||||
| class UnixSignalWatcherPrivate; |  | ||||||
|  |  | ||||||
|  |  | ||||||
| /*! |  | ||||||
|  * \brief The UnixSignalWatcher class converts Unix signals to Qt signals. |  | ||||||
|  * |  | ||||||
|  * To watch for a given signal, e.g. \c SIGINT, call \c watchForSignal(SIGINT) |  | ||||||
|  * and \c connect() your handler to unixSignal(). |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| class UnixSignalWatcher : public QObject |  | ||||||
| { |  | ||||||
|     Q_OBJECT |  | ||||||
| public: |  | ||||||
|     explicit UnixSignalWatcher(QObject *parent = 0); |  | ||||||
|     ~UnixSignalWatcher(); |  | ||||||
|  |  | ||||||
|     void watchForSignal(int signal); |  | ||||||
|  |  | ||||||
| signals: |  | ||||||
|     void unixSignal(int signal); |  | ||||||
|  |  | ||||||
| private: |  | ||||||
|     UnixSignalWatcherPrivate * const d_ptr; |  | ||||||
|     Q_DECLARE_PRIVATE(UnixSignalWatcher) |  | ||||||
|     Q_PRIVATE_SLOT(d_func(), void _q_onNotify(int)) |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| #endif // SIGWATCH_H |  | ||||||
							
								
								
									
										1
									
								
								3rdparty/qtkeychain
									
									
									
									
										vendored
									
									
								
							
							
								
								
								
								
								
							
						
						| @@ -1,39 +0,0 @@ | |||||||
| cmake_minimum_required(VERSION 3.10.0) |  | ||||||
|  |  | ||||||
| set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake;${CMAKE_MODULE_PATH}") |  | ||||||
| set(CMAKE_CXX_STANDARD 17) |  | ||||||
| set(CMAKE_CXX_STANDARD_REQUIRED ON) |  | ||||||
|  |  | ||||||
| set(CMAKE_AUTOMOC ON) |  | ||||||
| set(CMAKE_AUTORCC ON) |  | ||||||
| set(CMAKE_AUTOUIC ON) |  | ||||||
|  |  | ||||||
| file(STRINGS "VERSION" version) |  | ||||||
| project(GlobalProtect-openconnect LANGUAGES CXX) |  | ||||||
|  |  | ||||||
| # Set the CMAKE_INSTALL_PREFIX to /usr if not specified |  | ||||||
| if (CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) |  | ||||||
|     set(CMAKE_INSTALL_PREFIX "/usr" CACHE PATH "The default install prefix" FORCE) |  | ||||||
| endif() |  | ||||||
|  |  | ||||||
| message(STATUS "CMAKE_INSTALL_PREFIX was set to: ${CMAKE_INSTALL_PREFIX}") |  | ||||||
|  |  | ||||||
| configure_file(version.h.in version.h) |  | ||||||
|  |  | ||||||
| find_package(Qt5 REQUIRED COMPONENTS |  | ||||||
|     Core |  | ||||||
|     Widgets |  | ||||||
|     Network |  | ||||||
|     WebSockets |  | ||||||
|     WebEngine |  | ||||||
|     WebEngineWidgets |  | ||||||
|     DBus |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| find_package(Qt5Keychain REQUIRED) |  | ||||||
|  |  | ||||||
| add_subdirectory(3rdparty/qt-unix-signals) |  | ||||||
| add_subdirectory(3rdparty/inih) |  | ||||||
| add_subdirectory(GPService) |  | ||||||
| add_subdirectory(GPClient) |  | ||||||
| add_dependencies(gpclient gpservice) |  | ||||||
							
								
								
									
										4739
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										12
									
								
								Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,12 @@ | |||||||
|  | [workspace] | ||||||
|  |  | ||||||
|  | members = [ | ||||||
|  |     "gpcommon", | ||||||
|  |     "gpclient", | ||||||
|  |     "gpservice", | ||||||
|  |     "gpgui/src-tauri" | ||||||
|  | ] | ||||||
|  |  | ||||||
|  | [profile.release] | ||||||
|  | strip = true | ||||||
|  | opt-level = "z" | ||||||
| @@ -1,109 +0,0 @@ | |||||||
| include("${CMAKE_SOURCE_DIR}/cmake/Add3rdParty.cmake") |  | ||||||
|  |  | ||||||
| project(GPClient) |  | ||||||
|  |  | ||||||
| set(gpclient_GENERATED_SOURCES) |  | ||||||
|  |  | ||||||
| configure_file(com.yuezk.qt.gpclient.desktop.in com.yuezk.qt.gpclient.desktop) |  | ||||||
| configure_file(com.yuezk.qt.gpclient.metainfo.xml.in com.yuezk.qt.gpclient.metainfo.xml) |  | ||||||
|  |  | ||||||
| qt5_add_dbus_interface( |  | ||||||
|     gpclient_GENERATED_SOURCES |  | ||||||
|     ${CMAKE_BINARY_DIR}/com.yuezk.qt.GPService.xml |  | ||||||
|     gpserviceinterface |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| add_executable(gpclient |  | ||||||
|     cdpcommand.cpp |  | ||||||
|     cdpcommandmanager.cpp |  | ||||||
|     enhancedwebview.cpp |  | ||||||
|     gatewayauthenticator.cpp |  | ||||||
|     gatewayauthenticatorparams.cpp |  | ||||||
|     gpgateway.cpp |  | ||||||
|     gphelper.cpp |  | ||||||
|     loginparams.cpp |  | ||||||
|     main.cpp |  | ||||||
|     standardloginwindow.cpp |  | ||||||
|     portalauthenticator.cpp |  | ||||||
|     portalconfigresponse.cpp |  | ||||||
|     preloginresponse.cpp |  | ||||||
|     samlloginwindow.cpp |  | ||||||
|     gpclient.cpp |  | ||||||
|     settingsdialog.cpp |  | ||||||
|     gpclient.ui |  | ||||||
|     standardloginwindow.ui |  | ||||||
|     settingsdialog.ui |  | ||||||
|     challengedialog.h |  | ||||||
|     challengedialog.cpp |  | ||||||
|     challengedialog.ui |  | ||||||
|     vpn_dbus.cpp |  | ||||||
|     vpn_json.cpp |  | ||||||
|     resources.qrc |  | ||||||
|     ${gpclient_GENERATED_SOURCES} |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| add_3rdparty( |  | ||||||
|     SingleApplication |  | ||||||
|     GIT_REPOSITORY https://github.com/itay-grudev/SingleApplication.git |  | ||||||
|     GIT_TAG v3.3.0 |  | ||||||
|     CMAKE_ARGS |  | ||||||
|         -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} |  | ||||||
|         -DCMAKE_CXX_FLAGS_RELEASE=${CMAKE_CXX_FLAGS_RELEASE} |  | ||||||
|         -DCMAKE_FIND_ROOT_PATH=${CMAKE_FIND_ROOT_PATH} |  | ||||||
|         -DCMAKE_PREFIX_PATH=$ENV{CMAKE_PREFIX_PATH} |  | ||||||
|         -DQAPPLICATION_CLASS=QApplication |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| add_3rdparty( |  | ||||||
|     plog |  | ||||||
|     GIT_REPOSITORY https://github.com/SergiusTheBest/plog.git |  | ||||||
|     GIT_TAG master |  | ||||||
|     CMAKE_ARGS |  | ||||||
|         -DPLOG_BUILD_SAMPLES=OFF |  | ||||||
|         -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} |  | ||||||
|         -DCMAKE_CXX_FLAGS_RELEASE=${CMAKE_CXX_FLAGS_RELEASE} |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| ExternalProject_Get_Property(SingleApplication-${PROJECT_NAME} SOURCE_DIR BINARY_DIR) |  | ||||||
| set(SingleApplication_INCLUDE_DIR ${SOURCE_DIR}) |  | ||||||
| set(SingleApplication_LIBRARY ${BINARY_DIR}/libSingleApplication.a) |  | ||||||
|  |  | ||||||
| ExternalProject_Get_Property(plog-${PROJECT_NAME} SOURCE_DIR) |  | ||||||
| set(plog_INCLUDE_DIR "${SOURCE_DIR}/include") |  | ||||||
|  |  | ||||||
| add_dependencies(gpclient |  | ||||||
|     SingleApplication-${PROJECT_NAME} |  | ||||||
|     plog-${PROJECT_NAME} |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| target_include_directories(gpclient PRIVATE |  | ||||||
|     ${CMAKE_BINARY_DIR} |  | ||||||
|     ${CMAKE_CURRENT_SOURCE_DIR} |  | ||||||
|     ${CMAKE_CURRENT_BINARY_DIR} |  | ||||||
|     ${SingleApplication_INCLUDE_DIR} |  | ||||||
|     ${plog_INCLUDE_DIR} |  | ||||||
|     ${QTKEYCHAIN_INCLUDE_DIRS}/qt5keychain |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| target_link_libraries(gpclient |  | ||||||
|     ${SingleApplication_LIBRARY} |  | ||||||
|     Qt5::Widgets |  | ||||||
|     Qt5::Network |  | ||||||
|     Qt5::WebSockets |  | ||||||
|     Qt5::WebEngine |  | ||||||
|     Qt5::WebEngineWidgets |  | ||||||
|     Qt5::DBus |  | ||||||
|     QtSignals |  | ||||||
|     ${QTKEYCHAIN_LIBRARIES} |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 8.0 AND CMAKE_BUILD_TYPE STREQUAL Release) |  | ||||||
|     target_compile_options(gpclient PUBLIC "-ffile-prefix-map=${CMAKE_SOURCE_DIR}=.") |  | ||||||
| endif() |  | ||||||
|  |  | ||||||
| target_compile_definitions(gpclient PUBLIC QAPPLICATION_CLASS=QApplication) |  | ||||||
|  |  | ||||||
| install(TARGETS gpclient DESTINATION bin) |  | ||||||
| install(FILES "${CMAKE_CURRENT_BINARY_DIR}/com.yuezk.qt.gpclient.metainfo.xml" DESTINATION share/metainfo) |  | ||||||
| install(FILES "${CMAKE_CURRENT_BINARY_DIR}/com.yuezk.qt.gpclient.desktop" DESTINATION share/applications) |  | ||||||
| install(FILES "com.yuezk.qt.gpclient.svg" DESTINATION share/icons/hicolor/scalable/apps) |  | ||||||
| @@ -1,30 +0,0 @@ | |||||||
| #include <QtCore/QVariantMap> |  | ||||||
| #include <QtCore/QJsonDocument> |  | ||||||
| #include <QtCore/QJsonObject> |  | ||||||
|  |  | ||||||
| #include "cdpcommand.h" |  | ||||||
|  |  | ||||||
| CDPCommand::CDPCommand(QObject *parent) : QObject(parent) |  | ||||||
| { |  | ||||||
| } |  | ||||||
|  |  | ||||||
| CDPCommand::CDPCommand(int id, QString cmd, QVariantMap& params) : |  | ||||||
|     QObject(nullptr), |  | ||||||
|     id(id), |  | ||||||
|     cmd(cmd), |  | ||||||
|     params(¶ms) |  | ||||||
| { |  | ||||||
| } |  | ||||||
|  |  | ||||||
| QByteArray CDPCommand::toJson() |  | ||||||
| { |  | ||||||
|     QVariantMap payloadMap; |  | ||||||
|     payloadMap["id"] = id; |  | ||||||
|     payloadMap["method"] = cmd; |  | ||||||
|     payloadMap["params"] = *params; |  | ||||||
|  |  | ||||||
|     QJsonObject payloadJsonObject = QJsonObject::fromVariantMap(payloadMap); |  | ||||||
|     QJsonDocument payloadJson(payloadJsonObject); |  | ||||||
|  |  | ||||||
|     return payloadJson.toJson(); |  | ||||||
| } |  | ||||||
| @@ -1,24 +0,0 @@ | |||||||
| #ifndef CDPCOMMAND_H |  | ||||||
| #define CDPCOMMAND_H |  | ||||||
|  |  | ||||||
| #include <QtCore/QObject> |  | ||||||
|  |  | ||||||
| class CDPCommand : public QObject |  | ||||||
| { |  | ||||||
|     Q_OBJECT |  | ||||||
| public: |  | ||||||
|     explicit CDPCommand(QObject *parent = nullptr); |  | ||||||
|     CDPCommand(int id, QString cmd, QVariantMap& params); |  | ||||||
|  |  | ||||||
|     QByteArray toJson(); |  | ||||||
|  |  | ||||||
| signals: |  | ||||||
|     void finished(); |  | ||||||
|  |  | ||||||
| private: |  | ||||||
|     int id; |  | ||||||
|     QString cmd; |  | ||||||
|     QVariantMap *params; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| #endif // CDPCOMMAND_H |  | ||||||
| @@ -1,87 +0,0 @@ | |||||||
| #include <QtCore/QVariantMap> |  | ||||||
| #include <plog/Log.h> |  | ||||||
|  |  | ||||||
| #include "cdpcommandmanager.h" |  | ||||||
|  |  | ||||||
| CDPCommandManager::CDPCommandManager(QObject *parent) |  | ||||||
|     : QObject(parent) |  | ||||||
|     , networkManager(new QNetworkAccessManager) |  | ||||||
|     , socket(new QWebSocket) |  | ||||||
| { |  | ||||||
|     // WebSocket setup |  | ||||||
|     QObject::connect(socket, &QWebSocket::connected, this, &CDPCommandManager::ready); |  | ||||||
|     QObject::connect(socket, &QWebSocket::textMessageReceived, this, &CDPCommandManager::onTextMessageReceived); |  | ||||||
|     QObject::connect(socket, &QWebSocket::disconnected, this, &CDPCommandManager::onSocketDisconnected); |  | ||||||
|     QObject::connect(socket, QOverload<QAbstractSocket::SocketError>::of(&QWebSocket::error), this, &CDPCommandManager::onSocketError); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| CDPCommandManager::~CDPCommandManager() |  | ||||||
| { |  | ||||||
|     delete networkManager; |  | ||||||
|     delete socket; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void CDPCommandManager::initialize(QString endpoint) |  | ||||||
| { |  | ||||||
|     QNetworkReply *reply = networkManager->get(QNetworkRequest(endpoint)); |  | ||||||
|  |  | ||||||
|     QObject::connect( |  | ||||||
|         reply, &QNetworkReply::finished, |  | ||||||
|         [reply, this]() { |  | ||||||
|             if (reply->error()) { |  | ||||||
|                 LOGE << "CDP request error"; |  | ||||||
|                 return; |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             QJsonDocument doc = QJsonDocument::fromJson(reply->readAll()); |  | ||||||
|             QJsonArray pages = doc.array(); |  | ||||||
|             QJsonObject page = pages.first().toObject(); |  | ||||||
|             QString wsUrl = page.value("webSocketDebuggerUrl").toString(); |  | ||||||
|  |  | ||||||
|             socket->open(wsUrl); |  | ||||||
|         } |  | ||||||
|     ); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| CDPCommand *CDPCommandManager::sendCommand(QString cmd) |  | ||||||
| { |  | ||||||
|     QVariantMap emptyParams; |  | ||||||
|     return sendCommend(cmd, emptyParams); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| CDPCommand *CDPCommandManager::sendCommend(QString cmd, QVariantMap ¶ms) |  | ||||||
| { |  | ||||||
|     int id = ++commandId; |  | ||||||
|     CDPCommand *command = new CDPCommand(id, cmd, params); |  | ||||||
|     socket->sendTextMessage(command->toJson()); |  | ||||||
|     commandPool.insert(id, command); |  | ||||||
|  |  | ||||||
|     return command; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void CDPCommandManager::onTextMessageReceived(QString message) |  | ||||||
| { |  | ||||||
|     QJsonDocument responseDoc = QJsonDocument::fromJson(message.toUtf8()); |  | ||||||
|     QJsonObject response = responseDoc.object(); |  | ||||||
|  |  | ||||||
|     // Response for method |  | ||||||
|     if (response.contains("id")) { |  | ||||||
|         int id = response.value("id").toInt(); |  | ||||||
|         if (commandPool.contains(id)) { |  | ||||||
|             CDPCommand *command = commandPool.take(id); |  | ||||||
|             command->finished(); |  | ||||||
|         } |  | ||||||
|     } else { // Response for event |  | ||||||
|         emit eventReceived(response.value("method").toString(), response.value("params").toObject()); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void CDPCommandManager::onSocketDisconnected() |  | ||||||
| { |  | ||||||
|     LOGI << "WebSocket disconnected"; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void CDPCommandManager::onSocketError(QAbstractSocket::SocketError error) |  | ||||||
| { |  | ||||||
|     LOGE << "WebSocket error" << error; |  | ||||||
| } |  | ||||||
| @@ -1,40 +0,0 @@ | |||||||
| #ifndef CDPCOMMANDMANAGER_H |  | ||||||
| #define CDPCOMMANDMANAGER_H |  | ||||||
|  |  | ||||||
| #include <QtCore/QObject> |  | ||||||
| #include <QtCore/QHash> |  | ||||||
| #include <QtWebSockets/QtWebSockets> |  | ||||||
| #include <QtNetwork/QNetworkAccessManager> |  | ||||||
|  |  | ||||||
| #include "cdpcommand.h" |  | ||||||
|  |  | ||||||
| class CDPCommandManager : public QObject |  | ||||||
| { |  | ||||||
|     Q_OBJECT |  | ||||||
| public: |  | ||||||
|     explicit CDPCommandManager(QObject *parent = nullptr); |  | ||||||
|     ~CDPCommandManager(); |  | ||||||
|  |  | ||||||
|     void initialize(QString endpoint); |  | ||||||
|  |  | ||||||
|     CDPCommand *sendCommand(QString cmd); |  | ||||||
|     CDPCommand *sendCommend(QString cmd, QVariantMap& params); |  | ||||||
|  |  | ||||||
| signals: |  | ||||||
|     void ready(); |  | ||||||
|     void eventReceived(QString eventName, QJsonObject params); |  | ||||||
|  |  | ||||||
| private: |  | ||||||
|     QNetworkAccessManager *networkManager; |  | ||||||
|     QWebSocket *socket; |  | ||||||
|  |  | ||||||
|     int commandId = 0; |  | ||||||
|     QHash<int, CDPCommand*> commandPool; |  | ||||||
|  |  | ||||||
| private slots: |  | ||||||
|     void onTextMessageReceived(QString message); |  | ||||||
|     void onSocketDisconnected(); |  | ||||||
|     void onSocketError(QAbstractSocket::SocketError error); |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| #endif // CDPCOMMANDMANAGER_H |  | ||||||
| @@ -1,38 +0,0 @@ | |||||||
| #include <QtWidgets/QDialogButtonBox> |  | ||||||
| #include <QtWidgets/QPushButton> |  | ||||||
|  |  | ||||||
| #include "challengedialog.h" |  | ||||||
| #include "ui_challengedialog.h" |  | ||||||
|  |  | ||||||
| ChallengeDialog::ChallengeDialog(QWidget *parent) : |  | ||||||
|     QDialog(parent), |  | ||||||
|     ui(new Ui::ChallengeDialog) |  | ||||||
| { |  | ||||||
|     ui->setupUi(this); |  | ||||||
|     ui->buttonBox->button(QDialogButtonBox::Ok)->setDisabled(true); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| ChallengeDialog::~ChallengeDialog() |  | ||||||
| { |  | ||||||
|     delete ui; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void ChallengeDialog::setMessage(const QString &message) |  | ||||||
| { |  | ||||||
|     ui->challengeMessage->setText(message); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| const QString ChallengeDialog::getChallenge() |  | ||||||
| { |  | ||||||
|     return ui->challengeInput->text(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void ChallengeDialog::on_challengeInput_textChanged(const QString &value) |  | ||||||
| { |  | ||||||
|     QPushButton *okBtn = ui->buttonBox->button(QDialogButtonBox::Ok); |  | ||||||
|     if (value.isEmpty()) { |  | ||||||
|         okBtn->setDisabled(true); |  | ||||||
|     } else { |  | ||||||
|         okBtn->setEnabled(true); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,28 +0,0 @@ | |||||||
| #ifndef CHALLENGEDIALOG_H |  | ||||||
| #define CHALLENGEDIALOG_H |  | ||||||
|  |  | ||||||
| #include <QDialog> |  | ||||||
|  |  | ||||||
| namespace Ui { |  | ||||||
| class ChallengeDialog; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| class ChallengeDialog : public QDialog |  | ||||||
| { |  | ||||||
|     Q_OBJECT |  | ||||||
|  |  | ||||||
| public: |  | ||||||
|     explicit ChallengeDialog(QWidget *parent = nullptr); |  | ||||||
|     ~ChallengeDialog(); |  | ||||||
|  |  | ||||||
|     void setMessage(const QString &message); |  | ||||||
|     const QString getChallenge(); |  | ||||||
|  |  | ||||||
| private slots: |  | ||||||
|     void on_challengeInput_textChanged(const QString &arg1); |  | ||||||
|  |  | ||||||
| private: |  | ||||||
|     Ui::ChallengeDialog *ui; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| #endif // CHALLENGEDIALOG_H |  | ||||||
| @@ -1,111 +0,0 @@ | |||||||
| <?xml version="1.0" encoding="UTF-8"?> |  | ||||||
| <ui version="4.0"> |  | ||||||
|  <class>ChallengeDialog</class> |  | ||||||
|  <widget class="QDialog" name="ChallengeDialog"> |  | ||||||
|   <property name="geometry"> |  | ||||||
|    <rect> |  | ||||||
|     <x>0</x> |  | ||||||
|     <y>0</y> |  | ||||||
|     <width>405</width> |  | ||||||
|     <height>200</height> |  | ||||||
|    </rect> |  | ||||||
|   </property> |  | ||||||
|   <property name="windowTitle"> |  | ||||||
|    <string>GlobalProtect Challenge</string> |  | ||||||
|   </property> |  | ||||||
|   <property name="modal"> |  | ||||||
|    <bool>true</bool> |  | ||||||
|   </property> |  | ||||||
|   <layout class="QVBoxLayout" name="verticalLayout"> |  | ||||||
|    <item> |  | ||||||
|     <layout class="QVBoxLayout" name="verticalLayout_2" stretch="1,1"> |  | ||||||
|      <item> |  | ||||||
|       <widget class="QLabel" name="label"> |  | ||||||
|        <property name="font"> |  | ||||||
|         <font> |  | ||||||
|          <pointsize>14</pointsize> |  | ||||||
|          <weight>50</weight> |  | ||||||
|          <bold>false</bold> |  | ||||||
|         </font> |  | ||||||
|        </property> |  | ||||||
|        <property name="text"> |  | ||||||
|         <string>Sign In</string> |  | ||||||
|        </property> |  | ||||||
|       </widget> |  | ||||||
|      </item> |  | ||||||
|      <item> |  | ||||||
|       <widget class="QLabel" name="challengeMessage"> |  | ||||||
|        <property name="text"> |  | ||||||
|         <string>Duo two-factor login for [redacted]  Enter a passcode or select one of the following options:   1. Duo Push to XXX-XXX-[redacted]  2. SMS passcodes to XXX-XXX-[redacted]  Passcode or option (1-2): </string> |  | ||||||
|        </property> |  | ||||||
|        <property name="alignment"> |  | ||||||
|         <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set> |  | ||||||
|        </property> |  | ||||||
|        <property name="wordWrap"> |  | ||||||
|         <bool>true</bool> |  | ||||||
|        </property> |  | ||||||
|       </widget> |  | ||||||
|      </item> |  | ||||||
|     </layout> |  | ||||||
|    </item> |  | ||||||
|    <item> |  | ||||||
|     <widget class="QLineEdit" name="challengeInput"> |  | ||||||
|      <property name="echoMode"> |  | ||||||
|       <enum>QLineEdit::Password</enum> |  | ||||||
|      </property> |  | ||||||
|     </widget> |  | ||||||
|    </item> |  | ||||||
|    <item> |  | ||||||
|     <widget class="QDialogButtonBox" name="buttonBox"> |  | ||||||
|      <property name="layoutDirection"> |  | ||||||
|       <enum>Qt::LeftToRight</enum> |  | ||||||
|      </property> |  | ||||||
|      <property name="orientation"> |  | ||||||
|       <enum>Qt::Horizontal</enum> |  | ||||||
|      </property> |  | ||||||
|      <property name="standardButtons"> |  | ||||||
|       <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> |  | ||||||
|      </property> |  | ||||||
|      <property name="centerButtons"> |  | ||||||
|       <bool>false</bool> |  | ||||||
|      </property> |  | ||||||
|     </widget> |  | ||||||
|    </item> |  | ||||||
|   </layout> |  | ||||||
|  </widget> |  | ||||||
|  <resources/> |  | ||||||
|  <connections> |  | ||||||
|   <connection> |  | ||||||
|    <sender>buttonBox</sender> |  | ||||||
|    <signal>accepted()</signal> |  | ||||||
|    <receiver>ChallengeDialog</receiver> |  | ||||||
|    <slot>accept()</slot> |  | ||||||
|    <hints> |  | ||||||
|     <hint type="sourcelabel"> |  | ||||||
|      <x>248</x> |  | ||||||
|      <y>254</y> |  | ||||||
|     </hint> |  | ||||||
|     <hint type="destinationlabel"> |  | ||||||
|      <x>157</x> |  | ||||||
|      <y>274</y> |  | ||||||
|     </hint> |  | ||||||
|    </hints> |  | ||||||
|   </connection> |  | ||||||
|   <connection> |  | ||||||
|    <sender>buttonBox</sender> |  | ||||||
|    <signal>rejected()</signal> |  | ||||||
|    <receiver>ChallengeDialog</receiver> |  | ||||||
|    <slot>reject()</slot> |  | ||||||
|    <hints> |  | ||||||
|     <hint type="sourcelabel"> |  | ||||||
|      <x>316</x> |  | ||||||
|      <y>260</y> |  | ||||||
|     </hint> |  | ||||||
|     <hint type="destinationlabel"> |  | ||||||
|      <x>286</x> |  | ||||||
|      <y>274</y> |  | ||||||
|     </hint> |  | ||||||
|    </hints> |  | ||||||
|   </connection> |  | ||||||
|  </connections> |  | ||||||
| </ui> |  | ||||||
| @@ -1,12 +0,0 @@ | |||||||
| [Desktop Entry] |  | ||||||
|  |  | ||||||
| Type=Application |  | ||||||
| Version=1.0 |  | ||||||
| Name=GlobalProtect VPN |  | ||||||
| Comment=A GlobalProtect VPN client (GUI) for Linux based on OpenConnect and built with Qt5, supports SAML auth mode. |  | ||||||
| GenericName=GlobalProtect VPN client, supports SAML auth mode |  | ||||||
| Categories=Network;Dialup; |  | ||||||
| Exec=env QT_AUTO_SCREEN_SCALE_FACTOR=1 @CMAKE_INSTALL_PREFIX@/bin/gpclient |  | ||||||
| Icon=com.yuezk.qt.gpclient |  | ||||||
| Keywords=GlobalProtect;Openconnect;SAML;connection;VPN; |  | ||||||
| StartupWMClass=gpclient |  | ||||||
| @@ -1,43 +0,0 @@ | |||||||
| <?xml version="1.0" encoding="UTF-8"?> |  | ||||||
| <component type="desktop-application"> |  | ||||||
|     <id>com.yuezk.qt.gpclient</id> |  | ||||||
|  |  | ||||||
|     <name>globalprotect-openconnect</name> |  | ||||||
|     <summary>A GlobalProtect VPN client powered by OpenConnect</summary> |  | ||||||
|  |  | ||||||
|     <metadata_license>CC0-1.0</metadata_license> |  | ||||||
|     <project_license>AGPL-3.0-or-later</project_license> |  | ||||||
|  |  | ||||||
|     <description> |  | ||||||
|         <p>A GlobalProtect VPN client (GUI) for Linux based on OpenConnect and built with Qt5, supports the SAML auth mode.</p> |  | ||||||
|     </description> |  | ||||||
|  |  | ||||||
|     <categories> |  | ||||||
|         <category>Network</category> |  | ||||||
|     </categories> |  | ||||||
|  |  | ||||||
|     <update_contact>k3vinyue_AT_gmail.com</update_contact> |  | ||||||
|     <developer_name>Kevin Yue</developer_name> |  | ||||||
|  |  | ||||||
|     <url type="homepage">https://github.com/yuezk/GlobalProtect-openconnect</url> |  | ||||||
|     <url type="bugtracker">https://github.com/yuezk/GlobalProtect-openconnect/issues</url> |  | ||||||
|     <url type="help">https://github.com/yuezk/GlobalProtect-openconnect/issues</url> |  | ||||||
|  |  | ||||||
|     <keywords> |  | ||||||
|         <keyword>globalprotect</keyword> |  | ||||||
|         <keyword>openconnect</keyword> |  | ||||||
|         <keyword>vpn</keyword> |  | ||||||
|         <keyword>saml</keyword> |  | ||||||
|     </keywords> |  | ||||||
|  |  | ||||||
|     <launchable type="desktop-id">com.yuezk.qt.gpclient.desktop</launchable> |  | ||||||
|     <screenshots> |  | ||||||
|         <screenshot type="default"> |  | ||||||
|             <image>https://user-images.githubusercontent.com/3297602/133869036-5c02b0d9-c2d9-4f87-8c81-e44f68cfd6ac.png</image> |  | ||||||
|         </screenshot> |  | ||||||
|     </screenshots> |  | ||||||
|     <provides> |  | ||||||
|         <binary>@CMAKE_INSTALL_PREFIX@/bin/gpclient</binary> |  | ||||||
|         <dbus type="system">com.yuezk.qt.GPService</dbus> |  | ||||||
|     </provides> |  | ||||||
| </component> |  | ||||||
| @@ -1,99 +0,0 @@ | |||||||
| <?xml version="1.0" encoding="UTF-8" standalone="no"?> |  | ||||||
| <!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  --> |  | ||||||
|  |  | ||||||
| <svg |  | ||||||
|    xmlns:dc="http://purl.org/dc/elements/1.1/" |  | ||||||
|    xmlns:cc="http://creativecommons.org/ns#" |  | ||||||
|    xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" |  | ||||||
|    xmlns:svg="http://www.w3.org/2000/svg" |  | ||||||
|    xmlns="http://www.w3.org/2000/svg" |  | ||||||
|    xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" |  | ||||||
|    xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" |  | ||||||
|    version="1.1" |  | ||||||
|    id="Layer_1" |  | ||||||
|    x="0px" |  | ||||||
|    y="0px" |  | ||||||
|    viewBox="0 0 96 96" |  | ||||||
|    style="enable-background:new 0 0 96 96;" |  | ||||||
|    xml:space="preserve" |  | ||||||
|    sodipodi:docname="com.yuezk.qt.gpclient.svg" |  | ||||||
|    inkscape:version="0.92.4 5da689c313, 2019-01-14"><metadata |  | ||||||
|    id="metadata14"><rdf:RDF><cc:Work |  | ||||||
|        rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type |  | ||||||
|          rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><defs |  | ||||||
|    id="defs12" /><sodipodi:namedview |  | ||||||
|    pagecolor="#ffffff" |  | ||||||
|    bordercolor="#666666" |  | ||||||
|    borderopacity="1" |  | ||||||
|    objecttolerance="10" |  | ||||||
|    gridtolerance="10" |  | ||||||
|    guidetolerance="10" |  | ||||||
|    inkscape:pageopacity="0" |  | ||||||
|    inkscape:pageshadow="2" |  | ||||||
|    inkscape:window-width="1920" |  | ||||||
|    inkscape:window-height="1006" |  | ||||||
|    id="namedview10" |  | ||||||
|    showgrid="false" |  | ||||||
|    inkscape:zoom="6.9532168" |  | ||||||
|    inkscape:cx="7.9545315" |  | ||||||
|    inkscape:cy="59.062386" |  | ||||||
|    inkscape:window-x="0" |  | ||||||
|    inkscape:window-y="0" |  | ||||||
|    inkscape:window-maximized="1" |  | ||||||
|    inkscape:current-layer="g8499" /> |  | ||||||
| <style |  | ||||||
|    type="text/css" |  | ||||||
|    id="style2"> |  | ||||||
| 	.st0{fill:#2980B9;} |  | ||||||
| 	.st1{fill:#3498DB;} |  | ||||||
| 	.st2{fill:#2ECC71;} |  | ||||||
| 	.st3{fill:#27AE60;} |  | ||||||
| </style> |  | ||||||
|  |  | ||||||
| <g |  | ||||||
|    id="g8499" |  | ||||||
|    transform="matrix(1.3407388,0,0,1.3407388,-16.409202,-16.355463)"><g |  | ||||||
|      id="XMLID_1_"> |  | ||||||
| 	<circle |  | ||||||
|    r="32.5" |  | ||||||
|    cy="48" |  | ||||||
|    cx="48" |  | ||||||
|    class="st0" |  | ||||||
|    id="XMLID_3_" |  | ||||||
|    style="fill:#2980b9" /> |  | ||||||
| 	<path |  | ||||||
|    d="m 48,15.5 v 65 C 65.9,80.5 80.5,65.7 80.5,48 80.5,30 65.9,15.5 48,15.5 Z" |  | ||||||
|    class="st1" |  | ||||||
|    id="XMLID_4_" |  | ||||||
|    inkscape:connector-curvature="0" |  | ||||||
|    style="fill:#3498db" /> |  | ||||||
| 	<path |  | ||||||
|    d="m 48,15.5 v 0.6 l 1.2,-0.3 c 0.3,-0.3 0.4,-0.3 0.6,-0.3 h -1.1 z m 7.3,0.9 c -0.1,0 0.4,0.9 1.1,1.8 0.8,1.5 1.1,2.1 1.3,2.1 0.3,-0.3 1.9,-1.2 3,-2.1 -1.7,-0.9 -3.5,-1.5 -5.4,-1.8 z m 10.3,6.2 c -0.1,0 -0.4,0 -0.9,0.6 l -0.8,0.9 0.6,0.6 c 0.3,0.6 0.8,0.9 1,1.2 0.5,0.6 0.6,0.6 0.1,1.5 -0.2,0.6 -0.3,0.9 -0.3,0.9 0.1,0.3 0.3,0.3 1.4,0.3 h 1.6 c 0.1,0 0.3,-0.6 0.4,-1.2 l 0.1,-0.9 -1.1,-0.9 c -1,-0.9 -1,-0.9 -1.4,-1.8 -0.3,-0.6 -0.6,-1.2 -0.7,-1.2 z m -3,2.4 c -0.2,0 -1.3,2.1 -1.3,2.4 0,0 0.3,0.6 0.7,0.9 0.4,0.3 0.7,0.6 0.7,0.6 0.1,0 1.2,-1.2 1.4,-1.5 C 64.2,27.1 64,26.8 63.5,26.2 63.1,25.5 62.7,25 62.6,25 Z m 9.5,1.1 0.2,0.3 c 0,0.3 -0.7,0.9 -1.4,1.5 -1.2,0.9 -1.4,1.2 -2,1.2 -0.6,0 -0.9,0.3 -1.8,0.9 -0.6,0.6 -1.2,0.9 -1.2,1.2 0,0 0.2,0.3 0.6,0.9 0.7,0.6 0.7,0.9 0.2,1.8 l -0.4,0.3 h -1.1 c -0.6,0 -1.5,0 -1.8,-0.3 -0.9,0 -0.8,0 -0.1,2.1 1,3 1.1,3.2 1.3,3.2 0.1,0 1.3,-1.2 2.8,-2.4 1.5,-1.2 2.7,-2.4 2.8,-2.4 l 0.6,0.3 c 0.4,0.3 0.5,0 1.3,-0.6 l 0.8,-0.6 0.8,0.6 c 1.9,1.2 2.2,1.5 2.3,2.4 0.2,1.5 0.3,1.8 0.5,1.8 0.1,0 1.3,-1.5 1.6,-1.8 0.1,-0.3 -0.1,-0.6 -1.1,-2.1 -0.7,-0.9 -1.1,-1.8 -1.1,-2.1 0,0 0.1,0 0.3,-0.3 0.2,0 0.4,0.3 1,0.9 -1.6,-2.3 -3.2,-4.7 -5.1,-6.8 z m 2.8,10.7 c -0.2,0 -0.9,0.9 -0.8,1.2 l 0.5,0.3 H 75 c 0.2,0 0.3,0 0.2,-0.3 C 75.1,37.4 75,36.8 74.9,36.8 Z M 72.3,38 h -2.4 l -2.4,0.3 -4.5,3.5 -4.4,3.8 v 3.5 c 0,2.1 0,3.8 0.1,3.8 0.1,0 0.7,0.9 1.5,1.5 0.8,0.9 1.5,1.5 1.8,1.8 0.4,0.3 0.5,0.3 4,0.6 l 3.4,0.3 1.6,0.9 c 0.8,0.6 1.5,1.2 1.6,1.2 0.1,0 -0.3,0.3 -0.6,0.6 l -0.6,0.6 1,1.2 c 0.5,0.6 1.3,1.5 1.7,1.8 l 0.6,0.9 v 1.7 0.9 c 3.7,-5 5.9,-11.5 6.1,-18.3 0.1,-2.7 -0.3,-5.3 -0.8,-8 l -0.6,-0.3 c -0.1,0 -0.5,0.3 -1,0.6 -0.5,0.3 -1,0.9 -1.1,0.9 -0.1,0 -0.8,-0.3 -1.8,-0.6 l -1.8,-0.6 v -0.9 c 0,-0.6 0,-0.9 -0.6,-1.5 z M 48,63.7 V 64 h 0.2 z" |  | ||||||
|    class="st2" |  | ||||||
|    id="XMLID_13_" |  | ||||||
|    inkscape:connector-curvature="0" |  | ||||||
|    style="fill:#2ecc71" /> |  | ||||||
| 	<path |  | ||||||
|    d="m 48,15.5 c -3.1,0 -6.2,0.5 -9,1.3 0.3,0.4 0.3,0.4 0.6,0.9 1.5,2.5 1.7,2.8 2.1,2.9 0.3,0 0.9,0.1 1.6,0.1 h 1.2 l 0.9,-2 0.8,-1.9 1.8,-0.6 z m -16.9,4.7 c -2.8,1.7 -5.4,3.9 -7.6,6.4 -3.8,4.3 -6.3,9.6 -7.4,15.4 0.5,0 0.9,-0.1 1.8,-0.1 2.8,0.1 2.5,0 3.4,1.4 0.5,0.8 0.6,0.8 1.4,0.8 1,0.1 0.9,0 0.5,-1.6 -0.2,-0.6 -0.3,-1.2 -0.3,-1.4 0,-0.2 0.5,-0.7 1.7,-1.6 1.9,-1.5 1.8,-1.3 1.5,-2.9 -0.1,-0.3 0.1,-0.6 0.6,-1.2 0.7,-0.7 0.7,-0.6 1.4,-0.6 h 0.7 l 0.1,-1.2 c 0.1,-0.7 0.1,-1.3 0.2,-1.3 0,0 1.9,-1.1 4.1,-2.3 2.2,-1.2 4.1,-2.2 4.2,-2.3 0.2,-0.2 -0.3,-0.8 -2.7,-3.8 -1.5,-1.9 -2.8,-3.6 -2.9,-3.7 z m -5.8,23 c -0.1,0 -0.1,0.3 -0.1,0.6 0,0.6 0,0.7 0.6,1 0.8,0.4 0.9,0.5 0.8,0.2 -0.1,-0.4 -1.2,-1.9 -1.3,-1.8 z m -3.4,2.1 -0.5,1.8 c 0.1,0.1 0.9,0.3 1.8,0.5 1,0.2 1.6,0.4 1.8,0.3 l 0.5,-1.3 z m -3.8,1 -1.1,0.6 c -0.6,0.3 -1.2,0.6 -1.4,0.6 h -0.1 c 0,1.4 0.1,2.8 0.3,4.2 l 0.6,0.4 1,-0.1 h 1 l 0.6,1.4 c 0.3,0.7 0.7,1.4 0.8,1.5 0.1,0.1 1,0.1 1.8,0.1 h 1.5 L 23,56.2 c 0,1.2 0,1.3 -0.6,2.2 -0.4,0.5 -0.6,1.2 -0.6,1.4 0,0.2 0.7,2.1 1.6,4.3 l 1.5,4 1.6,0.8 c 1.2,0.6 1.5,0.8 1.5,1 0,0.1 -0.4,2.1 -0.6,3.1 3,2.5 6.4,4.5 10.2,5.8 3.5,-3.6 6.8,-7.1 7.3,-7.6 l 0.7,-0.7 0.2,-1.9 c 0.2,-1.1 0.4,-2.1 0.4,-2.2 0,-0.1 0.5,-0.6 1,-1.2 0.5,-0.5 0.8,-1 0.8,-1.1 v -0.2 c -0.1,-0.1 -1.4,-1.1 -3,-2.2 l -3.1,-2.1 -1.1,-0.1 c -0.8,0 -1.2,0 -1.3,-0.2 C 39.4,59.2 39.2,58.5 39.1,57.7 39,56.9 38.9,56.2 38.8,56.1 38.8,56 38,56 37.1,56 36.2,56 35.4,55.9 35.3,55.8 35.2,55.7 35.2,55.1 35.1,54.3 35,53.6 34.9,53 34.8,52.9 34.7,52.8 33.7,52.7 32.5,52.6 30.5,52.5 30.1,52.5 29.1,52 l -1.2,-0.6 -1.6,0.7 -1.7,0.9 -1.8,-0.1 c -2,0 -1.9,0.2 -2.1,-1.6 C 20.6,50.7 20.6,50.1 20.5,50.1 20.4,50 20,50 19.6,49.9 L 18.9,49.7 19,49.2 c 0,-0.3 0,-1 0.1,-1.4 L 19.2,47 18.7,46.5 Z m 9.1,1.1 C 27.1,47.5 27.1,47.8 27,48 l -0.1,0.5 2.9,1.2 c 2.9,1.1 3.4,1.2 3.9,0.7 0.2,-0.2 0.1,-0.2 -0.3,-0.4 -0.3,-0.1 -1.7,-0.9 -3.2,-1.6 -1.7,-0.7 -2.9,-1.1 -3,-1 z" |  | ||||||
|    class="st3" |  | ||||||
|    id="XMLID_20_" |  | ||||||
|    inkscape:connector-curvature="0" |  | ||||||
|    style="fill:#27ae60" /> |  | ||||||
| </g><g |  | ||||||
|      transform="matrix(1.458069,0,0,1.458069,-22.631538,-19.615144)" |  | ||||||
|      id="g7664"><path |  | ||||||
|        inkscape:connector-curvature="0" |  | ||||||
|        id="XMLID_6_" |  | ||||||
|        class="st3" |  | ||||||
|        d="m 38.8,56.1 c 0,1.2 1,2.2 2.2,2.2 h 15.2 c 1.2,0 2.2,-1 2.2,-2.2 V 45.3 c 0,-1.2 -1,-2.2 -2.2,-2.2 H 40.9 c -1.2,0 -2.2,1 -2.2,2.2 v 10.8 z" |  | ||||||
|        style="fill:#f1aa27;fill-opacity:1" /><path |  | ||||||
|        style="fill:#e6e6e6" |  | ||||||
|        inkscape:connector-curvature="0" |  | ||||||
|        id="XMLID_7_" |  | ||||||
|        class="st4" |  | ||||||
|        d="m 55.5,43.1 h -3.3 v -3.7 c 0,-2.1 -1.7,-3.8 -3.8,-3.8 -2.1,0 -3.8,1.7 -3.8,3.8 v 3.8 h -3.1 v -3.8 c 0,-3.9 3.2,-7 7,-7 3.9,0 7,3.2 7,7 z" /><path |  | ||||||
|        style="fill:#e6e6e6;fill-opacity:1" |  | ||||||
|        inkscape:connector-curvature="0" |  | ||||||
|        id="XMLID_8_" |  | ||||||
|        class="st5" |  | ||||||
|        d="m 50.35,48.2 c 0,-1 -0.8,-1.8 -1.8,-1.8 -1,0 -1.8,0.8 -1.8,1.8 0,0.7 0.4,1.3 1,1.6 l -1,5.2 h 3.6 l -1,-5.2 c 0.6,-0.3 1,-0.9 1,-1.6 z" /></g></g></svg> |  | ||||||
| Before Width: | Height: | Size: 6.7 KiB | 
| Before Width: | Height: | Size: 18 KiB | 
| @@ -1,31 +0,0 @@ | |||||||
| #include <QtCore/QProcessEnvironment> |  | ||||||
| #include <QtWebEngineWidgets/QWebEngineView> |  | ||||||
|  |  | ||||||
| #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() |  | ||||||
| { |  | ||||||
|     auto port = QProcessEnvironment::systemEnvironment().value(ENV_CDP_PORT); |  | ||||||
|     cdp->initialize("http://127.0.0.1:" + port + "/json"); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void EnhancedWebView::onCDPReady() |  | ||||||
| { |  | ||||||
|     cdp->sendCommand("Network.enable"); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void EnhancedWebView::onEventReceived(QString eventName, QJsonObject params) |  | ||||||
| { |  | ||||||
|     if (eventName == "Network.responseReceived") { |  | ||||||
|         emit responseReceived(params); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,29 +0,0 @@ | |||||||
| #ifndef ENHANCEDWEBVIEW_H |  | ||||||
| #define ENHANCEDWEBVIEW_H |  | ||||||
|  |  | ||||||
| #include <QtWebEngineWidgets/QWebEngineView> |  | ||||||
|  |  | ||||||
| #include "cdpcommandmanager.h" |  | ||||||
|  |  | ||||||
| #define ENV_CDP_PORT "QTWEBENGINE_REMOTE_DEBUGGING" |  | ||||||
|  |  | ||||||
| class EnhancedWebView : public QWebEngineView |  | ||||||
| { |  | ||||||
|     Q_OBJECT |  | ||||||
| public: |  | ||||||
|     explicit EnhancedWebView(QWidget *parent = nullptr); |  | ||||||
|  |  | ||||||
|     void initialize(); |  | ||||||
|  |  | ||||||
| signals: |  | ||||||
|     void responseReceived(QJsonObject params); |  | ||||||
|  |  | ||||||
| private slots: |  | ||||||
|     void onCDPReady(); |  | ||||||
|     void onEventReceived(QString eventName, QJsonObject params); |  | ||||||
|  |  | ||||||
| private: |  | ||||||
|     CDPCommandManager *cdp { nullptr }; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| #endif // ENHANCEDWEBVIEW_H |  | ||||||
| @@ -1,226 +0,0 @@ | |||||||
| #include <QtNetwork/QNetworkReply> |  | ||||||
| #include <QtCore/QRegularExpression> |  | ||||||
| #include <QtCore/QRegularExpressionMatch> |  | ||||||
| #include <plog/Log.h> |  | ||||||
|  |  | ||||||
| #include "gatewayauthenticator.h" |  | ||||||
| #include "gphelper.h" |  | ||||||
| #include "loginparams.h" |  | ||||||
| #include "preloginresponse.h" |  | ||||||
| #include "challengedialog.h" |  | ||||||
|  |  | ||||||
| using namespace gpclient::helper; |  | ||||||
|  |  | ||||||
| GatewayAuthenticator::GatewayAuthenticator(const QString& gateway, GatewayAuthenticatorParams params) |  | ||||||
|     : QObject() |  | ||||||
|     , gateway(gateway) |  | ||||||
|     , params(params) |  | ||||||
|     , preloginUrl("https://" + gateway + "/ssl-vpn/prelogin.esp?tmp=tmp&kerberos-support=yes&ipv6-support=yes&clientVer=4100") |  | ||||||
|     , loginUrl("https://" + gateway + "/ssl-vpn/login.esp") |  | ||||||
| { |  | ||||||
|     if (!params.clientos().isEmpty()) { |  | ||||||
|         preloginUrl = preloginUrl + "&clientos=" + params.clientos(); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void GatewayAuthenticator::authenticate() |  | ||||||
| { |  | ||||||
|     LOGI << "Start gateway authentication..."; |  | ||||||
|  |  | ||||||
|     LoginParams loginParams { params.clientos() }; |  | ||||||
|     loginParams.setUser(params.username()); |  | ||||||
|     loginParams.setPassword(params.password()); |  | ||||||
|     loginParams.setUserAuthCookie(params.userAuthCookie()); |  | ||||||
|     loginParams.setInputStr(params.inputStr()); |  | ||||||
|  |  | ||||||
|     login(loginParams); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void GatewayAuthenticator::login(const LoginParams &loginParams) |  | ||||||
| { |  | ||||||
|     LOGI << QString("Trying to login the gateway at %1, with %2").arg(loginUrl).arg(QString(loginParams.toUtf8())); |  | ||||||
|  |  | ||||||
|     auto *reply = createRequest(loginUrl, loginParams.toUtf8()); |  | ||||||
|     connect(reply, &QNetworkReply::finished, this, &GatewayAuthenticator::onLoginFinished); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void GatewayAuthenticator::onLoginFinished() |  | ||||||
| { |  | ||||||
|     QNetworkReply *reply = qobject_cast<QNetworkReply*>(sender()); |  | ||||||
|     QByteArray response = reply->readAll(); |  | ||||||
|  |  | ||||||
|     if (reply->error() || response.contains("Authentication failure")) { |  | ||||||
|         LOGE << QString("Failed to login the gateway at %1, %2").arg(loginUrl, reply->errorString()); |  | ||||||
|  |  | ||||||
|         if (standardLoginWindow) { |  | ||||||
|             standardLoginWindow->setProcessing(false); |  | ||||||
|             openMessageBox("Gateway login failed.", "Please check your credentials and try again."); |  | ||||||
|         } else { |  | ||||||
|             doAuth(); |  | ||||||
|         } |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // 2FA |  | ||||||
|     if (response.contains("Challenge")) { |  | ||||||
|         LOGI << "The server need input the challenge..."; |  | ||||||
|         showChallenge(response); |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     if (standardLoginWindow) { |  | ||||||
|         standardLoginWindow->close(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     const auto params = gpclient::helper::parseGatewayResponse(response); |  | ||||||
|     emit success(params.toString()); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void GatewayAuthenticator::doAuth() |  | ||||||
| { |  | ||||||
|     LOGI << "Perform the gateway prelogin at " << preloginUrl; |  | ||||||
|  |  | ||||||
|     auto *reply = createRequest(preloginUrl); |  | ||||||
|     connect(reply, &QNetworkReply::finished, this, &GatewayAuthenticator::onPreloginFinished); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void GatewayAuthenticator::onPreloginFinished() |  | ||||||
| { |  | ||||||
|     auto *reply = qobject_cast<QNetworkReply*>(sender()); |  | ||||||
|  |  | ||||||
|     if (reply->error()) { |  | ||||||
|         LOGE << QString("Failed to prelogin the gateway at %1, %2").arg(preloginUrl, reply->errorString()); |  | ||||||
|  |  | ||||||
|         emit fail("Error occurred on the gateway prelogin interface."); |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     LOGI << "Gateway prelogin succeeded."; |  | ||||||
|  |  | ||||||
|     auto response = PreloginResponse::parse(reply->readAll()); |  | ||||||
|  |  | ||||||
|     if (response.hasSamlAuthFields()) { |  | ||||||
|         samlAuth(response.samlMethod(), response.samlRequest(), reply->url().toString()); |  | ||||||
|     } else if (response.hasNormalAuthFields()) { |  | ||||||
|         normalAuth(response.labelUsername(), response.labelPassword(), response.authMessage()); |  | ||||||
|     } else { |  | ||||||
|         LOGE << QString("Unknown prelogin response for %1, got %2").arg(preloginUrl, QString::fromUtf8(response.rawResponse())); |  | ||||||
|         emit fail("Unknown response for gateway prelogin interface."); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     delete reply; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void GatewayAuthenticator::normalAuth(QString labelUsername, QString labelPassword, QString authMessage) |  | ||||||
| { |  | ||||||
|     LOGI << QString("Trying to perform the normal login with %1 / %2 credentials").arg(labelUsername, labelPassword); |  | ||||||
|  |  | ||||||
|     standardLoginWindow = new StandardLoginWindow {gateway, labelUsername, labelPassword, authMessage}; |  | ||||||
|  |  | ||||||
|     // Do login |  | ||||||
|     connect(standardLoginWindow, &StandardLoginWindow::performLogin, this, &GatewayAuthenticator::onPerformStandardLogin); |  | ||||||
|     connect(standardLoginWindow, &StandardLoginWindow::rejected, this, &GatewayAuthenticator::onLoginWindowRejected); |  | ||||||
|     connect(standardLoginWindow, &StandardLoginWindow::finished, this, &GatewayAuthenticator::onLoginWindowFinished); |  | ||||||
|  |  | ||||||
|     standardLoginWindow->show(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void GatewayAuthenticator::onPerformStandardLogin(const QString &username, const QString &password) |  | ||||||
| { |  | ||||||
|     LOGI << "Start to perform normal login..."; |  | ||||||
|  |  | ||||||
|     standardLoginWindow->setProcessing(true); |  | ||||||
|     params.setUsername(username); |  | ||||||
|     params.setPassword(password); |  | ||||||
|  |  | ||||||
|     authenticate(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void GatewayAuthenticator::onLoginWindowRejected() |  | ||||||
| { |  | ||||||
|     emit fail(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void GatewayAuthenticator::onLoginWindowFinished() |  | ||||||
| { |  | ||||||
|     delete standardLoginWindow; |  | ||||||
|     standardLoginWindow = nullptr; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void GatewayAuthenticator::samlAuth(QString samlMethod, QString samlRequest, QString preloginUrl) |  | ||||||
| { |  | ||||||
|     LOGI << "Trying to perform SAML login with saml-method " << samlMethod; |  | ||||||
|  |  | ||||||
|     auto *loginWindow = new SAMLLoginWindow; |  | ||||||
|  |  | ||||||
|     connect(loginWindow, &SAMLLoginWindow::success, [this, loginWindow](const QMap<QString, QString> &samlResult) { |  | ||||||
|         this->onSAMLLoginSuccess(samlResult); |  | ||||||
|         loginWindow->deleteLater(); |  | ||||||
|     }); |  | ||||||
|     connect(loginWindow, &SAMLLoginWindow::fail, [this, loginWindow](const QString &code, const QString &error) { |  | ||||||
|         this->onSAMLLoginFail(code, error); |  | ||||||
|         loginWindow->deleteLater(); |  | ||||||
|     }); |  | ||||||
|     connect(loginWindow, &SAMLLoginWindow::rejected, [this, loginWindow]() { |  | ||||||
|         this->onLoginWindowRejected(); |  | ||||||
|         loginWindow->deleteLater(); |  | ||||||
|     }); |  | ||||||
|  |  | ||||||
|     loginWindow->login(samlMethod, samlRequest, preloginUrl); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void GatewayAuthenticator::onSAMLLoginSuccess(const QMap<QString, QString> &samlResult) |  | ||||||
| { |  | ||||||
|     if (samlResult.contains("preloginCookie")) { |  | ||||||
|         LOGI << "SAML login succeeded, got the prelogin-cookie " << samlResult.value("preloginCookie"); |  | ||||||
|     } else { |  | ||||||
|         LOGI << "SAML login succeeded, got the portal-userauthcookie " << samlResult.value("userAuthCookie"); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     LoginParams loginParams { params.clientos() }; |  | ||||||
|     loginParams.setUser(samlResult.value("username")); |  | ||||||
|     loginParams.setPreloginCookie(samlResult.value("preloginCookie")); |  | ||||||
|     loginParams.setUserAuthCookie(samlResult.value("userAuthCookie")); |  | ||||||
|  |  | ||||||
|     login(loginParams); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void GatewayAuthenticator::onSAMLLoginFail(const QString &code, const QString &msg) |  | ||||||
| { |  | ||||||
|     emit fail(msg); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void GatewayAuthenticator::showChallenge(const QString &responseText) |  | ||||||
| { |  | ||||||
|     QRegularExpression re("\"(.*?)\";"); |  | ||||||
|     QRegularExpressionMatchIterator i = re.globalMatch(responseText); |  | ||||||
|  |  | ||||||
|     i.next(); // Skip the status value |  | ||||||
|     QString message = i.next().captured(1); |  | ||||||
|     QString inputStr = i.next().captured(1); |  | ||||||
|     // update the inputSrc field |  | ||||||
|     params.setInputStr(inputStr); |  | ||||||
|  |  | ||||||
|     challengeDialog = new ChallengeDialog; |  | ||||||
|     challengeDialog->setMessage(message); |  | ||||||
|  |  | ||||||
|     connect(challengeDialog, &ChallengeDialog::accepted, this, [this] { |  | ||||||
|         params.setPassword(challengeDialog->getChallenge()); |  | ||||||
|         LOGI << "Challenge submitted, try to re-authenticate..."; |  | ||||||
|         authenticate(); |  | ||||||
|     }); |  | ||||||
|  |  | ||||||
|     connect(challengeDialog, &ChallengeDialog::rejected, this, [this] { |  | ||||||
|         if (standardLoginWindow) { |  | ||||||
|             standardLoginWindow->close(); |  | ||||||
|         } |  | ||||||
|         emit fail(); |  | ||||||
|     }); |  | ||||||
|  |  | ||||||
|     connect(challengeDialog, &ChallengeDialog::finished, this, [this] { |  | ||||||
|         delete challengeDialog; |  | ||||||
|         challengeDialog = nullptr; |  | ||||||
|     }); |  | ||||||
|  |  | ||||||
|     challengeDialog->show(); |  | ||||||
| } |  | ||||||
| @@ -1,48 +0,0 @@ | |||||||
| #ifndef GATEWAYAUTHENTICATOR_H |  | ||||||
| #define GATEWAYAUTHENTICATOR_H |  | ||||||
|  |  | ||||||
| #include <QtCore/QObject> |  | ||||||
|  |  | ||||||
| #include "standardloginwindow.h" |  | ||||||
| #include "challengedialog.h" |  | ||||||
| #include "loginparams.h" |  | ||||||
| #include "gatewayauthenticatorparams.h" |  | ||||||
|  |  | ||||||
| class GatewayAuthenticator : public QObject |  | ||||||
| { |  | ||||||
|     Q_OBJECT |  | ||||||
| public: |  | ||||||
|     explicit GatewayAuthenticator(const QString &gateway, GatewayAuthenticatorParams params); |  | ||||||
|  |  | ||||||
|     void authenticate(); |  | ||||||
|  |  | ||||||
| signals: |  | ||||||
|     void success(const QString &authCookie); |  | ||||||
|     void fail(const QString &msg = ""); |  | ||||||
|  |  | ||||||
| private slots: |  | ||||||
|     void onLoginFinished(); |  | ||||||
|     void onPreloginFinished(); |  | ||||||
|     void onPerformStandardLogin(const QString &username, const QString &password); |  | ||||||
|     void onLoginWindowRejected(); |  | ||||||
|     void onLoginWindowFinished(); |  | ||||||
|     void onSAMLLoginSuccess(const QMap<QString, QString> &samlResult); |  | ||||||
|     void onSAMLLoginFail(const QString &code, const QString &msg); |  | ||||||
|  |  | ||||||
| private: |  | ||||||
|     QString gateway; |  | ||||||
|     GatewayAuthenticatorParams params; |  | ||||||
|     QString preloginUrl; |  | ||||||
|     QString loginUrl; |  | ||||||
|  |  | ||||||
|     StandardLoginWindow *standardLoginWindow { nullptr }; |  | ||||||
|     ChallengeDialog *challengeDialog { nullptr }; |  | ||||||
|  |  | ||||||
|     void login(const LoginParams& loginParams); |  | ||||||
|     void doAuth(); |  | ||||||
|     void normalAuth(QString labelUsername, QString labelPassword, QString authMessage); |  | ||||||
|     void samlAuth(QString samlMethod, QString samlRequest, QString preloginUrl = ""); |  | ||||||
|     void showChallenge(const QString &responseText); |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| #endif // GATEWAYAUTHENTICATOR_H |  | ||||||
| @@ -1,66 +0,0 @@ | |||||||
| #include "gatewayauthenticatorparams.h" |  | ||||||
|  |  | ||||||
| GatewayAuthenticatorParams::GatewayAuthenticatorParams() |  | ||||||
| { |  | ||||||
|  |  | ||||||
| } |  | ||||||
|  |  | ||||||
| GatewayAuthenticatorParams GatewayAuthenticatorParams::fromPortalConfigResponse(const PortalConfigResponse &portalConfig) |  | ||||||
| { |  | ||||||
|     GatewayAuthenticatorParams params; |  | ||||||
|     params.setUsername(portalConfig.username()); |  | ||||||
|     params.setPassword(portalConfig.password()); |  | ||||||
|     params.setUserAuthCookie(portalConfig.userAuthCookie()); |  | ||||||
|  |  | ||||||
|     return params; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| const QString &GatewayAuthenticatorParams::username() const |  | ||||||
| { |  | ||||||
|     return m_username; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void GatewayAuthenticatorParams::setUsername(const QString &newUsername) |  | ||||||
| { |  | ||||||
|     m_username = newUsername; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| const QString &GatewayAuthenticatorParams::password() const |  | ||||||
| { |  | ||||||
|     return m_password; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void GatewayAuthenticatorParams::setPassword(const QString &newPassword) |  | ||||||
| { |  | ||||||
|     m_password = newPassword; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| const QString &GatewayAuthenticatorParams::userAuthCookie() const |  | ||||||
| { |  | ||||||
|     return m_userAuthCookie; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void GatewayAuthenticatorParams::setUserAuthCookie(const QString &newUserAuthCookie) |  | ||||||
| { |  | ||||||
|     m_userAuthCookie = newUserAuthCookie; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| const QString &GatewayAuthenticatorParams::clientos() const |  | ||||||
| { |  | ||||||
|     return m_clientos; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void GatewayAuthenticatorParams::setClientos(const QString &newClientos) |  | ||||||
| { |  | ||||||
|     m_clientos = newClientos; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| const QString &GatewayAuthenticatorParams::inputStr() const |  | ||||||
| { |  | ||||||
|     return m_inputStr; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void GatewayAuthenticatorParams::setInputStr(const QString &inputStr) |  | ||||||
| { |  | ||||||
|     m_inputStr = inputStr; |  | ||||||
| } |  | ||||||
| @@ -1,38 +0,0 @@ | |||||||
| #ifndef GATEWAYAUTHENTICATORPARAMS_H |  | ||||||
| #define GATEWAYAUTHENTICATORPARAMS_H |  | ||||||
|  |  | ||||||
| #include <QtCore/QString> |  | ||||||
|  |  | ||||||
| #include "portalconfigresponse.h" |  | ||||||
|  |  | ||||||
| class GatewayAuthenticatorParams |  | ||||||
| { |  | ||||||
| public: |  | ||||||
|     GatewayAuthenticatorParams(); |  | ||||||
|  |  | ||||||
|     static GatewayAuthenticatorParams fromPortalConfigResponse(const PortalConfigResponse &portalConfig); |  | ||||||
|  |  | ||||||
|     const QString &username() const; |  | ||||||
|     void setUsername(const QString &newUsername); |  | ||||||
|  |  | ||||||
|     const QString &password() const; |  | ||||||
|     void setPassword(const QString &newPassword); |  | ||||||
|  |  | ||||||
|     const QString &userAuthCookie() const; |  | ||||||
|     void setUserAuthCookie(const QString &newUserAuthCookie); |  | ||||||
|  |  | ||||||
|     const QString &clientos() const; |  | ||||||
|     void setClientos(const QString &newClientos); |  | ||||||
|  |  | ||||||
|     const QString &inputStr() const; |  | ||||||
|     void setInputStr(const QString &inputStr); |  | ||||||
|  |  | ||||||
| private: |  | ||||||
|     QString m_username; |  | ||||||
|     QString m_password; |  | ||||||
|     QString m_userAuthCookie; |  | ||||||
|     QString m_clientos; |  | ||||||
|     QString m_inputStr; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| #endif // GATEWAYAUTHENTICATORPARAMS_H |  | ||||||
| @@ -1,519 +0,0 @@ | |||||||
| #include <QtGui/QIcon> |  | ||||||
| #include <plog/Log.h> |  | ||||||
|  |  | ||||||
| #include "gpclient.h" |  | ||||||
| #include "gphelper.h" |  | ||||||
| #include "ui_gpclient.h" |  | ||||||
| #include "portalauthenticator.h" |  | ||||||
| #include "gatewayauthenticator.h" |  | ||||||
| #include "settingsdialog.h" |  | ||||||
| #include "gatewayauthenticatorparams.h" |  | ||||||
|  |  | ||||||
| using namespace gpclient::helper; |  | ||||||
|  |  | ||||||
| GPClient::GPClient(QWidget *parent, IVpn *vpn) |  | ||||||
|     : QMainWindow(parent) |  | ||||||
|     , ui(new Ui::GPClient) |  | ||||||
|     , vpn(vpn) |  | ||||||
|     , settingsDialog(new SettingsDialog(this)) |  | ||||||
| { |  | ||||||
|     ui->setupUi(this); |  | ||||||
|  |  | ||||||
|     setWindowTitle("GlobalProtect"); |  | ||||||
|     setFixedSize(width(), height()); |  | ||||||
|     gpclient::helper::moveCenter(this); |  | ||||||
|  |  | ||||||
|     setupSettings(); |  | ||||||
|  |  | ||||||
|     // Restore portal from the previous settings |  | ||||||
|     this->portal(settings::get("portal", "").toString()); |  | ||||||
|  |  | ||||||
|     // DBus service setup |  | ||||||
|     QObject *ov = dynamic_cast<QObject*>(vpn); |  | ||||||
|     connect(ov, SIGNAL(connected()), this, SLOT(onVPNConnected())); |  | ||||||
|     connect(ov, SIGNAL(disconnected()), this, SLOT(onVPNDisconnected())); |  | ||||||
|     connect(ov, SIGNAL(error(QString)), this, SLOT(onVPNError(QString))); |  | ||||||
|     connect(ov, SIGNAL(logAvailable(QString)), this, SLOT(onVPNLogAvailable(QString))); |  | ||||||
|  |  | ||||||
|     // Initialize the context menu of system tray. |  | ||||||
|     initSystemTrayIcon(); |  | ||||||
|     initVpnStatus(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void GPClient::setupSettings() |  | ||||||
| { |  | ||||||
|     settingsButton = new QPushButton(this); |  | ||||||
|     settingsButton->setIcon(QIcon(":/images/settings_icon.png")); |  | ||||||
|     settingsButton->setFixedSize(QSize(28, 28)); |  | ||||||
|  |  | ||||||
|     QRect rect = this->geometry(); |  | ||||||
|     settingsButton->setGeometry( |  | ||||||
|                 rect.width() - settingsButton->width() - 15, |  | ||||||
|                 15, |  | ||||||
|                 settingsButton->geometry().width(), |  | ||||||
|                 settingsButton->geometry().height() |  | ||||||
|                 ); |  | ||||||
|  |  | ||||||
|     connect(settingsButton, &QPushButton::clicked, this, &GPClient::onSettingsButtonClicked); |  | ||||||
|     connect(settingsDialog, &QDialog::accepted, this, &GPClient::onSettingsAccepted); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void GPClient::onSettingsButtonClicked() |  | ||||||
| { |  | ||||||
|     settingsDialog->setClientos(settings::get("clientos", "Linux").toString()); |  | ||||||
|     settingsDialog->setOsVersion(settings::get("os-version", QSysInfo::prettyProductName()).toString()); |  | ||||||
|     settingsDialog->show(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void GPClient::onSettingsAccepted() |  | ||||||
| { |  | ||||||
|     settings::save("clientos", settingsDialog->clientos()); |  | ||||||
|     settings::save("os-version", settingsDialog->osVersion()); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void GPClient::on_connectButton_clicked() |  | ||||||
| { |  | ||||||
|     doConnect(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void GPClient::on_portalInput_returnPressed() |  | ||||||
| { |  | ||||||
|     doConnect(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void GPClient::on_portalInput_editingFinished() |  | ||||||
| { |  | ||||||
|     populateGatewayMenu(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void GPClient::initSystemTrayIcon() |  | ||||||
| { |  | ||||||
|     systemTrayIcon = new QSystemTrayIcon(this); |  | ||||||
|     contextMenu = new QMenu("GlobalProtect", this); |  | ||||||
|  |  | ||||||
|     gatewaySwitchMenu = new QMenu("Switch Gateway", this); |  | ||||||
|     gatewaySwitchMenu->setIcon(QIcon::fromTheme("network-workgroup")); |  | ||||||
|     populateGatewayMenu(); |  | ||||||
|  |  | ||||||
|     systemTrayIcon->setIcon(QIcon(":/images/not_connected.png")); |  | ||||||
|     systemTrayIcon->setToolTip("GlobalProtect"); |  | ||||||
|     systemTrayIcon->setContextMenu(contextMenu); |  | ||||||
|  |  | ||||||
|     connect(systemTrayIcon, &QSystemTrayIcon::activated, this, &GPClient::onSystemTrayActivated); |  | ||||||
|     connect(gatewaySwitchMenu, &QMenu::triggered, this, &GPClient::onGatewayChanged); |  | ||||||
|  |  | ||||||
|     openAction = contextMenu->addAction(QIcon::fromTheme("window-new"), "Open", this, &GPClient::activate); |  | ||||||
|     connectAction = contextMenu->addAction(QIcon::fromTheme("preferences-system-network"), "Connect", this, &GPClient::doConnect); |  | ||||||
|     contextMenu->addMenu(gatewaySwitchMenu); |  | ||||||
|     contextMenu->addSeparator(); |  | ||||||
|     clearAction = contextMenu->addAction(QIcon::fromTheme("edit-clear"), "Reset", this, &GPClient::reset); |  | ||||||
|     quitAction = contextMenu->addAction(QIcon::fromTheme("application-exit"), "Quit", this, &GPClient::quit); |  | ||||||
|  |  | ||||||
|     systemTrayIcon->show(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void GPClient::initVpnStatus() { |  | ||||||
|     int status = vpn->status(); |  | ||||||
|  |  | ||||||
|     if (status == 1) { |  | ||||||
|         ui->statusLabel->setText("Connecting..."); |  | ||||||
|         updateConnectionStatus(VpnStatus::pending); |  | ||||||
|     } else if (status == 2) { |  | ||||||
|         updateConnectionStatus(VpnStatus::connected); |  | ||||||
|     } else if (status == 3) { |  | ||||||
|         ui->statusLabel->setText("Disconnecting..."); |  | ||||||
|         updateConnectionStatus(VpnStatus::pending); |  | ||||||
|     } else { |  | ||||||
|         updateConnectionStatus(VpnStatus::disconnected); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void GPClient::populateGatewayMenu() |  | ||||||
| { |  | ||||||
|     LOGI << "Populating the Switch Gateway menu..."; |  | ||||||
|  |  | ||||||
|     const QList<GPGateway> gateways = allGateways(); |  | ||||||
|     gatewaySwitchMenu->clear(); |  | ||||||
|  |  | ||||||
|     if (gateways.isEmpty()) { |  | ||||||
|         gatewaySwitchMenu->addAction("<None>")->setData(-1); |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     const QString currentGatewayName = currentGateway().name(); |  | ||||||
|     for (int i = 0; i < gateways.length(); i++) { |  | ||||||
|         const GPGateway g = gateways.at(i); |  | ||||||
|         QString iconImage = ":/images/radio_unselected.png"; |  | ||||||
|         if (g.name() == currentGatewayName) { |  | ||||||
|             iconImage = ":/images/radio_selected.png"; |  | ||||||
|         } |  | ||||||
|         gatewaySwitchMenu->addAction(QIcon(iconImage), QString("%1 (%2)").arg(g.name(), g.address()))->setData(i); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void GPClient::updateConnectionStatus(const GPClient::VpnStatus &status) |  | ||||||
| { |  | ||||||
|     switch (status) { |  | ||||||
|         case VpnStatus::disconnected: |  | ||||||
|             ui->statusLabel->setText("Not Connected"); |  | ||||||
|             ui->statusImage->setStyleSheet("image: url(:/images/not_connected.png); padding: 15;"); |  | ||||||
|             ui->connectButton->setText("Connect"); |  | ||||||
|             ui->connectButton->setDisabled(false); |  | ||||||
|             ui->portalInput->setReadOnly(false); |  | ||||||
|  |  | ||||||
|             systemTrayIcon->setIcon(QIcon{ ":/images/not_connected.png" }); |  | ||||||
|             connectAction->setEnabled(true); |  | ||||||
|             connectAction->setText("Connect"); |  | ||||||
|             gatewaySwitchMenu->setEnabled(true); |  | ||||||
|             clearAction->setEnabled(true); |  | ||||||
|             break; |  | ||||||
|         case VpnStatus::pending: |  | ||||||
|             ui->statusImage->setStyleSheet("image: url(:/images/pending.png); padding: 15;"); |  | ||||||
|             ui->connectButton->setDisabled(true); |  | ||||||
|             ui->portalInput->setReadOnly(true); |  | ||||||
|  |  | ||||||
|             systemTrayIcon->setIcon(QIcon{ ":/images/pending.png" }); |  | ||||||
|             connectAction->setEnabled(false); |  | ||||||
|             gatewaySwitchMenu->setEnabled(false); |  | ||||||
|             clearAction->setEnabled(false); |  | ||||||
|             break; |  | ||||||
|         case VpnStatus::connected: |  | ||||||
|             ui->statusLabel->setText("Connected"); |  | ||||||
|             ui->statusImage->setStyleSheet("image: url(:/images/connected.png); padding: 15;"); |  | ||||||
|             ui->connectButton->setText("Disconnect"); |  | ||||||
|             ui->connectButton->setDisabled(false); |  | ||||||
|             ui->portalInput->setReadOnly(true); |  | ||||||
|  |  | ||||||
|             systemTrayIcon->setIcon(QIcon{ ":/images/connected.png" }); |  | ||||||
|             connectAction->setEnabled(true); |  | ||||||
|             connectAction->setText("Disconnect"); |  | ||||||
|             gatewaySwitchMenu->setEnabled(true); |  | ||||||
|             clearAction->setEnabled(false); |  | ||||||
|             break; |  | ||||||
|         default: |  | ||||||
|             break; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void GPClient::onSystemTrayActivated(QSystemTrayIcon::ActivationReason reason) |  | ||||||
| { |  | ||||||
|     switch (reason) { |  | ||||||
|         case QSystemTrayIcon::Trigger: |  | ||||||
|         case QSystemTrayIcon::DoubleClick: |  | ||||||
|             this->activate(); |  | ||||||
|             break; |  | ||||||
|         default: |  | ||||||
|             break; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void GPClient::onGatewayChanged(QAction *action) |  | ||||||
| { |  | ||||||
|     const int index = action->data().toInt(); |  | ||||||
|  |  | ||||||
|     if (index == -1) { |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     const auto g = allGateways().at(index); |  | ||||||
|  |  | ||||||
|     // If the selected gateway is the same as the current gateway |  | ||||||
|     if (g.name() == currentGateway().name()) { |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     setCurrentGateway(g); |  | ||||||
|  |  | ||||||
|     if (connected()) { |  | ||||||
|         ui->statusLabel->setText("Switching Gateway..."); |  | ||||||
|         ui->connectButton->setEnabled(false); |  | ||||||
|  |  | ||||||
|         vpn->disconnect(); |  | ||||||
|         isSwitchingGateway = true; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void GPClient::doConnect() |  | ||||||
| { |  | ||||||
|     LOGI << "Start connecting..."; |  | ||||||
|  |  | ||||||
|     const auto btnText = ui->connectButton->text(); |  | ||||||
|     const auto portal = this->portal(); |  | ||||||
|  |  | ||||||
|     // Display the main window if portal is empty |  | ||||||
|     if (portal.isEmpty()) { |  | ||||||
|         activate(); |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     if (btnText.endsWith("Connect")) { |  | ||||||
|         settings::save("portal", portal); |  | ||||||
|  |  | ||||||
|         // Login to the previously saved gateway |  | ||||||
|         if (!currentGateway().name().isEmpty()) { |  | ||||||
|             LOGI << "Start gateway login using the previously saved gateway..."; |  | ||||||
|             isQuickConnect = true; |  | ||||||
|             gatewayLogin(); |  | ||||||
|         } else { |  | ||||||
|             // Perform the portal login |  | ||||||
|             LOGI << "Start portal login..."; |  | ||||||
|             portalLogin(); |  | ||||||
|         } |  | ||||||
|     } else { |  | ||||||
|         LOGI << "Start disconnecting the VPN..."; |  | ||||||
|  |  | ||||||
|         ui->statusLabel->setText("Disconnecting..."); |  | ||||||
|         updateConnectionStatus(VpnStatus::pending); |  | ||||||
|         vpn->disconnect(); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Login to the portal interface to get the portal config and preferred gateway |  | ||||||
| void GPClient::portalLogin() |  | ||||||
| { |  | ||||||
|     auto *portalAuth = new PortalAuthenticator(portal(), settings::get("clientos", "Linux").toString()); |  | ||||||
|  |  | ||||||
|     connect(portalAuth, &PortalAuthenticator::success, [this, portalAuth](const PortalConfigResponse response, const QString region) { |  | ||||||
|         this->onPortalSuccess(response, region); |  | ||||||
|         portalAuth->deleteLater(); |  | ||||||
|     }); |  | ||||||
|     // Prelogin failed on the portal interface, try to treat the portal as a gateway interface |  | ||||||
|     connect(portalAuth, &PortalAuthenticator::preloginFailed, [this, portalAuth](const QString msg) { |  | ||||||
|         this->onPortalPreloginFail(msg); |  | ||||||
|         portalAuth->deleteLater(); |  | ||||||
|     }); |  | ||||||
|     connect(portalAuth, &PortalAuthenticator::portalConfigFailed, [this, portalAuth](const QString msg) { |  | ||||||
|         this->onPortalConfigFail(msg); |  | ||||||
|         portalAuth->deleteLater(); |  | ||||||
|     }); |  | ||||||
|     // Portal login failed |  | ||||||
|     connect(portalAuth, &PortalAuthenticator::fail, [this, portalAuth](const QString &msg) { |  | ||||||
|         this->onPortalFail(msg); |  | ||||||
|         portalAuth->deleteLater(); |  | ||||||
|     }); |  | ||||||
|  |  | ||||||
|     ui->statusLabel->setText("Authenticating..."); |  | ||||||
|     updateConnectionStatus(VpnStatus::pending); |  | ||||||
|     portalAuth->authenticate(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void GPClient::onPortalSuccess(const PortalConfigResponse portalConfig, const QString region) |  | ||||||
| { |  | ||||||
|     LOGI << "Portal authentication succeeded."; |  | ||||||
|  |  | ||||||
|     // No gateway found in portal configuration |  | ||||||
|     if (portalConfig.allGateways().size() == 0) { |  | ||||||
|         LOGI << "No gateway found in portal configuration, treat the portal address as a gateway."; |  | ||||||
|         tryGatewayLogin(); |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     GPGateway gateway = filterPreferredGateway(portalConfig.allGateways(), region); |  | ||||||
|     setAllGateways(portalConfig.allGateways()); |  | ||||||
|     setCurrentGateway(gateway); |  | ||||||
|     this->portalConfig = portalConfig; |  | ||||||
|  |  | ||||||
|     gatewayLogin(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void GPClient::onPortalPreloginFail(const QString msg) |  | ||||||
| { |  | ||||||
|     LOGI << "Portal prelogin failed, treat the portal address as a gateway." << msg; |  | ||||||
|     tryGatewayLogin(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void GPClient::onPortalConfigFail(const QString msg) |  | ||||||
| { |  | ||||||
|     LOGI << "Failed to get the portal configuration, " << msg << " Treat the portal address as gateway."; |  | ||||||
|     tryGatewayLogin(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void GPClient::onPortalFail(const QString &msg) |  | ||||||
| { |  | ||||||
|     if (!msg.isEmpty()) { |  | ||||||
|         openMessageBox("Portal authentication failed.", msg); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     updateConnectionStatus(VpnStatus::disconnected); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void GPClient::tryGatewayLogin() |  | ||||||
| { |  | ||||||
|     LOGI << "Try to perform login on the the gateway interface..."; |  | ||||||
|  |  | ||||||
|     // Treat the portal input as the gateway address |  | ||||||
|     GPGateway g; |  | ||||||
|     g.setName(portal()); |  | ||||||
|     g.setAddress(portal()); |  | ||||||
|  |  | ||||||
|     QList<GPGateway> gateways; |  | ||||||
|     gateways.append(g); |  | ||||||
|  |  | ||||||
|     setAllGateways(gateways); |  | ||||||
|     setCurrentGateway(g); |  | ||||||
|  |  | ||||||
|     gatewayLogin(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Login to the gateway |  | ||||||
| void GPClient::gatewayLogin() |  | ||||||
| { |  | ||||||
|     LOGI << "Performing gateway login..."; |  | ||||||
|  |  | ||||||
|     GatewayAuthenticatorParams params = GatewayAuthenticatorParams::fromPortalConfigResponse(portalConfig); |  | ||||||
|     params.setClientos(settings::get("clientos", "Linux").toString()); |  | ||||||
|  |  | ||||||
|     GatewayAuthenticator *gatewayAuth; |  | ||||||
|     gatewayAuth = new GatewayAuthenticator(currentGateway().address(), params); |  | ||||||
|  |  | ||||||
|     connect(gatewayAuth, &GatewayAuthenticator::success, [this, gatewayAuth](const QString &authToken) { |  | ||||||
|         this->onGatewaySuccess(authToken); |  | ||||||
|         gatewayAuth->deleteLater(); |  | ||||||
|     }); |  | ||||||
|     connect(gatewayAuth, &GatewayAuthenticator::fail, [this, gatewayAuth](const QString &msg) { |  | ||||||
|         this->onGatewayFail(msg); |  | ||||||
|         gatewayAuth->deleteLater(); |  | ||||||
|     }); |  | ||||||
|  |  | ||||||
|     ui->statusLabel->setText("Authenticating..."); |  | ||||||
|     updateConnectionStatus(VpnStatus::pending); |  | ||||||
|     gatewayAuth->authenticate(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void GPClient::onGatewaySuccess(const QString &authCookie) |  | ||||||
| { |  | ||||||
|     LOGI << "Gateway login succeeded, got the cookie " << authCookie; |  | ||||||
|  |  | ||||||
|     isQuickConnect = false; |  | ||||||
|     QList<QString> gatewayAddresses; |  | ||||||
|     for (GPGateway &gw : allGateways()) { |  | ||||||
|       gatewayAddresses.push_back(gw.address()); |  | ||||||
|     } |  | ||||||
|     vpn->connect(currentGateway().address(), gatewayAddresses, portalConfig.username(), authCookie); |  | ||||||
|     ui->statusLabel->setText("Connecting..."); |  | ||||||
|     updateConnectionStatus(VpnStatus::pending); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void GPClient::onGatewayFail(const QString &msg) |  | ||||||
| { |  | ||||||
|     // If the quick connect on gateway failed, perform the portal login |  | ||||||
|     if (isQuickConnect && !msg.isEmpty()) { |  | ||||||
|         isQuickConnect = false; |  | ||||||
|         LOGI << "Quick connection failed, trying to portal login..."; |  | ||||||
|         portalLogin(); |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     if (!msg.isEmpty()) { |  | ||||||
|         openMessageBox("Gateway authentication failed.", msg); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     updateConnectionStatus(VpnStatus::disconnected); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void GPClient::activate() |  | ||||||
| { |  | ||||||
|     activateWindow(); |  | ||||||
|     showNormal(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| QString GPClient::portal() const |  | ||||||
| { |  | ||||||
|     const QString input = ui->portalInput->text().trimmed(); |  | ||||||
|  |  | ||||||
|     if (input.startsWith("http")) { |  | ||||||
|         return QUrl(input).authority(); |  | ||||||
|     } |  | ||||||
|     return input; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void GPClient::portal(QString p) |  | ||||||
| { |  | ||||||
|     ui->portalInput->setText(p); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| bool GPClient::connected() const |  | ||||||
| { |  | ||||||
|     const QString statusText = ui->statusLabel->text(); |  | ||||||
|     return statusText.contains("Connected") && !statusText.contains("Not"); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| QList<GPGateway> GPClient::allGateways() const |  | ||||||
| { |  | ||||||
|  |  | ||||||
|     QList<GPGateway> gateways; |  | ||||||
|  |  | ||||||
|     for (auto g :settings::get_all("_gateways$") ){ |  | ||||||
|  |  | ||||||
|     	gateways.append(GPGateway::fromJson(settings::get(g).toString())); |  | ||||||
|     } |  | ||||||
|     return gateways; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void GPClient::setAllGateways(QList<GPGateway> gateways) |  | ||||||
| { |  | ||||||
|     LOGI << "Updating all the gateways..."; |  | ||||||
|  |  | ||||||
|     settings::save(portal() + "_gateways", GPGateway::serialize(gateways)); |  | ||||||
|     populateGatewayMenu(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| GPGateway GPClient::currentGateway() const |  | ||||||
| { |  | ||||||
|     const QString selectedGateway = settings::get(portal() + "_selectedGateway").toString(); |  | ||||||
|  |  | ||||||
|     for (auto g : allGateways()) { |  | ||||||
|         if (g.name() == selectedGateway) { |  | ||||||
|             return g; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     return GPGateway{}; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void GPClient::setCurrentGateway(const GPGateway gateway) |  | ||||||
| { |  | ||||||
|     LOGI << "Updating the current gateway to " << gateway.name(); |  | ||||||
|  |  | ||||||
|     settings::save(portal() + "_selectedGateway", gateway.name()); |  | ||||||
|     ui->portalInput->setText(gateway.address()); |  | ||||||
|     populateGatewayMenu(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void GPClient::reset() |  | ||||||
| { |  | ||||||
|     settings::clear(); |  | ||||||
|     populateGatewayMenu(); |  | ||||||
|     ui->portalInput->clear(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void GPClient::quit() |  | ||||||
| { |  | ||||||
|     vpn->disconnect(); |  | ||||||
|     QApplication::quit(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void GPClient::onVPNConnected() |  | ||||||
| { |  | ||||||
|     updateConnectionStatus(VpnStatus::connected); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void GPClient::onVPNDisconnected() |  | ||||||
| { |  | ||||||
|     updateConnectionStatus(VpnStatus::disconnected); |  | ||||||
|  |  | ||||||
|     if (isSwitchingGateway) { |  | ||||||
|         gatewayLogin(); |  | ||||||
|         isSwitchingGateway = false; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void GPClient::onVPNError(QString errorMessage) |  | ||||||
| { |  | ||||||
|     updateConnectionStatus(VpnStatus::disconnected); |  | ||||||
|     openMessageBox("Failed to connect", errorMessage); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void GPClient::onVPNLogAvailable(QString log) |  | ||||||
| { |  | ||||||
|     LOGI << log; |  | ||||||
| } |  | ||||||
| @@ -1,104 +0,0 @@ | |||||||
| #ifndef GPCLIENT_H |  | ||||||
| #define GPCLIENT_H |  | ||||||
|  |  | ||||||
| #include <QtWidgets/QMainWindow> |  | ||||||
| #include <QtWidgets/QSystemTrayIcon> |  | ||||||
| #include <QtWidgets/QMenu> |  | ||||||
| #include <QtWidgets/QPushButton> |  | ||||||
|  |  | ||||||
| #include "portalconfigresponse.h" |  | ||||||
| #include "settingsdialog.h" |  | ||||||
| #include "vpn.h" |  | ||||||
| #include "gatewayauthenticator.h" |  | ||||||
|  |  | ||||||
| QT_BEGIN_NAMESPACE |  | ||||||
| namespace Ui { class GPClient; } |  | ||||||
| QT_END_NAMESPACE |  | ||||||
|  |  | ||||||
| class GPClient : public QMainWindow |  | ||||||
| { |  | ||||||
|     Q_OBJECT |  | ||||||
|  |  | ||||||
| public: |  | ||||||
|     GPClient(QWidget *parent, IVpn *vpn); |  | ||||||
|  |  | ||||||
|     void activate(); |  | ||||||
|     void quit(); |  | ||||||
|  |  | ||||||
|     QString portal() const; |  | ||||||
|     void portal(QString); |  | ||||||
|  |  | ||||||
|     GPGateway currentGateway() const; |  | ||||||
|     void setCurrentGateway(const GPGateway gateway); |  | ||||||
|  |  | ||||||
|     void doConnect(); |  | ||||||
|     void reset(); |  | ||||||
|  |  | ||||||
| private slots: |  | ||||||
|     void onSettingsButtonClicked(); |  | ||||||
|     void onSettingsAccepted(); |  | ||||||
|  |  | ||||||
|     void on_connectButton_clicked(); |  | ||||||
|     void on_portalInput_returnPressed(); |  | ||||||
|     void on_portalInput_editingFinished(); |  | ||||||
|  |  | ||||||
|     void onSystemTrayActivated(QSystemTrayIcon::ActivationReason reason); |  | ||||||
|     void onGatewayChanged(QAction *action); |  | ||||||
|  |  | ||||||
|     void onPortalSuccess(const PortalConfigResponse portalConfig, const QString region); |  | ||||||
|     void onPortalPreloginFail(const QString msg); |  | ||||||
|     void onPortalConfigFail(const QString msg); |  | ||||||
|     void onPortalFail(const QString &msg); |  | ||||||
|  |  | ||||||
|     void onGatewaySuccess(const QString &authCookie); |  | ||||||
|     void onGatewayFail(const QString &msg); |  | ||||||
|  |  | ||||||
|     void onVPNConnected(); |  | ||||||
|     void onVPNDisconnected(); |  | ||||||
|     void onVPNError(QString errorMessage); |  | ||||||
|     void onVPNLogAvailable(QString log); |  | ||||||
|  |  | ||||||
| private: |  | ||||||
|     enum class VpnStatus |  | ||||||
|     { |  | ||||||
|         disconnected, |  | ||||||
|         pending, |  | ||||||
|         connected |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
|     Ui::GPClient *ui; |  | ||||||
|     IVpn *vpn; |  | ||||||
|  |  | ||||||
|     QSystemTrayIcon *systemTrayIcon; |  | ||||||
|     QMenu *contextMenu; |  | ||||||
|     QAction *openAction; |  | ||||||
|     QAction *connectAction; |  | ||||||
|  |  | ||||||
|     QMenu *gatewaySwitchMenu; |  | ||||||
|     QAction *clearAction; |  | ||||||
|     QAction *quitAction; |  | ||||||
|  |  | ||||||
|     SettingsDialog *settingsDialog; |  | ||||||
|     QPushButton *settingsButton; |  | ||||||
|  |  | ||||||
|     bool isQuickConnect { false }; |  | ||||||
|     bool isSwitchingGateway { false }; |  | ||||||
|     PortalConfigResponse portalConfig; |  | ||||||
|  |  | ||||||
|     void setupSettings(); |  | ||||||
|  |  | ||||||
|     void initSystemTrayIcon(); |  | ||||||
|     void initVpnStatus(); |  | ||||||
|     void populateGatewayMenu(); |  | ||||||
|     void updateConnectionStatus(const VpnStatus &status); |  | ||||||
|  |  | ||||||
|     void portalLogin(); |  | ||||||
|     void tryGatewayLogin(); |  | ||||||
|     void gatewayLogin(); |  | ||||||
|  |  | ||||||
|     bool connected() const; |  | ||||||
|  |  | ||||||
|     QList<GPGateway> allGateways() const; |  | ||||||
|     void setAllGateways(QList<GPGateway> gateways); |  | ||||||
| }; |  | ||||||
| #endif // GPCLIENT_H |  | ||||||
| @@ -1,143 +0,0 @@ | |||||||
| <?xml version="1.0" encoding="UTF-8"?> |  | ||||||
| <ui version="4.0"> |  | ||||||
|  <class>GPClient</class> |  | ||||||
|  <widget class="QMainWindow" name="GPClient"> |  | ||||||
|   <property name="geometry"> |  | ||||||
|    <rect> |  | ||||||
|     <x>0</x> |  | ||||||
|     <y>0</y> |  | ||||||
|     <width>260</width> |  | ||||||
|     <height>362</height> |  | ||||||
|    </rect> |  | ||||||
|   </property> |  | ||||||
|   <property name="windowTitle"> |  | ||||||
|    <string>GlobalProtect OpenConnect</string> |  | ||||||
|   </property> |  | ||||||
|   <property name="windowIcon"> |  | ||||||
|    <iconset resource="resources.qrc"> |  | ||||||
|     <normaloff>:/images/logo.svg</normaloff>:/images/logo.svg</iconset> |  | ||||||
|   </property> |  | ||||||
|   <property name="styleSheet"> |  | ||||||
|    <string notr="true"/> |  | ||||||
|   </property> |  | ||||||
|   <property name="iconSize"> |  | ||||||
|    <size> |  | ||||||
|     <width>22</width> |  | ||||||
|     <height>22</height> |  | ||||||
|    </size> |  | ||||||
|   </property> |  | ||||||
|   <widget class="QWidget" name="centralwidget"> |  | ||||||
|    <property name="sizePolicy"> |  | ||||||
|     <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> |  | ||||||
|      <horstretch>0</horstretch> |  | ||||||
|      <verstretch>0</verstretch> |  | ||||||
|     </sizepolicy> |  | ||||||
|    </property> |  | ||||||
|    <property name="layoutDirection"> |  | ||||||
|     <enum>Qt::LeftToRight</enum> |  | ||||||
|    </property> |  | ||||||
|    <layout class="QVBoxLayout" name="verticalLayout_3" stretch="1,0,0"> |  | ||||||
|     <property name="leftMargin"> |  | ||||||
|      <number>15</number> |  | ||||||
|     </property> |  | ||||||
|     <property name="topMargin"> |  | ||||||
|      <number>15</number> |  | ||||||
|     </property> |  | ||||||
|     <property name="rightMargin"> |  | ||||||
|      <number>15</number> |  | ||||||
|     </property> |  | ||||||
|     <property name="bottomMargin"> |  | ||||||
|      <number>15</number> |  | ||||||
|     </property> |  | ||||||
|     <item> |  | ||||||
|      <layout class="QVBoxLayout" name="verticalLayout" stretch="1,0"> |  | ||||||
|       <property name="bottomMargin"> |  | ||||||
|        <number>15</number> |  | ||||||
|       </property> |  | ||||||
|       <item> |  | ||||||
|        <widget class="QLabel" name="statusImage"> |  | ||||||
|         <property name="styleSheet"> |  | ||||||
|          <string notr="true">#statusImage { |  | ||||||
| 	image: url(:/images/not_connected.png); |  | ||||||
| 	padding: 15 |  | ||||||
| }</string> |  | ||||||
|         </property> |  | ||||||
|         <property name="text"> |  | ||||||
|          <string/> |  | ||||||
|         </property> |  | ||||||
|        </widget> |  | ||||||
|       </item> |  | ||||||
|       <item> |  | ||||||
|        <widget class="QLabel" name="statusLabel"> |  | ||||||
|         <property name="font"> |  | ||||||
|          <font> |  | ||||||
|           <pointsize>14</pointsize> |  | ||||||
|           <weight>50</weight> |  | ||||||
|           <bold>false</bold> |  | ||||||
|           <underline>false</underline> |  | ||||||
|          </font> |  | ||||||
|         </property> |  | ||||||
|         <property name="text"> |  | ||||||
|          <string>Not Connected</string> |  | ||||||
|         </property> |  | ||||||
|         <property name="alignment"> |  | ||||||
|          <set>Qt::AlignCenter</set> |  | ||||||
|         </property> |  | ||||||
|        </widget> |  | ||||||
|       </item> |  | ||||||
|      </layout> |  | ||||||
|     </item> |  | ||||||
|     <item> |  | ||||||
|      <layout class="QVBoxLayout" name="verticalLayout_2"> |  | ||||||
|       <property name="bottomMargin"> |  | ||||||
|        <number>0</number> |  | ||||||
|       </property> |  | ||||||
|       <item> |  | ||||||
|        <widget class="QLineEdit" name="portalInput"> |  | ||||||
|         <property name="text"> |  | ||||||
|          <string/> |  | ||||||
|         </property> |  | ||||||
|         <property name="placeholderText"> |  | ||||||
|          <string>Please enter your portal address</string> |  | ||||||
|         </property> |  | ||||||
|        </widget> |  | ||||||
|       </item> |  | ||||||
|       <item> |  | ||||||
|        <widget class="QPushButton" name="connectButton"> |  | ||||||
|         <property name="sizePolicy"> |  | ||||||
|          <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> |  | ||||||
|           <horstretch>0</horstretch> |  | ||||||
|           <verstretch>0</verstretch> |  | ||||||
|          </sizepolicy> |  | ||||||
|         </property> |  | ||||||
|         <property name="text"> |  | ||||||
|          <string>Connect</string> |  | ||||||
|         </property> |  | ||||||
|         <property name="autoDefault"> |  | ||||||
|          <bool>true</bool> |  | ||||||
|         </property> |  | ||||||
|         <property name="default"> |  | ||||||
|          <bool>false</bool> |  | ||||||
|         </property> |  | ||||||
|        </widget> |  | ||||||
|       </item> |  | ||||||
|      </layout> |  | ||||||
|     </item> |  | ||||||
|     <item> |  | ||||||
|      <widget class="QLabel" name="label"> |  | ||||||
|       <property name="text"> |  | ||||||
|        <string><html><head/><body><p align="center"><a href="https://bit.ly/3g5DHqy"><span style=" text-decoration: underline; color:#4c6b8a;">Report a bug</span></a> / <a href="https://bit.ly/3jQYfEi"><span style=" text-decoration: underline; color:#4c6b8a;">Buy me a coffee</span></a></p></body></html></string> |  | ||||||
|       </property> |  | ||||||
|       <property name="openExternalLinks"> |  | ||||||
|        <bool>true</bool> |  | ||||||
|       </property> |  | ||||||
|      </widget> |  | ||||||
|     </item> |  | ||||||
|    </layout> |  | ||||||
|   </widget> |  | ||||||
|  </widget> |  | ||||||
|  <resources> |  | ||||||
|   <include location="resources.qrc"/> |  | ||||||
|  </resources> |  | ||||||
|  <connections/> |  | ||||||
| </ui> |  | ||||||
| @@ -1,97 +0,0 @@ | |||||||
| #include <QtCore/QJsonObject> |  | ||||||
| #include <QtCore/QJsonDocument> |  | ||||||
| #include <QtCore/QJsonArray> |  | ||||||
|  |  | ||||||
| #include "gpgateway.h" |  | ||||||
|  |  | ||||||
| GPGateway::GPGateway() |  | ||||||
| { |  | ||||||
| } |  | ||||||
|  |  | ||||||
| QString GPGateway::name() const |  | ||||||
| { |  | ||||||
|     return _name; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| QString GPGateway::address() const |  | ||||||
| { |  | ||||||
|     return _address; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void GPGateway::setName(const QString &name) |  | ||||||
| { |  | ||||||
|     _name = name; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void GPGateway::setAddress(const QString &address) |  | ||||||
| { |  | ||||||
|     _address = address; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void GPGateway::setPriorityRules(const QMap<QString, int> &priorityRules) |  | ||||||
| { |  | ||||||
|     _priorityRules = priorityRules; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| int GPGateway::priorityOf(QString ruleName) const |  | ||||||
| { |  | ||||||
|     if (_priorityRules.contains(ruleName)) { |  | ||||||
|         return _priorityRules.value(ruleName); |  | ||||||
|     } |  | ||||||
|     return 0; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| QJsonObject GPGateway::toJsonObject() const |  | ||||||
| { |  | ||||||
|     QJsonObject obj; |  | ||||||
|     obj.insert("name", name()); |  | ||||||
|     obj.insert("address", address()); |  | ||||||
|  |  | ||||||
|     return obj; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| QString GPGateway::toString() const |  | ||||||
| { |  | ||||||
|     QJsonDocument jsonDoc{ toJsonObject() }; |  | ||||||
|     return QString::fromUtf8(jsonDoc.toJson()); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| QString GPGateway::serialize(QList<GPGateway> &gateways) |  | ||||||
| { |  | ||||||
|     QJsonArray arr; |  | ||||||
|  |  | ||||||
|     for (auto g : gateways) { |  | ||||||
|         arr.append(g.toJsonObject()); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     QJsonDocument jsonDoc{ arr }; |  | ||||||
|     return QString::fromUtf8(jsonDoc.toJson()); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| QList<GPGateway> GPGateway::fromJson(const QString &jsonString) |  | ||||||
| { |  | ||||||
|     QList<GPGateway> gateways; |  | ||||||
|  |  | ||||||
|     if (jsonString.isEmpty()) { |  | ||||||
|         return gateways; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonString.toUtf8()); |  | ||||||
|  |  | ||||||
|     for (auto item : jsonDoc.array()) { |  | ||||||
|         GPGateway g = GPGateway::fromJsonObject(item.toObject()); |  | ||||||
|         gateways.append(g); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     return gateways; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| GPGateway GPGateway::fromJsonObject(const QJsonObject &jsonObj) |  | ||||||
| { |  | ||||||
|     GPGateway g; |  | ||||||
|  |  | ||||||
|     g.setName(jsonObj.value("name").toString()); |  | ||||||
|     g.setAddress(jsonObj.value("address").toString()); |  | ||||||
|  |  | ||||||
|     return g; |  | ||||||
| } |  | ||||||
| @@ -1,33 +0,0 @@ | |||||||
| #ifndef GPGATEWAY_H |  | ||||||
| #define GPGATEWAY_H |  | ||||||
|  |  | ||||||
| #include <QtCore/QString> |  | ||||||
| #include <QtCore/QMap> |  | ||||||
| #include <QtCore/QJsonObject> |  | ||||||
|  |  | ||||||
| class GPGateway |  | ||||||
| { |  | ||||||
| public: |  | ||||||
|     GPGateway(); |  | ||||||
|  |  | ||||||
|     QString name() const; |  | ||||||
|     QString address() const; |  | ||||||
|  |  | ||||||
|     void setName(const QString &name); |  | ||||||
|     void setAddress(const QString &address); |  | ||||||
|     void setPriorityRules(const QMap<QString, int> &priorityRules); |  | ||||||
|     int priorityOf(QString ruleName) const; |  | ||||||
|     QJsonObject toJsonObject() const; |  | ||||||
|     QString toString() const; |  | ||||||
|  |  | ||||||
|     static QString serialize(QList<GPGateway> &gateways); |  | ||||||
|     static QList<GPGateway> fromJson(const QString &jsonString); |  | ||||||
|     static GPGateway fromJsonObject(const QJsonObject &jsonObj); |  | ||||||
|  |  | ||||||
| private: |  | ||||||
|     QString _name; |  | ||||||
|     QString _address; |  | ||||||
|     QMap<QString, int> _priorityRules; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| #endif // GPGATEWAY_H |  | ||||||
| @@ -1,178 +0,0 @@ | |||||||
| #include <QtCore/QXmlStreamReader> |  | ||||||
| #include <QtWidgets/QMessageBox> |  | ||||||
| #include <QtWidgets/QDesktopWidget> |  | ||||||
| #include <QtWidgets/QApplication> |  | ||||||
| #include <QtWidgets/QWidget> |  | ||||||
| #include <QtNetwork/QNetworkRequest> |  | ||||||
| #include <QtNetwork/QSslConfiguration> |  | ||||||
| #include <QtNetwork/QSslSocket> |  | ||||||
| #include <plog/Log.h> |  | ||||||
| #include <QWebEngineProfile> |  | ||||||
| #include <QWebEngineCookieStore> |  | ||||||
| #include <keychain.h> |  | ||||||
|  |  | ||||||
| #include "gphelper.h" |  | ||||||
|  |  | ||||||
| using namespace QKeychain; |  | ||||||
|  |  | ||||||
| QNetworkAccessManager* gpclient::helper::networkManager = new QNetworkAccessManager; |  | ||||||
|  |  | ||||||
| QNetworkReply* gpclient::helper::createRequest(QString url, QByteArray params) |  | ||||||
| { |  | ||||||
|     QNetworkRequest request(url); |  | ||||||
|  |  | ||||||
|     // Skip the ssl verifying |  | ||||||
|     QSslConfiguration conf = request.sslConfiguration(); |  | ||||||
|     conf.setPeerVerifyMode(QSslSocket::VerifyNone); |  | ||||||
|     conf.setSslOption(QSsl::SslOptionDisableLegacyRenegotiation, false); |  | ||||||
|     request.setSslConfiguration(conf); |  | ||||||
|  |  | ||||||
|     request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); |  | ||||||
|     request.setHeader(QNetworkRequest::UserAgentHeader, UA); |  | ||||||
|  |  | ||||||
|     if (params == nullptr) { |  | ||||||
|         return networkManager->post(request, QByteArray(nullptr)); |  | ||||||
|     } |  | ||||||
|     return networkManager->post(request, params); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| GPGateway gpclient::helper::filterPreferredGateway(QList<GPGateway> gateways, const QString ruleName) |  | ||||||
| { |  | ||||||
|     LOGI << gateways.size() << " gateway(s) available, filter the gateways with rule: " << ruleName; |  | ||||||
|  |  | ||||||
|     GPGateway gateway = gateways.first(); |  | ||||||
|  |  | ||||||
|     for (GPGateway g : gateways) { |  | ||||||
|         if (g.priorityOf(ruleName) > gateway.priorityOf(ruleName)) { |  | ||||||
|             LOGI << "Find a preferred gateway: " << g.name(); |  | ||||||
|             gateway = g; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     return gateway; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| QUrlQuery gpclient::helper::parseGatewayResponse(const QByteArray &xml) |  | ||||||
| { |  | ||||||
|     LOGI << "Start parsing the gateway response..."; |  | ||||||
|     LOGI << "The gateway response is: " << xml; |  | ||||||
|  |  | ||||||
|     QXmlStreamReader xmlReader{xml}; |  | ||||||
|     QList<QString> args; |  | ||||||
|  |  | ||||||
|     while (!xmlReader.atEnd()) { |  | ||||||
|         xmlReader.readNextStartElement(); |  | ||||||
|         if (xmlReader.name() == "argument") { |  | ||||||
|             args.append(QUrl::toPercentEncoding(xmlReader.readElementText())); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     QUrlQuery params{}; |  | ||||||
|     params.addQueryItem("authcookie", args.at(1)); |  | ||||||
|     params.addQueryItem("portal", args.at(3)); |  | ||||||
|     params.addQueryItem("user", args.at(4)); |  | ||||||
|     params.addQueryItem("domain", args.at(7)); |  | ||||||
|     params.addQueryItem("preferred-ip", args.at(15)); |  | ||||||
|     params.addQueryItem("computer", QUrl::toPercentEncoding(QSysInfo::machineHostName())); |  | ||||||
|  |  | ||||||
|     return params; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void gpclient::helper::openMessageBox(const QString &message, const QString& informativeText) |  | ||||||
| { |  | ||||||
|     QMessageBox msgBox; |  | ||||||
|     msgBox.setWindowTitle("Notice"); |  | ||||||
|     msgBox.setText(message); |  | ||||||
|     msgBox.setFixedWidth(500); |  | ||||||
|     msgBox.setStyleSheet("QLabel{min-width: 250px}"); |  | ||||||
|     msgBox.setInformativeText(informativeText); |  | ||||||
|     msgBox.exec(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void gpclient::helper::moveCenter(QWidget *widget) |  | ||||||
| { |  | ||||||
|     QDesktopWidget *desktop = QApplication::desktop(); |  | ||||||
|  |  | ||||||
|     int screenWidth, width; |  | ||||||
|     int screenHeight, height; |  | ||||||
|     int x, y; |  | ||||||
|     QSize windowSize; |  | ||||||
|  |  | ||||||
|     screenWidth = desktop->width(); |  | ||||||
|     screenHeight = desktop->height(); |  | ||||||
|  |  | ||||||
|     windowSize = widget->size(); |  | ||||||
|     width = windowSize.width(); |  | ||||||
|     height = windowSize.height(); |  | ||||||
|  |  | ||||||
|     x = (screenWidth - width) / 2; |  | ||||||
|     y = (screenHeight - height) / 2; |  | ||||||
|     y -= 50; |  | ||||||
|     widget->move(x, y); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| QSettings *gpclient::helper::settings::_settings = new QSettings("com.yuezk.qt", "GPClient"); |  | ||||||
|  |  | ||||||
| QVariant gpclient::helper::settings::get(const QString &key, const QVariant &defaultValue) |  | ||||||
| { |  | ||||||
|     return _settings->value(key, defaultValue); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| QStringList gpclient::helper::settings::get_all(const QString &key, const QVariant &defaultValue) |  | ||||||
| { |  | ||||||
| 	QRegularExpression re(key); |  | ||||||
| 	return 	_settings->allKeys().filter(re); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void gpclient::helper::settings::save(const QString &key, const QVariant &value) |  | ||||||
| { |  | ||||||
|     _settings->setValue(key, value); |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| void gpclient::helper::settings::clear() |  | ||||||
| { |  | ||||||
|     QStringList keys = _settings->allKeys(); |  | ||||||
|     for (const auto &key : qAsConst(keys)) { |  | ||||||
|         if (!reservedKeys.contains(key)) { |  | ||||||
|             _settings->remove(key); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     QWebEngineProfile::defaultProfile()->cookieStore()->deleteAllCookies(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| bool gpclient::helper::settings::secureSave(const QString &key, const QString &value) { |  | ||||||
|     WritePasswordJob job( QLatin1String("gpclient") ); |  | ||||||
|     job.setAutoDelete( false ); |  | ||||||
|     job.setKey( key ); |  | ||||||
|     job.setTextData( value ); |  | ||||||
|     QEventLoop loop; |  | ||||||
|     job.connect( &job, SIGNAL(finished(QKeychain::Job*)), &loop, SLOT(quit()) ); |  | ||||||
|     job.start(); |  | ||||||
|     loop.exec(); |  | ||||||
|     if ( job.error() ) { |  | ||||||
|         return false; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     return true; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| bool gpclient::helper::settings::secureGet(const QString &key, QString &value) { |  | ||||||
|     ReadPasswordJob job( QLatin1String("gpclient") ); |  | ||||||
|     job.setAutoDelete( false ); |  | ||||||
|     job.setKey( key ); |  | ||||||
|     QEventLoop loop; |  | ||||||
|     job.connect( &job, SIGNAL(finished(QKeychain::Job*)), &loop, SLOT(quit()) ); |  | ||||||
|     job.start(); |  | ||||||
|     loop.exec(); |  | ||||||
|  |  | ||||||
|     const QString pw = job.textData(); |  | ||||||
|     if ( job.error() ) { |  | ||||||
|         return false; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     value = pw; |  | ||||||
|     return true; |  | ||||||
| } |  | ||||||
| @@ -1,47 +0,0 @@ | |||||||
| #ifndef GPHELPER_H |  | ||||||
| #define GPHELPER_H |  | ||||||
|  |  | ||||||
| #include <QtCore/QObject> |  | ||||||
| #include <QtCore/QUrlQuery> |  | ||||||
| #include <QtCore/QSettings> |  | ||||||
| #include <QtNetwork/QNetworkAccessManager> |  | ||||||
| #include <QtNetwork/QNetworkRequest> |  | ||||||
| #include <QtNetwork/QNetworkReply> |  | ||||||
|  |  | ||||||
| #include "samlloginwindow.h" |  | ||||||
| #include "gpgateway.h" |  | ||||||
|  |  | ||||||
|  |  | ||||||
| const QString UA = "PAN GlobalProtect"; |  | ||||||
|  |  | ||||||
| namespace gpclient { |  | ||||||
|     namespace helper { |  | ||||||
|         extern QNetworkAccessManager *networkManager; |  | ||||||
|  |  | ||||||
|         QNetworkReply* createRequest(QString url, QByteArray params = nullptr); |  | ||||||
|  |  | ||||||
|         GPGateway filterPreferredGateway(QList<GPGateway> gateways, const QString ruleName); |  | ||||||
|  |  | ||||||
|         QUrlQuery parseGatewayResponse(const QByteArray& xml); |  | ||||||
|  |  | ||||||
|         void openMessageBox(const QString& message, const QString& informativeText = ""); |  | ||||||
|  |  | ||||||
|         void moveCenter(QWidget *widget); |  | ||||||
|  |  | ||||||
|         namespace settings { |  | ||||||
|  |  | ||||||
|             extern QSettings *_settings; |  | ||||||
|             static const QStringList reservedKeys {"extraArgs", "clientos"}; |  | ||||||
|  |  | ||||||
|             QVariant get(const QString &key, const QVariant &defaultValue = QVariant()); |  | ||||||
|             QStringList get_all(const QString &key, const QVariant &defaultValue = QVariant()); |  | ||||||
|             void save(const QString &key, const QVariant &value); |  | ||||||
|             void clear(); |  | ||||||
|  |  | ||||||
|             bool secureSave(const QString &key, const QString &value); |  | ||||||
|             bool secureGet(const QString &key, QString &value); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #endif // GPHELPER_H |  | ||||||
| @@ -1,88 +0,0 @@ | |||||||
| #include <QtCore/QUrlQuery> |  | ||||||
|  |  | ||||||
| #include "loginparams.h" |  | ||||||
| #include "gphelper.h" |  | ||||||
|  |  | ||||||
| using namespace gpclient::helper; |  | ||||||
|  |  | ||||||
| LoginParams::LoginParams(const QString clientos) |  | ||||||
| { |  | ||||||
|     params.addQueryItem("prot", QUrl::toPercentEncoding("https:")); |  | ||||||
|     params.addQueryItem("server", ""); |  | ||||||
|     params.addQueryItem("inputStr", ""); |  | ||||||
|     params.addQueryItem("jnlpReady", "jnlpReady"); |  | ||||||
|     params.addQueryItem("user", ""); |  | ||||||
|     params.addQueryItem("passwd", ""); |  | ||||||
|     params.addQueryItem("computer", QUrl::toPercentEncoding(QSysInfo::machineHostName())); |  | ||||||
|     params.addQueryItem("ok", "Login"); |  | ||||||
|     params.addQueryItem("direct", "yes"); |  | ||||||
|     params.addQueryItem("clientVer", "4100"); |  | ||||||
|  |  | ||||||
|     // add the clientos parameter if not empty |  | ||||||
|     if (!clientos.isEmpty()) { |  | ||||||
|         params.addQueryItem("clientos", clientos); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     auto osVersion = settings::get("os-version", "").toString(); |  | ||||||
|     if (osVersion.isEmpty()) { |  | ||||||
|         osVersion = QSysInfo::prettyProductName(); |  | ||||||
|     } |  | ||||||
|     params.addQueryItem("os-version", QUrl::toPercentEncoding(osVersion)); |  | ||||||
|  |  | ||||||
|     params.addQueryItem("portal-userauthcookie", ""); |  | ||||||
|     params.addQueryItem("portal-prelogonuserauthcookie", ""); |  | ||||||
|     params.addQueryItem("prelogin-cookie", ""); |  | ||||||
|     params.addQueryItem("ipv6-support", "yes"); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| LoginParams::~LoginParams() |  | ||||||
| { |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void LoginParams::setUser(const QString user) |  | ||||||
| { |  | ||||||
|     updateQueryItem("user", user); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void LoginParams::setServer(const QString server) |  | ||||||
| { |  | ||||||
|     updateQueryItem("server", server); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void LoginParams::setPassword(const QString password) |  | ||||||
| { |  | ||||||
|     updateQueryItem("passwd", password); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void LoginParams::setUserAuthCookie(const QString cookie) |  | ||||||
| { |  | ||||||
|     updateQueryItem("portal-userauthcookie", cookie); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void LoginParams::setPrelogonAuthCookie(const QString cookie) |  | ||||||
| { |  | ||||||
|     updateQueryItem("portal-prelogonuserauthcookie", cookie); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void LoginParams::setPreloginCookie(const QString cookie) |  | ||||||
| { |  | ||||||
|     updateQueryItem("prelogin-cookie", cookie); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void LoginParams::setInputStr(const QString inputStr) |  | ||||||
| { |  | ||||||
|     updateQueryItem("inputStr", inputStr); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| QByteArray LoginParams::toUtf8() const |  | ||||||
| { |  | ||||||
|     return params.toString().toUtf8(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void LoginParams::updateQueryItem(const QString key, const QString value) |  | ||||||
| { |  | ||||||
|     if (params.hasQueryItem(key)) { |  | ||||||
|         params.removeQueryItem(key); |  | ||||||
|     } |  | ||||||
|     params.addQueryItem(key, QUrl::toPercentEncoding(value)); |  | ||||||
| } |  | ||||||
| @@ -1,28 +0,0 @@ | |||||||
| #ifndef LOGINPARAMS_H |  | ||||||
| #define LOGINPARAMS_H |  | ||||||
|  |  | ||||||
| #include <QtCore/QUrlQuery> |  | ||||||
|  |  | ||||||
| class LoginParams |  | ||||||
| { |  | ||||||
| public: |  | ||||||
|     LoginParams(const QString clientos); |  | ||||||
|     ~LoginParams(); |  | ||||||
|  |  | ||||||
|     void setUser(const QString user); |  | ||||||
|     void setServer(const QString server); |  | ||||||
|     void setPassword(const QString password); |  | ||||||
|     void setUserAuthCookie(const QString cookie); |  | ||||||
|     void setPrelogonAuthCookie(const QString cookie); |  | ||||||
|     void setPreloginCookie(const QString cookie); |  | ||||||
|     void setInputStr(const QString inputStr); |  | ||||||
|  |  | ||||||
|     QByteArray toUtf8() const; |  | ||||||
|  |  | ||||||
| private: |  | ||||||
|     QUrlQuery params; |  | ||||||
|  |  | ||||||
|     void updateQueryItem(const QString key, const QString value); |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| #endif // LOGINPARAMS_H |  | ||||||
| @@ -1,96 +0,0 @@ | |||||||
| #include <QtCore/QObject> |  | ||||||
| #include <QtCore/QString> |  | ||||||
| #include <QtCore/QStandardPaths> |  | ||||||
| #include <plog/Log.h> |  | ||||||
| #include <plog/Init.h> |  | ||||||
| #include <plog/Appenders/ColorConsoleAppender.h> |  | ||||||
| #include <plog/Formatters/TxtFormatter.h> |  | ||||||
|  |  | ||||||
| #include "singleapplication.h" |  | ||||||
| #include "gpclient.h" |  | ||||||
| #include "vpn_dbus.h" |  | ||||||
| #include "vpn_json.h" |  | ||||||
| #include "enhancedwebview.h" |  | ||||||
| #include "sigwatch.h" |  | ||||||
| #include "version.h" |  | ||||||
|  |  | ||||||
| #define QT_AUTO_SCREEN_SCALE_FACTOR "QT_AUTO_SCREEN_SCALE_FACTOR" |  | ||||||
|  |  | ||||||
| int main(int argc, char *argv[]) |  | ||||||
| { |  | ||||||
|     plog::ColorConsoleAppender<plog::TxtFormatter> consoleAppender(plog::streamStdErr); |  | ||||||
|     plog::init(plog::debug, &consoleAppender); |  | ||||||
|  |  | ||||||
|     LOGI << "GlobalProtect started, version: " << VERSION; |  | ||||||
|  |  | ||||||
|     auto port = QString::fromLocal8Bit(qgetenv(ENV_CDP_PORT)); |  | ||||||
|     auto hidpiSupport = QString::fromLocal8Bit(qgetenv(QT_AUTO_SCREEN_SCALE_FACTOR)); |  | ||||||
|  |  | ||||||
|     if (port.isEmpty()) { |  | ||||||
|         qputenv(ENV_CDP_PORT, "12315"); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     if (hidpiSupport.isEmpty()) { |  | ||||||
|         qputenv(QT_AUTO_SCREEN_SCALE_FACTOR, "1"); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     SingleApplication app(argc, argv); |  | ||||||
|     app.setQuitOnLastWindowClosed(false); |  | ||||||
|  |  | ||||||
|     QCommandLineParser parser; |  | ||||||
|     parser.addHelpOption(); |  | ||||||
|     parser.addVersionOption(); |  | ||||||
|     parser.addPositionalArgument("server", "The URL of the VPN server. Optional."); |  | ||||||
|     parser.addPositionalArgument("gateway", "The URL of the specific VPN gateway. Optional."); |  | ||||||
|     parser.addOptions({ |  | ||||||
|       {"json", "Write the result of the handshake with the GlobalConnect server to stdout as JSON and terminate. Useful for scripting."}, |  | ||||||
|       {"now", "Do not show the dialog with the connect button; connect immediately instead."}, |  | ||||||
|       {"start-minimized", "Launch the client minimized."}, |  | ||||||
|       {"reset", "Reset the client's settings."}, |  | ||||||
|     }); |  | ||||||
|     parser.process(app); |  | ||||||
|  |  | ||||||
|     const auto positional = parser.positionalArguments(); |  | ||||||
|  |  | ||||||
|     auto *vpn = parser.isSet("json") // yes it leaks, but this is cleared on exit anyway |  | ||||||
|       ? static_cast<IVpn*>(new VpnJson(nullptr)) // Print to stdout and exit |  | ||||||
|       : static_cast<IVpn*>(new VpnDbus(nullptr)); // Contact GPService daemon via dbus |  | ||||||
|     GPClient w(nullptr, vpn); |  | ||||||
|  |  | ||||||
|     if (positional.size() > 0) { |  | ||||||
|       w.portal(positional.at(0)); |  | ||||||
|     } |  | ||||||
|     if (positional.size() > 1) { |  | ||||||
|       GPGateway gw; |  | ||||||
|       gw.setName(positional.at(1)); |  | ||||||
|       gw.setAddress(positional.at(1)); |  | ||||||
|       w.setCurrentGateway(gw); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     QObject::connect(&app, &SingleApplication::instanceStarted, &w, &GPClient::activate); |  | ||||||
|  |  | ||||||
|     UnixSignalWatcher sigwatch; |  | ||||||
|     sigwatch.watchForSignal(SIGINT); |  | ||||||
|     sigwatch.watchForSignal(SIGTERM); |  | ||||||
|     sigwatch.watchForSignal(SIGQUIT); |  | ||||||
|     sigwatch.watchForSignal(SIGHUP); |  | ||||||
|     QObject::connect(&sigwatch, &UnixSignalWatcher::unixSignal, &w, &GPClient::quit); |  | ||||||
|  |  | ||||||
|     if (parser.isSet("json")) { |  | ||||||
|         QObject::connect(static_cast<VpnJson*>(vpn), &VpnJson::connected, &w, &GPClient::quit); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     if (parser.isSet("reset")) { |  | ||||||
|         w.reset(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     if (parser.isSet("now")) { |  | ||||||
|       w.doConnect(); |  | ||||||
|     } else if (parser.isSet("start-minimized")) { |  | ||||||
|       w.showMinimized(); |  | ||||||
|     } else { |  | ||||||
|       w.show(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     return app.exec(); |  | ||||||
| } |  | ||||||
| Before Width: | Height: | Size: 16 KiB | 
| Before Width: | Height: | Size: 16 KiB | 
| @@ -1,219 +0,0 @@ | |||||||
| #include <QtNetwork/QNetworkReply> |  | ||||||
| #include <plog/Log.h> |  | ||||||
|  |  | ||||||
| #include "portalauthenticator.h" |  | ||||||
| #include "gphelper.h" |  | ||||||
| #include "standardloginwindow.h" |  | ||||||
| #include "samlloginwindow.h" |  | ||||||
| #include "loginparams.h" |  | ||||||
| #include "preloginresponse.h" |  | ||||||
| #include "portalconfigresponse.h" |  | ||||||
| #include "gpgateway.h" |  | ||||||
|  |  | ||||||
| using namespace gpclient::helper; |  | ||||||
|  |  | ||||||
| PortalAuthenticator::PortalAuthenticator(const QString& portal, const QString& clientos) : QObject() |  | ||||||
|   , portal(portal) |  | ||||||
|   , clientos(clientos) |  | ||||||
|   , preloginUrl("https://" + portal + "/global-protect/prelogin.esp?tmp=tmp&kerberos-support=yes&ipv6-support=yes&clientVer=4100") |  | ||||||
|   , configUrl("https://" + portal + "/global-protect/getconfig.esp") |  | ||||||
| { |  | ||||||
|     if (!clientos.isEmpty()) { |  | ||||||
|         preloginUrl = preloginUrl + "&clientos=" + clientos; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| PortalAuthenticator::~PortalAuthenticator() |  | ||||||
| { |  | ||||||
|     delete standardLoginWindow; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void PortalAuthenticator::authenticate() |  | ||||||
| { |  | ||||||
|     attempts++; |  | ||||||
|  |  | ||||||
|     LOGI << QString("(%1/%2) attempts").arg(attempts).arg(MAX_ATTEMPTS) << ", perform portal prelogin at " << preloginUrl; |  | ||||||
|  |  | ||||||
|     QNetworkReply *reply = createRequest(preloginUrl); |  | ||||||
|     connect(reply, &QNetworkReply::finished, this, &PortalAuthenticator::onPreloginFinished); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void PortalAuthenticator::onPreloginFinished() |  | ||||||
| { |  | ||||||
|     auto *reply = qobject_cast<QNetworkReply*>(sender()); |  | ||||||
|  |  | ||||||
|     if (reply->error()) { |  | ||||||
|         LOGE << QString("Error occurred while accessing %1, %2").arg(preloginUrl, reply->errorString()); |  | ||||||
|         emit preloginFailed("Error occurred on the portal prelogin interface."); |  | ||||||
|         delete reply; |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     LOGI << "Portal prelogin succeeded."; |  | ||||||
|  |  | ||||||
|     preloginResponse = PreloginResponse::parse(reply->readAll()); |  | ||||||
|  |  | ||||||
|     LOGI << "Finished parsing the prelogin response. The region field is: " << preloginResponse.region(); |  | ||||||
|  |  | ||||||
|     if (preloginResponse.hasSamlAuthFields()) { |  | ||||||
|         // Do SAML authentication |  | ||||||
|         samlAuth(); |  | ||||||
|     } else if (preloginResponse.hasNormalAuthFields()) { |  | ||||||
|         // Do normal username/password authentication |  | ||||||
|         tryAutoLogin(); |  | ||||||
|     } else { |  | ||||||
|         LOGE << QString("Unknown prelogin response for %1 got %2").arg(preloginUrl).arg(QString::fromUtf8(preloginResponse.rawResponse())); |  | ||||||
|         emit preloginFailed("Unknown response for portal prelogin interface."); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     delete reply; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void PortalAuthenticator::tryAutoLogin() |  | ||||||
| { |  | ||||||
|     const QString username = settings::get("username").toString(); |  | ||||||
|     const QString password = settings::get("password").toString(); |  | ||||||
|  |  | ||||||
|     if (!username.isEmpty() && !password.isEmpty()) { |  | ||||||
|         LOGI << "Trying auto login using the saved credentials"; |  | ||||||
|         isAutoLogin = true; |  | ||||||
|         fetchConfig(settings::get("username").toString(), settings::get("password").toString()); |  | ||||||
|     } else { |  | ||||||
|         normalAuth(); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void PortalAuthenticator::normalAuth() |  | ||||||
| { |  | ||||||
|     LOGI << "Trying to launch the normal login window..."; |  | ||||||
|  |  | ||||||
|     standardLoginWindow = new StandardLoginWindow {portal, preloginResponse.labelUsername(), preloginResponse.labelPassword(), preloginResponse.authMessage() }; |  | ||||||
|  |  | ||||||
|     // Do login |  | ||||||
|     connect(standardLoginWindow, &StandardLoginWindow::performLogin, this, &PortalAuthenticator::onPerformNormalLogin); |  | ||||||
|     connect(standardLoginWindow, &StandardLoginWindow::rejected, this, &PortalAuthenticator::onLoginWindowRejected); |  | ||||||
|     connect(standardLoginWindow, &StandardLoginWindow::finished, this, &PortalAuthenticator::onLoginWindowFinished); |  | ||||||
|  |  | ||||||
|     standardLoginWindow->show(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void PortalAuthenticator::onPerformNormalLogin(const QString &username, const QString &password) |  | ||||||
| { |  | ||||||
|     standardLoginWindow->setProcessing(true); |  | ||||||
|     fetchConfig(username, password); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void PortalAuthenticator::onLoginWindowRejected() |  | ||||||
| { |  | ||||||
|     emitFail(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void PortalAuthenticator::onLoginWindowFinished() |  | ||||||
| { |  | ||||||
|     delete standardLoginWindow; |  | ||||||
|     standardLoginWindow = nullptr; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void PortalAuthenticator::samlAuth() |  | ||||||
| { |  | ||||||
|     LOGI << "Trying to perform SAML login with saml-method " << preloginResponse.samlMethod(); |  | ||||||
|  |  | ||||||
|     auto *loginWindow = new SAMLLoginWindow; |  | ||||||
|  |  | ||||||
|     connect(loginWindow, &SAMLLoginWindow::success, [this, loginWindow](const QMap<QString, QString> samlResult) { |  | ||||||
|         this->onSAMLLoginSuccess(samlResult); |  | ||||||
|         loginWindow->deleteLater(); |  | ||||||
|     }); |  | ||||||
|     connect(loginWindow, &SAMLLoginWindow::fail, [this, loginWindow](const QString &code, const QString msg) { |  | ||||||
|         this->onSAMLLoginFail(code, msg); |  | ||||||
|         loginWindow->deleteLater(); |  | ||||||
|     }); |  | ||||||
|     connect(loginWindow, &SAMLLoginWindow::rejected, [this, loginWindow]() { |  | ||||||
|         this->onLoginWindowRejected(); |  | ||||||
|         loginWindow->deleteLater(); |  | ||||||
|     }); |  | ||||||
|  |  | ||||||
|     loginWindow->login(preloginResponse.samlMethod(), preloginResponse.samlRequest(), preloginUrl); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void PortalAuthenticator::onSAMLLoginSuccess(const QMap<QString, QString> samlResult) |  | ||||||
| { |  | ||||||
|     if (samlResult.contains("preloginCookie")) { |  | ||||||
|         LOGI << "SAML login succeeded, got the prelogin-cookie"; |  | ||||||
|     } else { |  | ||||||
|         LOGI << "SAML login succeeded, got the portal-userauthcookie"; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fetchConfig(samlResult.value("username"), "", samlResult.value("preloginCookie"), samlResult.value("userAuthCookie")); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void PortalAuthenticator::onSAMLLoginFail(const QString &code, const QString &msg) |  | ||||||
| { |  | ||||||
|     if (code == "ERR002" && attempts < MAX_ATTEMPTS) { |  | ||||||
|         LOGI << "Failed to authenticate, trying to re-authenticate..."; |  | ||||||
|         authenticate(); |  | ||||||
|     } else { |  | ||||||
|         emitFail(msg); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void PortalAuthenticator::fetchConfig(QString username, QString password, QString preloginCookie, QString userAuthCookie) |  | ||||||
| { |  | ||||||
|     LoginParams loginParams { clientos }; |  | ||||||
|     loginParams.setServer(portal); |  | ||||||
|     loginParams.setUser(username); |  | ||||||
|     loginParams.setPassword(password); |  | ||||||
|     loginParams.setPreloginCookie(preloginCookie); |  | ||||||
|     loginParams.setUserAuthCookie(userAuthCookie); |  | ||||||
|  |  | ||||||
|     // Save the username and password for future use. |  | ||||||
|     this->username = username; |  | ||||||
|     this->password = password; |  | ||||||
|  |  | ||||||
|     LOGI << "Fetching the portal config from " << configUrl; |  | ||||||
|  |  | ||||||
|     auto *reply = createRequest(configUrl, loginParams.toUtf8()); |  | ||||||
|     connect(reply, &QNetworkReply::finished, this, &PortalAuthenticator::onFetchConfigFinished); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void PortalAuthenticator::onFetchConfigFinished() |  | ||||||
| { |  | ||||||
|     QNetworkReply *reply = qobject_cast<QNetworkReply*>(sender()); |  | ||||||
|  |  | ||||||
|     if (reply->error()) { |  | ||||||
|         LOGE << QString("Failed to fetch the portal config from %1, %2").arg(configUrl).arg(reply->errorString()); |  | ||||||
|  |  | ||||||
|         // Login failed, enable the fields of the normal login window |  | ||||||
|         if (standardLoginWindow) { |  | ||||||
|             standardLoginWindow->setProcessing(false); |  | ||||||
|             openMessageBox("Portal login failed.", "Please check your credentials and try again."); |  | ||||||
|         } else if (isAutoLogin) { |  | ||||||
|             isAutoLogin = false; |  | ||||||
|             normalAuth(); |  | ||||||
|         } else { |  | ||||||
|             emit portalConfigFailed("Failed to fetch the portal config."); |  | ||||||
|         } |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     LOGI << "Fetch the portal config succeeded."; |  | ||||||
|     PortalConfigResponse response = PortalConfigResponse::parse(reply->readAll()); |  | ||||||
|  |  | ||||||
|     // Add the username & password to the response object |  | ||||||
|     response.setUsername(username); |  | ||||||
|     response.setPassword(password); |  | ||||||
|  |  | ||||||
|     // Close the login window |  | ||||||
|     if (standardLoginWindow) { |  | ||||||
|         LOGI << "Closing the StandardLoginWindow..."; |  | ||||||
|  |  | ||||||
|         standardLoginWindow->close(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     emit success(response, preloginResponse.region()); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void PortalAuthenticator::emitFail(const QString& msg) |  | ||||||
| { |  | ||||||
|     emit fail(msg); |  | ||||||
| } |  | ||||||
| @@ -1,60 +0,0 @@ | |||||||
| #ifndef PORTALAUTHENTICATOR_H |  | ||||||
| #define PORTALAUTHENTICATOR_H |  | ||||||
|  |  | ||||||
| #include <QtCore/QObject> |  | ||||||
|  |  | ||||||
| #include "portalconfigresponse.h" |  | ||||||
| #include "standardloginwindow.h" |  | ||||||
| #include "samlloginwindow.h" |  | ||||||
| #include "preloginresponse.h" |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class PortalAuthenticator : public QObject |  | ||||||
| { |  | ||||||
|     Q_OBJECT |  | ||||||
| public: |  | ||||||
|     explicit PortalAuthenticator(const QString& portal, const QString& clientos); |  | ||||||
|     ~PortalAuthenticator(); |  | ||||||
|  |  | ||||||
|     void authenticate(); |  | ||||||
|  |  | ||||||
| signals: |  | ||||||
|     void success(const PortalConfigResponse response, const QString region); |  | ||||||
|     void fail(const QString& msg); |  | ||||||
|     void preloginFailed(const QString& msg); |  | ||||||
|     void portalConfigFailed(const QString msg); |  | ||||||
|  |  | ||||||
| private slots: |  | ||||||
|     void onPreloginFinished(); |  | ||||||
|     void onPerformNormalLogin(const QString &username, const QString &password); |  | ||||||
|     void onLoginWindowRejected(); |  | ||||||
|     void onLoginWindowFinished(); |  | ||||||
|     void onSAMLLoginSuccess(const QMap<QString, QString> samlResult); |  | ||||||
|     void onSAMLLoginFail(const QString &code, const QString &msg); |  | ||||||
|     void onFetchConfigFinished(); |  | ||||||
|  |  | ||||||
| private: |  | ||||||
|     static const auto MAX_ATTEMPTS{ 5 }; |  | ||||||
|  |  | ||||||
|     QString portal; |  | ||||||
|     QString clientos; |  | ||||||
|     QString preloginUrl; |  | ||||||
|     QString configUrl; |  | ||||||
|     QString username; |  | ||||||
|     QString password; |  | ||||||
|  |  | ||||||
|     int attempts{ 0 }; |  | ||||||
|     PreloginResponse preloginResponse; |  | ||||||
|  |  | ||||||
|     bool isAutoLogin{ false }; |  | ||||||
|  |  | ||||||
|     StandardLoginWindow *standardLoginWindow { nullptr }; |  | ||||||
|  |  | ||||||
|     void tryAutoLogin(); |  | ||||||
|     void normalAuth(); |  | ||||||
|     void samlAuth(); |  | ||||||
|     void fetchConfig(QString username, QString password, QString preloginCookie = "", QString userAuthCookie = ""); |  | ||||||
|     void emitFail(const QString& msg = ""); |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| #endif // PORTALAUTHENTICATOR_H |  | ||||||
| @@ -1,174 +0,0 @@ | |||||||
| #include <QtCore/QXmlStreamReader> |  | ||||||
| #include <plog/Log.h> |  | ||||||
|  |  | ||||||
| #include "portalconfigresponse.h" |  | ||||||
|  |  | ||||||
| QString PortalConfigResponse::xmlUserAuthCookie = "portal-userauthcookie"; |  | ||||||
| QString PortalConfigResponse::xmlPrelogonUserAuthCookie = "portal-prelogonuserauthcookie"; |  | ||||||
| QString PortalConfigResponse::xmlGateways = "gateways"; |  | ||||||
|  |  | ||||||
| PortalConfigResponse::PortalConfigResponse() |  | ||||||
| { |  | ||||||
| } |  | ||||||
|  |  | ||||||
| PortalConfigResponse::~PortalConfigResponse() |  | ||||||
| { |  | ||||||
| } |  | ||||||
|  |  | ||||||
| PortalConfigResponse PortalConfigResponse::parse(const QByteArray xml) |  | ||||||
| { |  | ||||||
|     LOGI << "Start parsing the portal configuration..."; |  | ||||||
|  |  | ||||||
|     QXmlStreamReader xmlReader(xml); |  | ||||||
|     PortalConfigResponse response; |  | ||||||
|     response.setRawResponse(xml); |  | ||||||
|  |  | ||||||
|     while (!xmlReader.atEnd()) { |  | ||||||
|         xmlReader.readNextStartElement(); |  | ||||||
|  |  | ||||||
|         QString name = xmlReader.name().toString(); |  | ||||||
|  |  | ||||||
|         if (name == xmlUserAuthCookie) { |  | ||||||
|             LOGI << "Start reading " << name; |  | ||||||
|             response.setUserAuthCookie(xmlReader.readElementText()); |  | ||||||
|         } else if (name == xmlPrelogonUserAuthCookie) { |  | ||||||
|             LOGI << "Start reading " << name; |  | ||||||
|             response.setPrelogonUserAuthCookie(xmlReader.readElementText()); |  | ||||||
|         } else if (name == xmlGateways) { |  | ||||||
|             response.setAllGateways(parseGateways(xmlReader)); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     LOGI << "Finished parsing portal configuration."; |  | ||||||
|  |  | ||||||
|     return response; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| const QByteArray PortalConfigResponse::rawResponse() const |  | ||||||
| { |  | ||||||
|     return m_rawResponse; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| const QString &PortalConfigResponse::username() const |  | ||||||
| { |  | ||||||
|     return m_username; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| QString PortalConfigResponse::password() const |  | ||||||
| { |  | ||||||
|     return m_password; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| QList<GPGateway> PortalConfigResponse::parseGateways(QXmlStreamReader &xmlReader) |  | ||||||
| { |  | ||||||
|     LOGI << "Start parsing the gateways from portal configuration..."; |  | ||||||
|  |  | ||||||
|     QList<GPGateway> gateways; |  | ||||||
|  |  | ||||||
|     while (xmlReader.name() != "external"){ |  | ||||||
|         xmlReader.readNext(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     while (xmlReader.name() != "list"){ |  | ||||||
|         xmlReader.readNext(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     while (xmlReader.name() != xmlGateways || !xmlReader.isEndElement()) { |  | ||||||
|         xmlReader.readNext(); |  | ||||||
|         // Parse the gateways -> external -> list -> entry |  | ||||||
|         if (xmlReader.name() == "entry" && xmlReader.isStartElement()) { |  | ||||||
|             GPGateway g; |  | ||||||
|             parseGateway(xmlReader, g); |  | ||||||
|             gateways.append(g); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     LOGI << "Finished parsing the gateways."; |  | ||||||
|  |  | ||||||
|     return gateways; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void PortalConfigResponse::parseGateway(QXmlStreamReader &reader, GPGateway &gateway) { |  | ||||||
|     LOGI << "Start parsing gateway..."; |  | ||||||
|  |  | ||||||
|     auto finished = false; |  | ||||||
|     while (!finished) { |  | ||||||
|         if (reader.name() == "entry" && reader.isStartElement()) { |  | ||||||
|             auto address = reader.attributes().value("name").toString(); |  | ||||||
|             gateway.setAddress(address); |  | ||||||
|         } else if (reader.name() == "description" && reader.isStartElement()) { // gateway name |  | ||||||
|             gateway.setName(reader.readElementText()); |  | ||||||
|         } else if (reader.name() == "priority-rule" && reader.isStartElement()) { // priority rules |  | ||||||
|             parsePriorityRule(reader, gateway); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         auto result = reader.readNext(); |  | ||||||
|         finished = result == QXmlStreamReader::Invalid || (reader.name() == "entry" && reader.isEndElement()); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void PortalConfigResponse::parsePriorityRule(QXmlStreamReader &reader, GPGateway &gateway) { |  | ||||||
|     LOGI << "Start parsing priority rule..."; |  | ||||||
|  |  | ||||||
|     QMap<QString, int> priorityRules; |  | ||||||
|     auto finished = false; |  | ||||||
|  |  | ||||||
|     while (!finished) { |  | ||||||
|         // Parse the priority-rule -> entry |  | ||||||
|         if (reader.name() == "entry" && reader.isStartElement()) { |  | ||||||
|             auto ruleName = reader.attributes().value("name").toString(); |  | ||||||
|             // move to the priority value |  | ||||||
|             while (reader.readNextStartElement()) { |  | ||||||
|                 if (reader.name() == "priority") { |  | ||||||
|                     auto priority = reader.readElementText().toInt(); |  | ||||||
|                     priorityRules.insert(ruleName, priority); |  | ||||||
|                     break; |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         auto result = reader.readNext(); |  | ||||||
|         finished = result == QXmlStreamReader::Invalid || (reader.name() == "priority-rule" && reader.isEndElement()); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     gateway.setPriorityRules(priorityRules); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| QString PortalConfigResponse::userAuthCookie() const |  | ||||||
| { |  | ||||||
|     return m_userAuthCookie; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| QList<GPGateway> PortalConfigResponse::allGateways() const |  | ||||||
| { |  | ||||||
|     return m_gateways; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void PortalConfigResponse::setAllGateways(QList<GPGateway> gateways) |  | ||||||
| { |  | ||||||
|     m_gateways = gateways; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void PortalConfigResponse::setRawResponse(const QByteArray response) |  | ||||||
| { |  | ||||||
|     m_rawResponse = response; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void PortalConfigResponse::setUsername(const QString username) |  | ||||||
| { |  | ||||||
|     m_username = username; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void PortalConfigResponse::setPassword(const QString password) |  | ||||||
| { |  | ||||||
|     m_password = password; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void PortalConfigResponse::setUserAuthCookie(const QString cookie) |  | ||||||
| { |  | ||||||
|     m_userAuthCookie = cookie; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void PortalConfigResponse::setPrelogonUserAuthCookie(const QString cookie) |  | ||||||
| { |  | ||||||
|     m_prelogonAuthCookie = cookie; |  | ||||||
| } |  | ||||||
| @@ -1,51 +0,0 @@ | |||||||
| #ifndef PORTALCONFIGRESPONSE_H |  | ||||||
| #define PORTALCONFIGRESPONSE_H |  | ||||||
|  |  | ||||||
| #include <QtCore/QString> |  | ||||||
| #include <QtCore/QList> |  | ||||||
| #include <QtCore/QXmlStreamReader> |  | ||||||
|  |  | ||||||
| #include "gpgateway.h" |  | ||||||
|  |  | ||||||
| class PortalConfigResponse |  | ||||||
| { |  | ||||||
| public: |  | ||||||
|     PortalConfigResponse(); |  | ||||||
|     ~PortalConfigResponse(); |  | ||||||
|  |  | ||||||
|     static PortalConfigResponse parse(const QByteArray xml); |  | ||||||
|  |  | ||||||
|     const QByteArray rawResponse() const; |  | ||||||
|     const QString &username() const; |  | ||||||
|     QString password() const; |  | ||||||
|     QString userAuthCookie() const; |  | ||||||
|     QList<GPGateway> allGateways() const; |  | ||||||
|     void setAllGateways(QList<GPGateway> gateways); |  | ||||||
|  |  | ||||||
|     void setUsername(const QString username); |  | ||||||
|     void setPassword(const QString password); |  | ||||||
|  |  | ||||||
| private: |  | ||||||
|     static QString xmlUserAuthCookie; |  | ||||||
|     static QString xmlPrelogonUserAuthCookie; |  | ||||||
|     static QString xmlGateways; |  | ||||||
|  |  | ||||||
|     QByteArray m_rawResponse; |  | ||||||
|     QString m_username; |  | ||||||
|     QString m_password; |  | ||||||
|     QString m_userAuthCookie; |  | ||||||
|     QString m_prelogonAuthCookie; |  | ||||||
|  |  | ||||||
|     QList<GPGateway> m_gateways; |  | ||||||
|  |  | ||||||
|     void setRawResponse(const QByteArray response); |  | ||||||
|     void setUserAuthCookie(const QString cookie); |  | ||||||
|     void setPrelogonUserAuthCookie(const QString cookie); |  | ||||||
|  |  | ||||||
|     static QList<GPGateway> parseGateways(QXmlStreamReader &xmlReader); |  | ||||||
|     static void parseGateway(QXmlStreamReader &reader, GPGateway &gateway); |  | ||||||
|     static void parsePriorityRule(QXmlStreamReader &reader, GPGateway &gateway); |  | ||||||
|  |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| #endif // PORTALCONFIGRESPONSE_H |  | ||||||
| @@ -1,100 +0,0 @@ | |||||||
| #include <QtCore/QXmlStreamReader> |  | ||||||
| #include <QtCore/QMap> |  | ||||||
| #include <plog/Log.h> |  | ||||||
|  |  | ||||||
| #include "preloginresponse.h" |  | ||||||
|  |  | ||||||
| QString PreloginResponse::xmlAuthMessage = "authentication-message"; |  | ||||||
| QString PreloginResponse::xmlLabelUsername = "username-label"; |  | ||||||
| QString PreloginResponse::xmlLabelPassword = "password-label"; |  | ||||||
| QString PreloginResponse::xmlSamlMethod = "saml-auth-method"; |  | ||||||
| QString PreloginResponse::xmlSamlRequest = "saml-request"; |  | ||||||
| QString PreloginResponse::xmlRegion = "region"; |  | ||||||
|  |  | ||||||
| PreloginResponse::PreloginResponse() |  | ||||||
| { |  | ||||||
|     add(xmlAuthMessage, ""); |  | ||||||
|     add(xmlLabelUsername, ""); |  | ||||||
|     add(xmlLabelPassword, ""); |  | ||||||
|     add(xmlSamlMethod, ""); |  | ||||||
|     add(xmlSamlRequest, ""); |  | ||||||
|     add(xmlRegion, ""); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| PreloginResponse PreloginResponse::parse(const QByteArray& xml) |  | ||||||
| { |  | ||||||
|     LOGI << "Start parsing the prelogin response..."; |  | ||||||
|  |  | ||||||
|     QXmlStreamReader xmlReader(xml); |  | ||||||
|     PreloginResponse response; |  | ||||||
|     response.setRawResponse(xml); |  | ||||||
|  |  | ||||||
|     while (!xmlReader.atEnd()) { |  | ||||||
|         xmlReader.readNextStartElement(); |  | ||||||
|         QString name = xmlReader.name().toString(); |  | ||||||
|         if (response.has(name)) { |  | ||||||
|             response.add(name, xmlReader.readElementText()); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     return response; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| const QByteArray& PreloginResponse::rawResponse() const |  | ||||||
| { |  | ||||||
|     return _rawResponse; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| QString PreloginResponse::authMessage() const |  | ||||||
| { |  | ||||||
|     return resultMap.value(xmlAuthMessage); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| QString PreloginResponse::labelUsername() const |  | ||||||
| { |  | ||||||
|     return resultMap.value(xmlLabelUsername); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| QString PreloginResponse::labelPassword() const |  | ||||||
| { |  | ||||||
|     return resultMap.value(xmlLabelPassword); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| QString PreloginResponse::samlMethod() const |  | ||||||
| { |  | ||||||
|     return resultMap.value(xmlSamlMethod); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| QString PreloginResponse::samlRequest() const |  | ||||||
| { |  | ||||||
|     return QByteArray::fromBase64(resultMap.value(xmlSamlRequest).toUtf8()); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| QString PreloginResponse::region() const |  | ||||||
| { |  | ||||||
|     return resultMap.value(xmlRegion); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| bool PreloginResponse::hasSamlAuthFields() const |  | ||||||
| { |  | ||||||
|     return !samlMethod().isEmpty() && !samlRequest().isEmpty(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| bool PreloginResponse::hasNormalAuthFields() const |  | ||||||
| { |  | ||||||
|     return !labelUsername().isEmpty() && !labelPassword().isEmpty(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void PreloginResponse::setRawResponse(const QByteArray response) |  | ||||||
| { |  | ||||||
|     _rawResponse = response; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| bool PreloginResponse::has(const QString name) const |  | ||||||
| { |  | ||||||
|     return resultMap.contains(name); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void PreloginResponse::add(const QString name, const QString value) |  | ||||||
| { |  | ||||||
|     resultMap.insert(name, value); |  | ||||||
| } |  | ||||||
| @@ -1,41 +0,0 @@ | |||||||
| #ifndef PRELOGINRESPONSE_H |  | ||||||
| #define PRELOGINRESPONSE_H |  | ||||||
|  |  | ||||||
| #include <QtCore/QString> |  | ||||||
| #include <QtCore/QMap> |  | ||||||
|  |  | ||||||
| class PreloginResponse |  | ||||||
| { |  | ||||||
| public: |  | ||||||
|     PreloginResponse(); |  | ||||||
|  |  | ||||||
|     static PreloginResponse parse(const QByteArray& xml); |  | ||||||
|  |  | ||||||
|     const QByteArray& rawResponse() const; |  | ||||||
|     QString authMessage() const; |  | ||||||
|     QString labelUsername() const; |  | ||||||
|     QString labelPassword() const; |  | ||||||
|     QString samlMethod() const; |  | ||||||
|     QString samlRequest() const; |  | ||||||
|     QString region() const; |  | ||||||
|  |  | ||||||
|     bool hasSamlAuthFields() const; |  | ||||||
|     bool hasNormalAuthFields() const; |  | ||||||
|  |  | ||||||
| private: |  | ||||||
|     static QString xmlAuthMessage; |  | ||||||
|     static QString xmlLabelUsername; |  | ||||||
|     static QString xmlLabelPassword; |  | ||||||
|     static QString xmlSamlMethod; |  | ||||||
|     static QString xmlSamlRequest; |  | ||||||
|     static QString xmlRegion; |  | ||||||
|  |  | ||||||
|     QMap<QString, QString> resultMap; |  | ||||||
|     QByteArray _rawResponse; |  | ||||||
|  |  | ||||||
|     void setRawResponse(const QByteArray response); |  | ||||||
|     void add(const QString name, const QString value); |  | ||||||
|     bool has(const QString name) const; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| #endif // PRELOGINRESPONSE_H |  | ||||||
| Before Width: | Height: | Size: 1.2 KiB | 
| Before Width: | Height: | Size: 993 B | 
| @@ -1,11 +0,0 @@ | |||||||
| <RCC> |  | ||||||
|     <qresource prefix="/images"> |  | ||||||
|         <file alias="logo.svg">com.yuezk.qt.gpclient.svg</file> |  | ||||||
|         <file>connected.png</file> |  | ||||||
|         <file>pending.png</file> |  | ||||||
|         <file>not_connected.png</file> |  | ||||||
|         <file>radio_unselected.png</file> |  | ||||||
|         <file>radio_selected.png</file> |  | ||||||
|         <file>settings_icon.png</file> |  | ||||||
|     </qresource> |  | ||||||
| </RCC> |  | ||||||
| @@ -1,136 +0,0 @@ | |||||||
| #include <QtWidgets/QVBoxLayout> |  | ||||||
| #include <QtWebEngineWidgets/QWebEngineProfile> |  | ||||||
| #include <QtWebEngineWidgets/QWebEngineView> |  | ||||||
| #include <QWebEngineCookieStore> |  | ||||||
| #include <plog/Log.h> |  | ||||||
|  |  | ||||||
| #include "samlloginwindow.h" |  | ||||||
|  |  | ||||||
| SAMLLoginWindow::SAMLLoginWindow(QWidget *parent) |  | ||||||
|     : QDialog(parent) |  | ||||||
|     , webView(new EnhancedWebView(this)) |  | ||||||
| { |  | ||||||
|     setWindowTitle("GlobalProtect Login"); |  | ||||||
|     setModal(true); |  | ||||||
|     resize(700, 550); |  | ||||||
|  |  | ||||||
|     QVBoxLayout *verticalLayout = new QVBoxLayout(this); |  | ||||||
|     webView->setUrl(QUrl("about:blank")); |  | ||||||
|     webView->setAttribute(Qt::WA_DeleteOnClose); |  | ||||||
|     verticalLayout->addWidget(webView); |  | ||||||
|  |  | ||||||
|     webView->initialize(); |  | ||||||
|     connect(webView, &EnhancedWebView::responseReceived, this, &SAMLLoginWindow::onResponseReceived); |  | ||||||
|     connect(webView, &EnhancedWebView::loadFinished, this, &SAMLLoginWindow::onLoadFinished); |  | ||||||
|  |  | ||||||
|     // Show the login window automatically when exceeds the MAX_WAIT_TIME |  | ||||||
|     QTimer::singleShot(MAX_WAIT_TIME, this, [this]() { |  | ||||||
|         if (failed) { |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
|         LOGI << "MAX_WAIT_TIME exceeded, display the login window."; |  | ||||||
|         this->show(); |  | ||||||
|     }); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void SAMLLoginWindow::closeEvent(QCloseEvent *event) |  | ||||||
| { |  | ||||||
|     event->accept(); |  | ||||||
|     reject(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void SAMLLoginWindow::login(const QString samlMethod, const QString samlRequest, const QString preloginUrl) |  | ||||||
| { |  | ||||||
|     webView->page()->profile()->cookieStore()->deleteSessionCookies(); |  | ||||||
|  |  | ||||||
|     if (samlMethod == "POST") { |  | ||||||
|         webView->setHtml(samlRequest, preloginUrl); |  | ||||||
|     } else if (samlMethod == "REDIRECT") { |  | ||||||
|         LOGI << "Redirect to " << samlRequest; |  | ||||||
|         webView->load(samlRequest); |  | ||||||
|     } else { |  | ||||||
|         LOGE << "Unknown saml-auth-method expected POST or REDIRECT, got " << samlMethod; |  | ||||||
|         failed = true; |  | ||||||
|         emit fail("ERR001", "Unknown saml-auth-method, got " + samlMethod); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void SAMLLoginWindow::onResponseReceived(QJsonObject params) |  | ||||||
| { |  | ||||||
|     const auto type = params.value("type").toString(); |  | ||||||
|     // Skip non-document response |  | ||||||
|     if (type != "Document") { |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     auto response = params.value("response").toObject(); |  | ||||||
|     auto headers = response.value("headers").toObject(); |  | ||||||
|  |  | ||||||
|     LOGI << "Trying to receive authentication cookie from " << response.value("url").toString(); |  | ||||||
|  |  | ||||||
|     const auto username = headers.value("saml-username").toString(); |  | ||||||
|     const auto preloginCookie = headers.value("prelogin-cookie").toString(); |  | ||||||
|     const auto userAuthCookie = headers.value("portal-userauthcookie").toString(); |  | ||||||
|  |  | ||||||
|     this->checkSamlResult(username, preloginCookie, userAuthCookie); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void SAMLLoginWindow::checkSamlResult(QString username, QString preloginCookie, QString userAuthCookie) |  | ||||||
| { |  | ||||||
|     LOGI << "Checking the authentication result..."; |  | ||||||
|  |  | ||||||
|     if (!username.isEmpty()) { |  | ||||||
|         samlResult.insert("username", username); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     if (!preloginCookie.isEmpty()) { |  | ||||||
|         samlResult.insert("preloginCookie", preloginCookie); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     if (!userAuthCookie.isEmpty()) { |  | ||||||
|         samlResult.insert("userAuthCookie", userAuthCookie); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // Check the SAML result |  | ||||||
|     if (samlResult.contains("username") |  | ||||||
|             && (samlResult.contains("preloginCookie") || samlResult.contains("userAuthCookie"))) { |  | ||||||
|         LOGI << "Got the SAML authentication information successfully. " |  | ||||||
|              << "username: " << samlResult.value("username") |  | ||||||
|              << ", preloginCookie: " << samlResult.value("preloginCookie") |  | ||||||
|              << ", userAuthCookie: " << samlResult.value("userAuthCookie"); |  | ||||||
|  |  | ||||||
|         emit success(samlResult); |  | ||||||
|         accept(); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void SAMLLoginWindow::onLoadFinished() |  | ||||||
| { |  | ||||||
|      LOGI << "Load finished " << webView->page()->url().toString(); |  | ||||||
|      webView->page()->toHtml([this] (const QString &html) { this->handleHtml(html); }); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void SAMLLoginWindow::handleHtml(const QString &html) |  | ||||||
| { |  | ||||||
|     // try to check the html body and extract from there |  | ||||||
|     const auto samlAuthStatus = parseTag("saml-auth-status", html); |  | ||||||
|  |  | ||||||
|     if (samlAuthStatus == "1") { |  | ||||||
|         const auto preloginCookie = parseTag("prelogin-cookie", html); |  | ||||||
|         const auto username = parseTag("saml-username", html); |  | ||||||
|         const auto userAuthCookie = parseTag("portal-userauthcookie", html); |  | ||||||
|  |  | ||||||
|         checkSamlResult(username, preloginCookie, userAuthCookie); |  | ||||||
|     } else if (samlAuthStatus == "-1") { |  | ||||||
|         LOGI << "SAML authentication failed..."; |  | ||||||
|         failed = true; |  | ||||||
|         emit fail("ERR002", "Authentication failed, please try again."); |  | ||||||
|     } else { |  | ||||||
|         show(); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| QString SAMLLoginWindow::parseTag(const QString &tag, const QString &html) { |  | ||||||
|     const QRegularExpression expression(QString("<%1>(.*)</%1>").arg(tag)); |  | ||||||
|     return expression.match(html).captured(1); |  | ||||||
| } |  | ||||||
| @@ -1,41 +0,0 @@ | |||||||
| #ifndef SAMLLOGINWINDOW_H |  | ||||||
| #define SAMLLOGINWINDOW_H |  | ||||||
|  |  | ||||||
| #include <QtCore/QMap> |  | ||||||
| #include <QtGui/QCloseEvent> |  | ||||||
| #include <QtWidgets/QDialog> |  | ||||||
|  |  | ||||||
| #include "enhancedwebview.h" |  | ||||||
|  |  | ||||||
| class SAMLLoginWindow : public QDialog |  | ||||||
| { |  | ||||||
|     Q_OBJECT |  | ||||||
|  |  | ||||||
| public: |  | ||||||
|     explicit SAMLLoginWindow(QWidget *parent = nullptr); |  | ||||||
|  |  | ||||||
|     void login(const QString samlMethod, const QString samlRequest, const QString preloginUrl); |  | ||||||
|  |  | ||||||
| signals: |  | ||||||
|     void success(QMap<QString, QString> samlResult); |  | ||||||
|     void fail(const QString code, const QString msg); |  | ||||||
|  |  | ||||||
| private slots: |  | ||||||
|     void onResponseReceived(QJsonObject params); |  | ||||||
|     void onLoadFinished(); |  | ||||||
|     void checkSamlResult(QString username, QString preloginCookie, QString userAuthCookie); |  | ||||||
|  |  | ||||||
| private: |  | ||||||
|     static const auto MAX_WAIT_TIME { 10 * 1000 }; |  | ||||||
|  |  | ||||||
|     bool failed { false }; |  | ||||||
|     EnhancedWebView *webView { nullptr }; |  | ||||||
|     QMap<QString, QString> samlResult; |  | ||||||
|  |  | ||||||
|     void closeEvent(QCloseEvent *event); |  | ||||||
|     void handleHtml(const QString &html); |  | ||||||
|  |  | ||||||
|     static QString parseTag(const QString &tag, const QString &html); |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| #endif // SAMLLOGINWINDOW_H |  | ||||||
| Before Width: | Height: | Size: 1.1 KiB | 
| @@ -1,42 +0,0 @@ | |||||||
| #include "settingsdialog.h" |  | ||||||
| #include "ui_settingsdialog.h" |  | ||||||
|  |  | ||||||
| SettingsDialog::SettingsDialog(QWidget *parent) : |  | ||||||
|     QDialog(parent), |  | ||||||
|     ui(new Ui::SettingsDialog) |  | ||||||
| { |  | ||||||
|     ui->setupUi(this); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| SettingsDialog::~SettingsDialog() |  | ||||||
| { |  | ||||||
|     delete ui; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void SettingsDialog::setExtraArgs(QString extraArgs) |  | ||||||
| { |  | ||||||
|     ui->extraArgsInput->setPlainText(extraArgs); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| QString SettingsDialog::extraArgs() |  | ||||||
| { |  | ||||||
|     return ui->extraArgsInput->toPlainText().trimmed(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void SettingsDialog::setClientos(QString clientos) |  | ||||||
| { |  | ||||||
|     ui->clientosInput->setText(clientos); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| QString SettingsDialog::clientos() |  | ||||||
| { |  | ||||||
|     return ui->clientosInput->text(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void SettingsDialog::setOsVersion(QString osVersion) { |  | ||||||
|     ui->osVersionInput->setText(osVersion); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| QString SettingsDialog::osVersion() { |  | ||||||
|     return ui->osVersionInput->text(); |  | ||||||
| } |  | ||||||
| @@ -1,31 +0,0 @@ | |||||||
| #ifndef SETTINGSDIALOG_H |  | ||||||
| #define SETTINGSDIALOG_H |  | ||||||
|  |  | ||||||
| #include <QtWidgets/QDialog> |  | ||||||
|  |  | ||||||
| namespace Ui { |  | ||||||
| class SettingsDialog; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| class SettingsDialog : public QDialog |  | ||||||
| { |  | ||||||
|     Q_OBJECT |  | ||||||
|  |  | ||||||
| public: |  | ||||||
|     explicit SettingsDialog(QWidget *parent = nullptr); |  | ||||||
|     ~SettingsDialog(); |  | ||||||
|  |  | ||||||
|     void setExtraArgs(QString extraArgs); |  | ||||||
|     QString extraArgs(); |  | ||||||
|  |  | ||||||
|     void setClientos(QString clientos); |  | ||||||
|     QString clientos(); |  | ||||||
|  |  | ||||||
|     void setOsVersion(QString osVersion); |  | ||||||
|     QString osVersion(); |  | ||||||
|  |  | ||||||
| private: |  | ||||||
|     Ui::SettingsDialog *ui; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| #endif // SETTINGSDIALOG_H |  | ||||||
| @@ -1,117 +0,0 @@ | |||||||
| <?xml version="1.0" encoding="UTF-8"?> |  | ||||||
| <ui version="4.0"> |  | ||||||
|  <class>SettingsDialog</class> |  | ||||||
|  <widget class="QDialog" name="SettingsDialog"> |  | ||||||
|   <property name="geometry"> |  | ||||||
|    <rect> |  | ||||||
|     <x>0</x> |  | ||||||
|     <y>0</y> |  | ||||||
|     <width>488</width> |  | ||||||
|     <height>220</height> |  | ||||||
|    </rect> |  | ||||||
|   </property> |  | ||||||
|   <property name="sizePolicy"> |  | ||||||
|    <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> |  | ||||||
|     <horstretch>0</horstretch> |  | ||||||
|     <verstretch>0</verstretch> |  | ||||||
|    </sizepolicy> |  | ||||||
|   </property> |  | ||||||
|   <property name="windowTitle"> |  | ||||||
|    <string>Settings</string> |  | ||||||
|   </property> |  | ||||||
|   <property name="windowIcon"> |  | ||||||
|    <iconset resource="resources.qrc"> |  | ||||||
|     <normaloff>:/images/connected.png</normaloff>:/images/connected.png</iconset> |  | ||||||
|   </property> |  | ||||||
|   <layout class="QFormLayout" name="formLayout_3"> |  | ||||||
|    <item row="0" column="0"> |  | ||||||
|     <widget class="QLabel" name="label"> |  | ||||||
|      <property name="text"> |  | ||||||
|       <string>Custom Parameters:</string> |  | ||||||
|      </property> |  | ||||||
|     </widget> |  | ||||||
|    </item> |  | ||||||
|    <item row="0" column="1"> |  | ||||||
|     <widget class="QPlainTextEdit" name="extraArgsInput"> |  | ||||||
|      <property name="readOnly"> |  | ||||||
|       <bool>true</bool> |  | ||||||
|      </property> |  | ||||||
|      <property name="placeholderText"> |  | ||||||
|       <string>The configuration has been moved to "/etc/gpservice/gp.conf"</string> |  | ||||||
|      </property> |  | ||||||
|     </widget> |  | ||||||
|    </item> |  | ||||||
|    <item row="1" column="0"> |  | ||||||
|     <widget class="QLabel" name="label_2"> |  | ||||||
|      <property name="text"> |  | ||||||
|       <string>clientos:</string> |  | ||||||
|      </property> |  | ||||||
|     </widget> |  | ||||||
|    </item> |  | ||||||
|    <item row="1" column="1"> |  | ||||||
|     <widget class="QLineEdit" name="clientosInput"> |  | ||||||
|      <property name="placeholderText"> |  | ||||||
|       <string>e.g., Windows</string> |  | ||||||
|      </property> |  | ||||||
|     </widget> |  | ||||||
|    </item> |  | ||||||
|    <item row="3" column="1"> |  | ||||||
|     <widget class="QDialogButtonBox" name="buttonBox"> |  | ||||||
|      <property name="orientation"> |  | ||||||
|       <enum>Qt::Horizontal</enum> |  | ||||||
|      </property> |  | ||||||
|      <property name="standardButtons"> |  | ||||||
|       <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> |  | ||||||
|      </property> |  | ||||||
|     </widget> |  | ||||||
|    </item> |  | ||||||
|    <item row="2" column="1"> |  | ||||||
|     <widget class="QLineEdit" name="osVersionInput"/> |  | ||||||
|    </item> |  | ||||||
|    <item row="2" column="0"> |  | ||||||
|     <widget class="QLabel" name="label_3"> |  | ||||||
|      <property name="text"> |  | ||||||
|       <string>os-version:</string> |  | ||||||
|      </property> |  | ||||||
|     </widget> |  | ||||||
|    </item> |  | ||||||
|   </layout> |  | ||||||
|  </widget> |  | ||||||
|  <resources> |  | ||||||
|   <include location="resources.qrc"/> |  | ||||||
|  </resources> |  | ||||||
|  <connections> |  | ||||||
|   <connection> |  | ||||||
|    <sender>buttonBox</sender> |  | ||||||
|    <signal>accepted()</signal> |  | ||||||
|    <receiver>SettingsDialog</receiver> |  | ||||||
|    <slot>accept()</slot> |  | ||||||
|    <hints> |  | ||||||
|     <hint type="sourcelabel"> |  | ||||||
|      <x>248</x> |  | ||||||
|      <y>254</y> |  | ||||||
|     </hint> |  | ||||||
|     <hint type="destinationlabel"> |  | ||||||
|      <x>157</x> |  | ||||||
|      <y>274</y> |  | ||||||
|     </hint> |  | ||||||
|    </hints> |  | ||||||
|   </connection> |  | ||||||
|   <connection> |  | ||||||
|    <sender>buttonBox</sender> |  | ||||||
|    <signal>rejected()</signal> |  | ||||||
|    <receiver>SettingsDialog</receiver> |  | ||||||
|    <slot>reject()</slot> |  | ||||||
|    <hints> |  | ||||||
|     <hint type="sourcelabel"> |  | ||||||
|      <x>316</x> |  | ||||||
|      <y>260</y> |  | ||||||
|     </hint> |  | ||||||
|     <hint type="destinationlabel"> |  | ||||||
|      <x>286</x> |  | ||||||
|      <y>274</y> |  | ||||||
|     </hint> |  | ||||||
|    </hints> |  | ||||||
|   </connection> |  | ||||||
|  </connections> |  | ||||||
| </ui> |  | ||||||
| @@ -1,60 +0,0 @@ | |||||||
| #include <QtGui/QCloseEvent> |  | ||||||
|  |  | ||||||
| #include "standardloginwindow.h" |  | ||||||
| #include "ui_standardloginwindow.h" |  | ||||||
| #include "gphelper.h" |  | ||||||
|  |  | ||||||
| using namespace gpclient::helper; |  | ||||||
|  |  | ||||||
| StandardLoginWindow::StandardLoginWindow(const QString &portalAddress, const QString &labelUsername, |  | ||||||
|                                          const QString &labelPassword, const QString &authMessage) : |  | ||||||
|         QDialog(nullptr), |  | ||||||
|         ui(new Ui::StandardLoginWindow) { |  | ||||||
|     ui->setupUi(this); |  | ||||||
|     ui->portalAddress->setText(portalAddress); |  | ||||||
|     ui->username->setPlaceholderText(labelUsername); |  | ||||||
|     ui->password->setPlaceholderText(labelPassword); |  | ||||||
|     ui->authMessage->setText(authMessage); |  | ||||||
|  |  | ||||||
|     autocomplete(); |  | ||||||
|  |  | ||||||
|     setWindowTitle("GlobalProtect Login"); |  | ||||||
|     setFixedSize(width(), height()); |  | ||||||
|     setModal(true); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void StandardLoginWindow::autocomplete() { |  | ||||||
|     QString username, password; |  | ||||||
|     settings::secureGet("username", username); |  | ||||||
|     settings::secureGet("password", password); |  | ||||||
|  |  | ||||||
|     if (!username.isEmpty() && !password.isEmpty()) { |  | ||||||
|         ui->username->setText(username); |  | ||||||
|         ui->password->setText(password); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void StandardLoginWindow::setProcessing(bool isProcessing) { |  | ||||||
|     ui->username->setReadOnly(isProcessing); |  | ||||||
|     ui->password->setReadOnly(isProcessing); |  | ||||||
|     ui->loginButton->setDisabled(isProcessing); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void StandardLoginWindow::on_loginButton_clicked() { |  | ||||||
|     const QString username = ui->username->text().trimmed(); |  | ||||||
|     const QString password = ui->password->text().trimmed(); |  | ||||||
|  |  | ||||||
|     if (username.isEmpty() || password.isEmpty()) { |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     settings::secureSave("username", username); |  | ||||||
|     settings::secureSave("password", password); |  | ||||||
|  |  | ||||||
|     emit performLogin(username, password); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void StandardLoginWindow::closeEvent(QCloseEvent *event) { |  | ||||||
|     event->accept(); |  | ||||||
|     reject(); |  | ||||||
| } |  | ||||||
| @@ -1,34 +0,0 @@ | |||||||
| #ifndef STANDARDLOGINWINDOW_H |  | ||||||
| #define STANDARDLOGINWINDOW_H |  | ||||||
|  |  | ||||||
| #include <QtWidgets/QDialog> |  | ||||||
|  |  | ||||||
| namespace Ui { |  | ||||||
|     class StandardLoginWindow; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| class StandardLoginWindow : public QDialog { |  | ||||||
| Q_OBJECT |  | ||||||
|  |  | ||||||
| public: |  | ||||||
|     explicit StandardLoginWindow(const QString &portalAddress, const QString &labelUsername, |  | ||||||
|                                  const QString &labelPassword, const QString &authMessage); |  | ||||||
|  |  | ||||||
|     void setProcessing(bool isProcessing); |  | ||||||
|  |  | ||||||
| private slots: |  | ||||||
|  |  | ||||||
|     void on_loginButton_clicked(); |  | ||||||
|  |  | ||||||
| signals: |  | ||||||
|  |  | ||||||
|     void performLogin(QString username, QString password); |  | ||||||
|  |  | ||||||
| private: |  | ||||||
|     Ui::StandardLoginWindow *ui; |  | ||||||
|  |  | ||||||
|     void closeEvent(QCloseEvent *event); |  | ||||||
|     void autocomplete(); |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| #endif // STANDARDLOGINWINDOW_H |  | ||||||
| @@ -1,148 +0,0 @@ | |||||||
| <?xml version="1.0" encoding="UTF-8"?> |  | ||||||
| <ui version="4.0"> |  | ||||||
|  <class>StandardLoginWindow</class> |  | ||||||
|  <widget class="QDialog" name="StandardLoginWindow"> |  | ||||||
|   <property name="geometry"> |  | ||||||
|    <rect> |  | ||||||
|     <x>0</x> |  | ||||||
|     <y>0</y> |  | ||||||
|     <width>255</width> |  | ||||||
|     <height>269</height> |  | ||||||
|    </rect> |  | ||||||
|   </property> |  | ||||||
|   <property name="sizePolicy"> |  | ||||||
|    <sizepolicy hsizetype="Preferred" vsizetype="Minimum"> |  | ||||||
|     <horstretch>0</horstretch> |  | ||||||
|     <verstretch>0</verstretch> |  | ||||||
|    </sizepolicy> |  | ||||||
|   </property> |  | ||||||
|   <property name="cursor"> |  | ||||||
|    <cursorShape>ArrowCursor</cursorShape> |  | ||||||
|   </property> |  | ||||||
|   <property name="windowTitle"> |  | ||||||
|    <string>Login</string> |  | ||||||
|   </property> |  | ||||||
|   <property name="modal"> |  | ||||||
|    <bool>true</bool> |  | ||||||
|   </property> |  | ||||||
|   <layout class="QVBoxLayout" name="verticalLayout_5"> |  | ||||||
|    <item> |  | ||||||
|     <layout class="QVBoxLayout" name="verticalLayout_4" stretch="1,0,0"> |  | ||||||
|      <item> |  | ||||||
|       <layout class="QVBoxLayout" name="verticalLayout"> |  | ||||||
|        <item> |  | ||||||
|         <widget class="QLabel" name="label"> |  | ||||||
|          <property name="font"> |  | ||||||
|           <font> |  | ||||||
|            <pointsize>20</pointsize> |  | ||||||
|           </font> |  | ||||||
|          </property> |  | ||||||
|          <property name="text"> |  | ||||||
|           <string>Login</string> |  | ||||||
|          </property> |  | ||||||
|          <property name="alignment"> |  | ||||||
|           <set>Qt::AlignCenter</set> |  | ||||||
|          </property> |  | ||||||
|         </widget> |  | ||||||
|        </item> |  | ||||||
|        <item> |  | ||||||
|         <widget class="QLabel" name="authMessage"> |  | ||||||
|          <property name="enabled"> |  | ||||||
|           <bool>true</bool> |  | ||||||
|          </property> |  | ||||||
|          <property name="sizePolicy"> |  | ||||||
|           <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> |  | ||||||
|            <horstretch>0</horstretch> |  | ||||||
|            <verstretch>2</verstretch> |  | ||||||
|           </sizepolicy> |  | ||||||
|          </property> |  | ||||||
|          <property name="text"> |  | ||||||
|           <string>Please enter the login credentials</string> |  | ||||||
|          </property> |  | ||||||
|          <property name="alignment"> |  | ||||||
|           <set>Qt::AlignCenter</set> |  | ||||||
|          </property> |  | ||||||
|         </widget> |  | ||||||
|        </item> |  | ||||||
|       </layout> |  | ||||||
|      </item> |  | ||||||
|      <item> |  | ||||||
|       <layout class="QVBoxLayout" name="verticalLayout_2"> |  | ||||||
|        <property name="spacing"> |  | ||||||
|         <number>0</number> |  | ||||||
|        </property> |  | ||||||
|        <property name="leftMargin"> |  | ||||||
|         <number>6</number> |  | ||||||
|        </property> |  | ||||||
|        <item> |  | ||||||
|         <widget class="QLabel" name="portalLabel"> |  | ||||||
|          <property name="sizePolicy"> |  | ||||||
|           <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> |  | ||||||
|            <horstretch>0</horstretch> |  | ||||||
|            <verstretch>0</verstretch> |  | ||||||
|           </sizepolicy> |  | ||||||
|          </property> |  | ||||||
|          <property name="text"> |  | ||||||
|           <string>Portal:</string> |  | ||||||
|          </property> |  | ||||||
|          <property name="margin"> |  | ||||||
|           <number>0</number> |  | ||||||
|          </property> |  | ||||||
|         </widget> |  | ||||||
|        </item> |  | ||||||
|        <item> |  | ||||||
|         <widget class="QLabel" name="portalAddress"> |  | ||||||
|          <property name="sizePolicy"> |  | ||||||
|           <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> |  | ||||||
|            <horstretch>0</horstretch> |  | ||||||
|            <verstretch>0</verstretch> |  | ||||||
|           </sizepolicy> |  | ||||||
|          </property> |  | ||||||
|          <property name="text"> |  | ||||||
|           <string>vpn.example.com</string> |  | ||||||
|          </property> |  | ||||||
|         </widget> |  | ||||||
|        </item> |  | ||||||
|       </layout> |  | ||||||
|      </item> |  | ||||||
|      <item> |  | ||||||
|       <layout class="QVBoxLayout" name="verticalLayout_3"> |  | ||||||
|        <item> |  | ||||||
|         <widget class="QLineEdit" name="username"> |  | ||||||
|          <property name="placeholderText"> |  | ||||||
|           <string>Username</string> |  | ||||||
|          </property> |  | ||||||
|         </widget> |  | ||||||
|        </item> |  | ||||||
|        <item> |  | ||||||
|         <widget class="QLineEdit" name="password"> |  | ||||||
|          <property name="text"> |  | ||||||
|           <string/> |  | ||||||
|          </property> |  | ||||||
|          <property name="echoMode"> |  | ||||||
|           <enum>QLineEdit::Password</enum> |  | ||||||
|          </property> |  | ||||||
|          <property name="placeholderText"> |  | ||||||
|           <string>Password</string> |  | ||||||
|          </property> |  | ||||||
|          <property name="clearButtonEnabled"> |  | ||||||
|           <bool>false</bool> |  | ||||||
|          </property> |  | ||||||
|         </widget> |  | ||||||
|        </item> |  | ||||||
|        <item> |  | ||||||
|         <widget class="QPushButton" name="loginButton"> |  | ||||||
|          <property name="text"> |  | ||||||
|           <string>Login</string> |  | ||||||
|          </property> |  | ||||||
|         </widget> |  | ||||||
|        </item> |  | ||||||
|       </layout> |  | ||||||
|      </item> |  | ||||||
|     </layout> |  | ||||||
|    </item> |  | ||||||
|   </layout> |  | ||||||
|  </widget> |  | ||||||
|  <resources/> |  | ||||||
|  <connections/> |  | ||||||
| </ui> |  | ||||||
| @@ -1,24 +0,0 @@ | |||||||
| #ifndef VPN_H |  | ||||||
| #define VPN_H |  | ||||||
| #include <QtCore/QObject> |  | ||||||
| #include <QtCore/QString> |  | ||||||
|  |  | ||||||
| class IVpn |  | ||||||
| { |  | ||||||
| public: |  | ||||||
|     virtual ~IVpn() = default; |  | ||||||
|  |  | ||||||
|     virtual void connect(const QString &preferredServer, const QList<QString> &servers, const QString &username, const QString &passwd) = 0; |  | ||||||
|     virtual void disconnect() = 0; |  | ||||||
|     virtual int status() = 0; |  | ||||||
|  |  | ||||||
| // signals: // SIGNALS |  | ||||||
| //     virtual void connected(); |  | ||||||
| //     virtual void disconnected(); |  | ||||||
| //     virtual void error(const QString &errorMessage); |  | ||||||
| //     virtual void logAvailable(const QString &log); |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| Q_DECLARE_INTERFACE(IVpn, "IVpn") // define this out of namespace scope |  | ||||||
|  |  | ||||||
| #endif |  | ||||||
| @@ -1,13 +0,0 @@ | |||||||
| #include "vpn_dbus.h" |  | ||||||
|  |  | ||||||
| void VpnDbus::connect(const QString &preferredServer, const QList<QString> &servers, const QString &username, const QString &passwd) { |  | ||||||
|     inner->connect(preferredServer, username, passwd); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void VpnDbus::disconnect() { |  | ||||||
|     inner->disconnect(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| int VpnDbus::status() { |  | ||||||
|     return inner->status(); |  | ||||||
| } |  | ||||||
| @@ -1,33 +0,0 @@ | |||||||
| #ifndef VPN_DBUS_H |  | ||||||
| #define VPN_DBUS_H |  | ||||||
| #include "vpn.h" |  | ||||||
| #include "gpserviceinterface.h" |  | ||||||
|  |  | ||||||
| class VpnDbus : public QObject, public IVpn |  | ||||||
| { |  | ||||||
|   Q_OBJECT |  | ||||||
|   Q_INTERFACES(IVpn) |  | ||||||
|  |  | ||||||
| private: |  | ||||||
|   com::yuezk::qt::GPService *inner; |  | ||||||
|  |  | ||||||
| public: |  | ||||||
|   VpnDbus(QObject *parent) : QObject(parent) { |  | ||||||
|     inner = new com::yuezk::qt::GPService("com.yuezk.qt.GPService", "/", QDBusConnection::systemBus(), this); |  | ||||||
|     QObject::connect(inner, &com::yuezk::qt::GPService::connected, this, &VpnDbus::connected); |  | ||||||
|     QObject::connect(inner, &com::yuezk::qt::GPService::disconnected, this, &VpnDbus::disconnected); |  | ||||||
|     QObject::connect(inner, &com::yuezk::qt::GPService::error, this, &VpnDbus::error); |  | ||||||
|     QObject::connect(inner, &com::yuezk::qt::GPService::logAvailable, this, &VpnDbus::logAvailable); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   void connect(const QString &preferredServer, const QList<QString> &servers, const QString &username, const QString &passwd); |  | ||||||
|   void disconnect(); |  | ||||||
|   int status(); |  | ||||||
|  |  | ||||||
| signals: // SIGNALS |  | ||||||
|   void connected(); |  | ||||||
|   void disconnected(); |  | ||||||
|   void error(QString errorMessage); |  | ||||||
|   void logAvailable(QString log); |  | ||||||
| }; |  | ||||||
| #endif |  | ||||||
| @@ -1,24 +0,0 @@ | |||||||
| #include "vpn_json.h" |  | ||||||
| #include <QTextStream> |  | ||||||
| #include <QJsonDocument> |  | ||||||
| #include <QJsonObject> |  | ||||||
| #include <QJsonArray> |  | ||||||
|  |  | ||||||
| void VpnJson::connect(const QString &preferredServer, const QList<QString> &servers, const QString &username, const QString &passwd) { |  | ||||||
|     QJsonArray sl; |  | ||||||
|     for (const QString &srv : servers) { |  | ||||||
|       sl.push_back(QJsonValue(srv)); |  | ||||||
|     } |  | ||||||
|     QJsonObject j; |  | ||||||
|     j["server"] = preferredServer; |  | ||||||
|     j["availableServers"] = sl; |  | ||||||
|     j["cookie"] = passwd; |  | ||||||
|     QTextStream(stdout) << QJsonDocument(j).toJson(QJsonDocument::Compact) << "\n"; |  | ||||||
|     emit connected(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void VpnJson::disconnect() { /* nop */ } |  | ||||||
|  |  | ||||||
| int VpnJson::status() { |  | ||||||
|     return 4; // disconnected |  | ||||||
| } |  | ||||||
| @@ -1,23 +0,0 @@ | |||||||
| #ifndef VPN_JSON_H |  | ||||||
| #define VPN_JSON_H |  | ||||||
| #include "vpn.h" |  | ||||||
|  |  | ||||||
| class VpnJson : public QObject, public IVpn |  | ||||||
| { |  | ||||||
|   Q_OBJECT |  | ||||||
|   Q_INTERFACES(IVpn) |  | ||||||
|  |  | ||||||
| public: |  | ||||||
|   VpnJson(QObject *parent) : QObject(parent) {} |  | ||||||
|  |  | ||||||
|   void connect(const QString &preferredServer, const QList<QString> &servers, const QString &username, const QString &passwd); |  | ||||||
|   void disconnect(); |  | ||||||
|   int status(); |  | ||||||
|  |  | ||||||
| signals: // SIGNALS |  | ||||||
|   void connected(); |  | ||||||
|   void disconnected(); |  | ||||||
|   void error(const QString &errorMessage); |  | ||||||
|   void logAvailable(const QString &log); |  | ||||||
| }; |  | ||||||
| #endif |  | ||||||
| @@ -1,83 +0,0 @@ | |||||||
| include("${CMAKE_SOURCE_DIR}/cmake/Add3rdParty.cmake") |  | ||||||
|  |  | ||||||
| project(GPService) |  | ||||||
|  |  | ||||||
| set(gpservice_GENERATED_SOURCES) |  | ||||||
|  |  | ||||||
| execute_process(COMMAND logname OUTPUT_VARIABLE CMAKE_LOGNAME) |  | ||||||
| string(STRIP "${CMAKE_LOGNAME}" CMAKE_LOGNAME) |  | ||||||
|  |  | ||||||
| message(STATUS "CMAKE_LOGNAME: ${CMAKE_LOGNAME}") |  | ||||||
|  |  | ||||||
| configure_file(dbus/com.yuezk.qt.GPService.conf.in dbus/com.yuezk.qt.GPService.conf) |  | ||||||
| configure_file(dbus/com.yuezk.qt.GPService.service.in dbus/com.yuezk.qt.GPService.service) |  | ||||||
| configure_file(systemd/gpservice.service.in systemd/gpservice.service) |  | ||||||
|  |  | ||||||
| # generate the dbus xml definition |  | ||||||
| qt5_generate_dbus_interface( |  | ||||||
|     gpservice.h |  | ||||||
|     ${CMAKE_BINARY_DIR}/com.yuezk.qt.GPService.xml |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| # generate dbus adaptor |  | ||||||
| qt5_add_dbus_adaptor( |  | ||||||
|     gpservice_GENERATED_SOURCES |  | ||||||
|     ${CMAKE_BINARY_DIR}/com.yuezk.qt.GPService.xml |  | ||||||
|     gpservice.h |  | ||||||
|     GPService |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| add_executable(gpservice |  | ||||||
|     gpservice.h |  | ||||||
|     gpservice.cpp |  | ||||||
|     main.cpp |  | ||||||
|     ${gpservice_GENERATED_SOURCES} |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| add_3rdparty( |  | ||||||
|     SingleApplication |  | ||||||
|     GIT_REPOSITORY https://github.com/itay-grudev/SingleApplication.git |  | ||||||
|     GIT_TAG v3.3.0 |  | ||||||
|     CMAKE_ARGS |  | ||||||
|         -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} |  | ||||||
|         -DCMAKE_CXX_FLAGS_RELEASE=${CMAKE_CXX_FLAGS_RELEASE} |  | ||||||
|         -DCMAKE_FIND_ROOT_PATH=${CMAKE_FIND_ROOT_PATH} |  | ||||||
|         -DCMAKE_PREFIX_PATH=$ENV{CMAKE_PREFIX_PATH} |  | ||||||
|         -DQAPPLICATION_CLASS=QCoreApplication |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| ExternalProject_Get_Property(SingleApplication-${PROJECT_NAME} SOURCE_DIR BINARY_DIR) |  | ||||||
|  |  | ||||||
| set(SingleApplication_INCLUDE_DIR ${SOURCE_DIR}) |  | ||||||
| set(SingleApplication_LIBRARY ${BINARY_DIR}/libSingleApplication.a) |  | ||||||
|  |  | ||||||
| add_dependencies(gpservice SingleApplication-${PROJECT_NAME}) |  | ||||||
|  |  | ||||||
| target_include_directories(gpservice PRIVATE |  | ||||||
|     ${CMAKE_CURRENT_SOURCE_DIR} |  | ||||||
|     ${CMAKE_CURRENT_BINARY_DIR} |  | ||||||
|     ${SingleApplication_INCLUDE_DIR} |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| target_link_libraries(gpservice |  | ||||||
|     ${SingleApplication_LIBRARY} |  | ||||||
|     Qt5::Core |  | ||||||
|     Qt5::Network |  | ||||||
|     Qt5::DBus |  | ||||||
|     QtSignals |  | ||||||
|     inih |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| target_compile_definitions(gpservice PUBLIC QAPPLICATION_CLASS=QCoreApplication) |  | ||||||
|  |  | ||||||
| install(TARGETS gpservice DESTINATION bin) |  | ||||||
| install(FILES "${CMAKE_CURRENT_BINARY_DIR}/dbus/com.yuezk.qt.GPService.conf" DESTINATION share/dbus-1/system.d ) |  | ||||||
| install(FILES "${CMAKE_CURRENT_BINARY_DIR}/dbus/com.yuezk.qt.GPService.service" DESTINATION share/dbus-1/system-services) |  | ||||||
| install(FILES "gp.conf" DESTINATION /etc/gpservice) |  | ||||||
|  |  | ||||||
| if("$ENV{DEBIAN_PACKAGE}") |  | ||||||
|     # Install the systemd unit files to /lib/systemd/system for debian package |  | ||||||
|     install(FILES "${CMAKE_CURRENT_BINARY_DIR}/systemd/gpservice.service" DESTINATION /lib/systemd/system) |  | ||||||
| else() |  | ||||||
|     install(FILES "${CMAKE_CURRENT_BINARY_DIR}/systemd/gpservice.service" DESTINATION lib/systemd/system) |  | ||||||
| endif() |  | ||||||
| @@ -1,18 +0,0 @@ | |||||||
| <?xml version="1.0" encoding="UTF-8"?> |  | ||||||
| <!DOCTYPE busconfig PUBLIC |  | ||||||
| "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN" |  | ||||||
| "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd"> |  | ||||||
| <busconfig> |  | ||||||
|         <policy user="root"> |  | ||||||
|                 <allow own="com.yuezk.qt.GPService"/> |  | ||||||
|         </policy> |  | ||||||
|  |  | ||||||
|         <policy context="default"> |  | ||||||
|                 <allow send_destination="com.yuezk.qt.GPService" |  | ||||||
|                         send_interface="com.yuezk.qt.GPService" |  | ||||||
|                         /> |  | ||||||
|                 <allow send_destination="com.yuezk.qt.GPService" |  | ||||||
|                         send_interface="org.freedesktop.DBus.Introspectable" |  | ||||||
|                         /> |  | ||||||
|         </policy> |  | ||||||
| </busconfig> |  | ||||||
| @@ -1,5 +0,0 @@ | |||||||
| [D-BUS Service] |  | ||||||
| Name=com.yuezk.qt.GPService |  | ||||||
| Exec=@CMAKE_INSTALL_PREFIX@/bin/gpservice |  | ||||||
| User=root |  | ||||||
| SystemdService=gpservice.service |  | ||||||
| @@ -1,17 +0,0 @@ | |||||||
| # Configuration file for GlobalProtect-openconnect |  | ||||||
| # |  | ||||||
| # Description: |  | ||||||
| # |  | ||||||
| # Each section is a VPN gateway address, and [*] is a special section that defines the default configuration. |  | ||||||
| # See https://github.com/yuezk/GlobalProtect-openconnect/wiki/Configuration for more details. |  | ||||||
| # |  | ||||||
| # Example: |  | ||||||
| # |  | ||||||
| # [*] |  | ||||||
| # openconnect-args=<value> |  | ||||||
| # |  | ||||||
| # [vpn1.company.com] |  | ||||||
| # openconnect-args=--script=/path/to/vpnc-script |  | ||||||
|  |  | ||||||
| [*] |  | ||||||
| openconnect-args= |  | ||||||
| @@ -1,229 +0,0 @@ | |||||||
| #include <QtCore/QFileInfo> |  | ||||||
| #include <QtCore/QDateTime> |  | ||||||
| #include <QtCore/QVariant> |  | ||||||
| #include <QtCore/QRegularExpression> |  | ||||||
| #include <QtCore/QRegularExpressionMatch> |  | ||||||
| #include <QtDBus/QtDBus> |  | ||||||
|  |  | ||||||
| #include "INIReader.h" |  | ||||||
| #include "gpservice.h" |  | ||||||
| #include "gpserviceadaptor.h" |  | ||||||
|  |  | ||||||
| GPService::GPService(QObject *parent) |  | ||||||
|     : QObject(parent) |  | ||||||
|     , openconnect(new QProcess) |  | ||||||
| { |  | ||||||
|     // Register the DBus service |  | ||||||
|     new GPServiceAdaptor(this); |  | ||||||
|     QDBusConnection dbus = QDBusConnection::systemBus(); |  | ||||||
|     dbus.registerObject("/", this); |  | ||||||
|     dbus.registerService("com.yuezk.qt.GPService"); |  | ||||||
|  |  | ||||||
|     // Setup the openconnect process |  | ||||||
|     QObject::connect(openconnect, &QProcess::started, this, &GPService::onProcessStarted); |  | ||||||
|     QObject::connect(openconnect, &QProcess::errorOccurred, this, &GPService::onProcessError); |  | ||||||
|     QObject::connect(openconnect, &QProcess::readyReadStandardOutput, this, &GPService::onProcessStdout); |  | ||||||
|     QObject::connect(openconnect, &QProcess::readyReadStandardError, this, &GPService::onProcessStderr); |  | ||||||
|     QObject::connect(openconnect, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, &GPService::onProcessFinished); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| GPService::~GPService() |  | ||||||
| { |  | ||||||
|     delete openconnect; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| QString GPService::findBinary() |  | ||||||
| { |  | ||||||
|     for (auto& binaryPath : binaryPaths) { |  | ||||||
|         if (QFileInfo::exists(binaryPath)) { |  | ||||||
|             return binaryPath; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     return nullptr; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| QString GPService::extraOpenconnectArgs(const QString &gateway) |  | ||||||
| { |  | ||||||
|     INIReader reader("/etc/gpservice/gp.conf"); |  | ||||||
|  |  | ||||||
|     if (reader.ParseError() < 0) { |  | ||||||
|         return ""; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     std::string defaultArgs = reader.Get("*", "openconnect-args", ""); |  | ||||||
|     std::string extraArgs = reader.Get(gateway.toStdString(), "openconnect-args", defaultArgs); |  | ||||||
|  |  | ||||||
|     return QString::fromStdString(extraArgs); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /* Port from https://github.com/qt/qtbase/blob/11d1dcc6e263c5059f34b44d531c9ccdf7c0b1d6/src/corelib/io/qprocess.cpp#L2115 */ |  | ||||||
| QStringList GPService::splitCommand(const QString &command) |  | ||||||
| { |  | ||||||
|     QStringList args; |  | ||||||
|     QString tmp; |  | ||||||
|     int quoteCount = 0; |  | ||||||
|     bool inQuote = false; |  | ||||||
|  |  | ||||||
|     // handle quoting. tokens can be surrounded by double quotes |  | ||||||
|     // "hello world". three consecutive double quotes represent |  | ||||||
|     // the quote character itself. |  | ||||||
|     for (int i = 0; i < command.size(); ++i) { |  | ||||||
|         if (command.at(i) == QLatin1Char('"')) { |  | ||||||
|             ++quoteCount; |  | ||||||
|             if (quoteCount == 3) { |  | ||||||
|                 // third consecutive quote |  | ||||||
|                 quoteCount = 0; |  | ||||||
|                 tmp += command.at(i); |  | ||||||
|             } |  | ||||||
|             continue; |  | ||||||
|         } |  | ||||||
|         if (quoteCount) { |  | ||||||
|             if (quoteCount == 1) |  | ||||||
|                 inQuote = !inQuote; |  | ||||||
|             quoteCount = 0; |  | ||||||
|         } |  | ||||||
|         if (!inQuote && command.at(i).isSpace()) { |  | ||||||
|             if (!tmp.isEmpty()) { |  | ||||||
|                 args += tmp; |  | ||||||
|                 tmp.clear(); |  | ||||||
|             } |  | ||||||
|         } else { |  | ||||||
|             tmp += command.at(i); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     if (!tmp.isEmpty()) |  | ||||||
|         args += tmp; |  | ||||||
|  |  | ||||||
|     return args; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void GPService::quit() |  | ||||||
| { |  | ||||||
|     if (openconnect->state() == QProcess::NotRunning) { |  | ||||||
|         exit(0); |  | ||||||
|     } else { |  | ||||||
|         aboutToQuit = true; |  | ||||||
|         openconnect->terminate(); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void GPService::connect(QString server, QString username, QString passwd) |  | ||||||
| { |  | ||||||
|     if (vpnStatus != GPService::VpnNotConnected) { |  | ||||||
|         log("VPN status is: " + QVariant::fromValue(vpnStatus).toString()); |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     QString bin = findBinary(); |  | ||||||
|     if (bin == nullptr) { |  | ||||||
|         log("Could not find openconnect binary, make sure openconnect is installed, exiting."); |  | ||||||
|         emit error("The OpenConect CLI was not found, make sure it has been installed!"); |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     if (!isValidVersion(bin)) { |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     const QString extraArgs = extraOpenconnectArgs(server); |  | ||||||
|     log(QString("Got extra OpenConnect args for server: %1, %2").arg(server, extraArgs.isEmpty() ? "<empty>" : extraArgs)); |  | ||||||
|  |  | ||||||
|     QStringList args; |  | ||||||
|     args << QCoreApplication::arguments().mid(1) |  | ||||||
|          << "--protocol=gp" |  | ||||||
|          << splitCommand(extraArgs) |  | ||||||
|          << "-u" << username |  | ||||||
|          << "--cookie-on-stdin" |  | ||||||
|          << server; |  | ||||||
|  |  | ||||||
|     log("Start process with arguments: " + args.join(", ")); |  | ||||||
|  |  | ||||||
|     openconnect->start(bin, args); |  | ||||||
|     openconnect->write((passwd + "\n").toUtf8()); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| bool GPService::isValidVersion(QString &bin) { |  | ||||||
|     QProcess p; |  | ||||||
|     p.start(bin, QStringList("--version")); |  | ||||||
|     p.waitForFinished(); |  | ||||||
|     QString output = p.readAllStandardError() + p.readAllStandardOutput(); |  | ||||||
|  |  | ||||||
|     QRegularExpression re("v(\\d+).*?(\\s|\\n)"); |  | ||||||
|     QRegularExpressionMatch match = re.match(output); |  | ||||||
|  |  | ||||||
|     if (match.hasMatch()) { |  | ||||||
|         log("Output of `openconnect --version`: " + output); |  | ||||||
|  |  | ||||||
|         QString fullVersion = match.captured(0); |  | ||||||
|         QString majorVersion = match.captured(1); |  | ||||||
|  |  | ||||||
|         if (majorVersion.toInt() < 8) { |  | ||||||
|             emit error("The OpenConnect version must greater than v8.0.0, got " + fullVersion); |  | ||||||
|             return false; |  | ||||||
|         } |  | ||||||
|     } else { |  | ||||||
|         log("Failed to parse the OpenConnect version from " + output); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     return true; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void GPService::disconnect() |  | ||||||
| { |  | ||||||
|     if (openconnect->state() != QProcess::NotRunning) { |  | ||||||
|         vpnStatus = GPService::VpnDisconnecting; |  | ||||||
|         openconnect->terminate(); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| int GPService::status() |  | ||||||
| { |  | ||||||
|     return vpnStatus; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void GPService::onProcessStarted() |  | ||||||
| { |  | ||||||
|     log("Openconnect started successfully, PID=" + QString::number(openconnect->processId())); |  | ||||||
|     vpnStatus = GPService::VpnConnecting; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void GPService::onProcessError(QProcess::ProcessError error) |  | ||||||
| { |  | ||||||
|     log("Error occurred: " + QVariant::fromValue(error).toString()); |  | ||||||
|     vpnStatus = GPService::VpnNotConnected; |  | ||||||
|     emit disconnected(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void GPService::onProcessStdout() |  | ||||||
| { |  | ||||||
|     QString output = openconnect->readAllStandardOutput(); |  | ||||||
|  |  | ||||||
|     log(output); |  | ||||||
|     if (output.indexOf("Connected as") >= 0 || |  | ||||||
|         output.indexOf("Configured as") >= 0 || |  | ||||||
|         output.indexOf("Configurado como") >= 0) { |  | ||||||
|         vpnStatus = GPService::VpnConnected; |  | ||||||
|         emit connected(); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void GPService::onProcessStderr() |  | ||||||
| { |  | ||||||
|     log(openconnect->readAllStandardError()); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void GPService::onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus) |  | ||||||
| { |  | ||||||
|     log("Openconnect process exited with code " + QString::number(exitCode) + " and exit status " + QVariant::fromValue(exitStatus).toString()); |  | ||||||
|     vpnStatus = GPService::VpnNotConnected; |  | ||||||
|     emit disconnected(); |  | ||||||
|  |  | ||||||
|     if (aboutToQuit) { |  | ||||||
|         exit(0); |  | ||||||
|     }; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void GPService::log(QString msg) |  | ||||||
| { |  | ||||||
|     emit logAvailable(msg); |  | ||||||
| } |  | ||||||
| @@ -1,62 +0,0 @@ | |||||||
| #ifndef GLOBALPROTECTSERVICE_H |  | ||||||
| #define GLOBALPROTECTSERVICE_H |  | ||||||
|  |  | ||||||
| #include <QtCore/QObject> |  | ||||||
| #include <QtCore/QProcess> |  | ||||||
|  |  | ||||||
| static QList<QString> binaryPaths = QList<QString>() << |  | ||||||
|     "/usr/local/bin/openconnect" << |  | ||||||
|      "/usr/local/sbin/openconnect" << |  | ||||||
|      "/usr/bin/openconnect" << |  | ||||||
|      "/usr/sbin/openconnect" << |  | ||||||
|      "/opt/bin/openconnect" << |  | ||||||
|      "/opt/sbin/openconnect"; |  | ||||||
|  |  | ||||||
| class GPService : public QObject |  | ||||||
| { |  | ||||||
|     Q_OBJECT |  | ||||||
|     Q_CLASSINFO("D-Bus Interface", "com.yuezk.qt.GPService") |  | ||||||
| public: |  | ||||||
|     explicit GPService(QObject *parent = nullptr); |  | ||||||
|     ~GPService(); |  | ||||||
|  |  | ||||||
|     void quit(); |  | ||||||
|  |  | ||||||
|     enum VpnStatus { |  | ||||||
|         VpnNotConnected, |  | ||||||
|         VpnConnecting, |  | ||||||
|         VpnConnected, |  | ||||||
|         VpnDisconnecting, |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
| signals: |  | ||||||
|     void connected(); |  | ||||||
|     void disconnected(); |  | ||||||
|     void error(QString errorMessage); |  | ||||||
|     void logAvailable(QString log); |  | ||||||
|  |  | ||||||
| public slots: |  | ||||||
|     void connect(QString server, QString username, QString passwd); |  | ||||||
|     void disconnect(); |  | ||||||
|     int status(); |  | ||||||
|  |  | ||||||
| private slots: |  | ||||||
|     void onProcessStarted(); |  | ||||||
|     void onProcessError(QProcess::ProcessError error); |  | ||||||
|     void onProcessStdout(); |  | ||||||
|     void onProcessStderr(); |  | ||||||
|     void onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus); |  | ||||||
|  |  | ||||||
| private: |  | ||||||
|     QProcess *openconnect; |  | ||||||
|     bool aboutToQuit = false; |  | ||||||
|     int vpnStatus = GPService::VpnNotConnected; |  | ||||||
|  |  | ||||||
|     void log(QString msg); |  | ||||||
|     bool isValidVersion(QString &bin); |  | ||||||
|     static QString findBinary(); |  | ||||||
|     static QString extraOpenconnectArgs(const QString &gateway); |  | ||||||
|     static QStringList splitCommand(const QString &command); |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| #endif // GLOBALPROTECTSERVICE_H |  | ||||||
| @@ -1,27 +0,0 @@ | |||||||
| #include <QtDBus/QtDBus> |  | ||||||
|  |  | ||||||
| #include "gpservice.h" |  | ||||||
| #include "singleapplication.h" |  | ||||||
| #include "sigwatch.h" |  | ||||||
|  |  | ||||||
| int main(int argc, char *argv[]) |  | ||||||
| { |  | ||||||
|     SingleApplication app(argc, argv); |  | ||||||
|  |  | ||||||
|     if (!QDBusConnection::systemBus().isConnected()) { |  | ||||||
|         qWarning("Cannot connect to the D-Bus session bus.\n" |  | ||||||
|                  "Please check your system settings and try again.\n"); |  | ||||||
|         return 1; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     GPService service; |  | ||||||
|  |  | ||||||
|     UnixSignalWatcher sigwatch; |  | ||||||
|     sigwatch.watchForSignal(SIGINT); |  | ||||||
|     sigwatch.watchForSignal(SIGTERM); |  | ||||||
|     sigwatch.watchForSignal(SIGQUIT); |  | ||||||
|     sigwatch.watchForSignal(SIGHUP); |  | ||||||
|     QObject::connect(&sigwatch, &UnixSignalWatcher::unixSignal, &service, &GPService::quit); |  | ||||||
|  |  | ||||||
|     return app.exec(); |  | ||||||
| } |  | ||||||
| @@ -1,11 +0,0 @@ | |||||||
| [Unit] |  | ||||||
| Description=GlobalProtect openconnect DBus service |  | ||||||
|  |  | ||||||
| [Service] |  | ||||||
| Environment="LANG=en_US.utf8" |  | ||||||
| Type=dbus |  | ||||||
| BusName=com.yuezk.qt.GPService |  | ||||||
| ExecStart=@CMAKE_INSTALL_PREFIX@/bin/gpservice |  | ||||||
|  |  | ||||||
| [Install] |  | ||||||
| WantedBy=multi-user.target |  | ||||||
							
								
								
									
										674
									
								
								LICENSE
									
									
									
									
									
								
							
							
						
						| @@ -1,674 +0,0 @@ | |||||||
|                     GNU GENERAL PUBLIC LICENSE |  | ||||||
|                        Version 3, 29 June 2007 |  | ||||||
|  |  | ||||||
|  Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/> |  | ||||||
|  Everyone is permitted to copy and distribute verbatim copies |  | ||||||
|  of this license document, but changing it is not allowed. |  | ||||||
|  |  | ||||||
|                             Preamble |  | ||||||
|  |  | ||||||
|   The GNU General Public License is a free, copyleft license for |  | ||||||
| software and other kinds of works. |  | ||||||
|  |  | ||||||
|   The licenses for most software and other practical works are designed |  | ||||||
| to take away your freedom to share and change the works.  By contrast, |  | ||||||
| the GNU General Public License is intended to guarantee your freedom to |  | ||||||
| share and change all versions of a program--to make sure it remains free |  | ||||||
| software for all its users.  We, the Free Software Foundation, use the |  | ||||||
| GNU General Public License for most of our software; it applies also to |  | ||||||
| any other work released this way by its authors.  You can apply it to |  | ||||||
| your programs, too. |  | ||||||
|  |  | ||||||
|   When we speak of free software, we are referring to freedom, not |  | ||||||
| price.  Our General Public Licenses are designed to make sure that you |  | ||||||
| have the freedom to distribute copies of free software (and charge for |  | ||||||
| them if you wish), that you receive source code or can get it if you |  | ||||||
| want it, that you can change the software or use pieces of it in new |  | ||||||
| free programs, and that you know you can do these things. |  | ||||||
|  |  | ||||||
|   To protect your rights, we need to prevent others from denying you |  | ||||||
| these rights or asking you to surrender the rights.  Therefore, you have |  | ||||||
| certain responsibilities if you distribute copies of the software, or if |  | ||||||
| you modify it: responsibilities to respect the freedom of others. |  | ||||||
|  |  | ||||||
|   For example, if you distribute copies of such a program, whether |  | ||||||
| gratis or for a fee, you must pass on to the recipients the same |  | ||||||
| freedoms that you received.  You must make sure that they, too, receive |  | ||||||
| or can get the source code.  And you must show them these terms so they |  | ||||||
| know their rights. |  | ||||||
|  |  | ||||||
|   Developers that use the GNU GPL protect your rights with two steps: |  | ||||||
| (1) assert copyright on the software, and (2) offer you this License |  | ||||||
| giving you legal permission to copy, distribute and/or modify it. |  | ||||||
|  |  | ||||||
|   For the developers' and authors' protection, the GPL clearly explains |  | ||||||
| that there is no warranty for this free software.  For both users' and |  | ||||||
| authors' sake, the GPL requires that modified versions be marked as |  | ||||||
| changed, so that their problems will not be attributed erroneously to |  | ||||||
| authors of previous versions. |  | ||||||
|  |  | ||||||
|   Some devices are designed to deny users access to install or run |  | ||||||
| modified versions of the software inside them, although the manufacturer |  | ||||||
| can do so.  This is fundamentally incompatible with the aim of |  | ||||||
| protecting users' freedom to change the software.  The systematic |  | ||||||
| pattern of such abuse occurs in the area of products for individuals to |  | ||||||
| use, which is precisely where it is most unacceptable.  Therefore, we |  | ||||||
| have designed this version of the GPL to prohibit the practice for those |  | ||||||
| products.  If such problems arise substantially in other domains, we |  | ||||||
| stand ready to extend this provision to those domains in future versions |  | ||||||
| of the GPL, as needed to protect the freedom of users. |  | ||||||
|  |  | ||||||
|   Finally, every program is threatened constantly by software patents. |  | ||||||
| States should not allow patents to restrict development and use of |  | ||||||
| software on general-purpose computers, but in those that do, we wish to |  | ||||||
| avoid the special danger that patents applied to a free program could |  | ||||||
| make it effectively proprietary.  To prevent this, the GPL assures that |  | ||||||
| patents cannot be used to render the program non-free. |  | ||||||
|  |  | ||||||
|   The precise terms and conditions for copying, distribution and |  | ||||||
| modification follow. |  | ||||||
|  |  | ||||||
|                        TERMS AND CONDITIONS |  | ||||||
|  |  | ||||||
|   0. Definitions. |  | ||||||
|  |  | ||||||
|   "This License" refers to version 3 of the GNU General Public License. |  | ||||||
|  |  | ||||||
|   "Copyright" also means copyright-like laws that apply to other kinds of |  | ||||||
| works, such as semiconductor masks. |  | ||||||
|  |  | ||||||
|   "The Program" refers to any copyrightable work licensed under this |  | ||||||
| License.  Each licensee is addressed as "you".  "Licensees" and |  | ||||||
| "recipients" may be individuals or organizations. |  | ||||||
|  |  | ||||||
|   To "modify" a work means to copy from or adapt all or part of the work |  | ||||||
| in a fashion requiring copyright permission, other than the making of an |  | ||||||
| exact copy.  The resulting work is called a "modified version" of the |  | ||||||
| earlier work or a work "based on" the earlier work. |  | ||||||
|  |  | ||||||
|   A "covered work" means either the unmodified Program or a work based |  | ||||||
| on the Program. |  | ||||||
|  |  | ||||||
|   To "propagate" a work means to do anything with it that, without |  | ||||||
| permission, would make you directly or secondarily liable for |  | ||||||
| infringement under applicable copyright law, except executing it on a |  | ||||||
| computer or modifying a private copy.  Propagation includes copying, |  | ||||||
| distribution (with or without modification), making available to the |  | ||||||
| public, and in some countries other activities as well. |  | ||||||
|  |  | ||||||
|   To "convey" a work means any kind of propagation that enables other |  | ||||||
| parties to make or receive copies.  Mere interaction with a user through |  | ||||||
| a computer network, with no transfer of a copy, is not conveying. |  | ||||||
|  |  | ||||||
|   An interactive user interface displays "Appropriate Legal Notices" |  | ||||||
| to the extent that it includes a convenient and prominently visible |  | ||||||
| feature that (1) displays an appropriate copyright notice, and (2) |  | ||||||
| tells the user that there is no warranty for the work (except to the |  | ||||||
| extent that warranties are provided), that licensees may convey the |  | ||||||
| work under this License, and how to view a copy of this License.  If |  | ||||||
| the interface presents a list of user commands or options, such as a |  | ||||||
| menu, a prominent item in the list meets this criterion. |  | ||||||
|  |  | ||||||
|   1. Source Code. |  | ||||||
|  |  | ||||||
|   The "source code" for a work means the preferred form of the work |  | ||||||
| for making modifications to it.  "Object code" means any non-source |  | ||||||
| form of a work. |  | ||||||
|  |  | ||||||
|   A "Standard Interface" means an interface that either is an official |  | ||||||
| standard defined by a recognized standards body, or, in the case of |  | ||||||
| interfaces specified for a particular programming language, one that |  | ||||||
| is widely used among developers working in that language. |  | ||||||
|  |  | ||||||
|   The "System Libraries" of an executable work include anything, other |  | ||||||
| than the work as a whole, that (a) is included in the normal form of |  | ||||||
| packaging a Major Component, but which is not part of that Major |  | ||||||
| Component, and (b) serves only to enable use of the work with that |  | ||||||
| Major Component, or to implement a Standard Interface for which an |  | ||||||
| implementation is available to the public in source code form.  A |  | ||||||
| "Major Component", in this context, means a major essential component |  | ||||||
| (kernel, window system, and so on) of the specific operating system |  | ||||||
| (if any) on which the executable work runs, or a compiler used to |  | ||||||
| produce the work, or an object code interpreter used to run it. |  | ||||||
|  |  | ||||||
|   The "Corresponding Source" for a work in object code form means all |  | ||||||
| the source code needed to generate, install, and (for an executable |  | ||||||
| work) run the object code and to modify the work, including scripts to |  | ||||||
| control those activities.  However, it does not include the work's |  | ||||||
| System Libraries, or general-purpose tools or generally available free |  | ||||||
| programs which are used unmodified in performing those activities but |  | ||||||
| which are not part of the work.  For example, Corresponding Source |  | ||||||
| includes interface definition files associated with source files for |  | ||||||
| the work, and the source code for shared libraries and dynamically |  | ||||||
| linked subprograms that the work is specifically designed to require, |  | ||||||
| such as by intimate data communication or control flow between those |  | ||||||
| subprograms and other parts of the work. |  | ||||||
|  |  | ||||||
|   The Corresponding Source need not include anything that users |  | ||||||
| can regenerate automatically from other parts of the Corresponding |  | ||||||
| Source. |  | ||||||
|  |  | ||||||
|   The Corresponding Source for a work in source code form is that |  | ||||||
| same work. |  | ||||||
|  |  | ||||||
|   2. Basic Permissions. |  | ||||||
|  |  | ||||||
|   All rights granted under this License are granted for the term of |  | ||||||
| copyright on the Program, and are irrevocable provided the stated |  | ||||||
| conditions are met.  This License explicitly affirms your unlimited |  | ||||||
| permission to run the unmodified Program.  The output from running a |  | ||||||
| covered work is covered by this License only if the output, given its |  | ||||||
| content, constitutes a covered work.  This License acknowledges your |  | ||||||
| rights of fair use or other equivalent, as provided by copyright law. |  | ||||||
|  |  | ||||||
|   You may make, run and propagate covered works that you do not |  | ||||||
| convey, without conditions so long as your license otherwise remains |  | ||||||
| in force.  You may convey covered works to others for the sole purpose |  | ||||||
| of having them make modifications exclusively for you, or provide you |  | ||||||
| with facilities for running those works, provided that you comply with |  | ||||||
| the terms of this License in conveying all material for which you do |  | ||||||
| not control copyright.  Those thus making or running the covered works |  | ||||||
| for you must do so exclusively on your behalf, under your direction |  | ||||||
| and control, on terms that prohibit them from making any copies of |  | ||||||
| your copyrighted material outside their relationship with you. |  | ||||||
|  |  | ||||||
|   Conveying under any other circumstances is permitted solely under |  | ||||||
| the conditions stated below.  Sublicensing is not allowed; section 10 |  | ||||||
| makes it unnecessary. |  | ||||||
|  |  | ||||||
|   3. Protecting Users' Legal Rights From Anti-Circumvention Law. |  | ||||||
|  |  | ||||||
|   No covered work shall be deemed part of an effective technological |  | ||||||
| measure under any applicable law fulfilling obligations under article |  | ||||||
| 11 of the WIPO copyright treaty adopted on 20 December 1996, or |  | ||||||
| similar laws prohibiting or restricting circumvention of such |  | ||||||
| measures. |  | ||||||
|  |  | ||||||
|   When you convey a covered work, you waive any legal power to forbid |  | ||||||
| circumvention of technological measures to the extent such circumvention |  | ||||||
| is effected by exercising rights under this License with respect to |  | ||||||
| the covered work, and you disclaim any intention to limit operation or |  | ||||||
| modification of the work as a means of enforcing, against the work's |  | ||||||
| users, your or third parties' legal rights to forbid circumvention of |  | ||||||
| technological measures. |  | ||||||
|  |  | ||||||
|   4. Conveying Verbatim Copies. |  | ||||||
|  |  | ||||||
|   You may convey verbatim copies of the Program's source code as you |  | ||||||
| receive it, in any medium, provided that you conspicuously and |  | ||||||
| appropriately publish on each copy an appropriate copyright notice; |  | ||||||
| keep intact all notices stating that this License and any |  | ||||||
| non-permissive terms added in accord with section 7 apply to the code; |  | ||||||
| keep intact all notices of the absence of any warranty; and give all |  | ||||||
| recipients a copy of this License along with the Program. |  | ||||||
|  |  | ||||||
|   You may charge any price or no price for each copy that you convey, |  | ||||||
| and you may offer support or warranty protection for a fee. |  | ||||||
|  |  | ||||||
|   5. Conveying Modified Source Versions. |  | ||||||
|  |  | ||||||
|   You may convey a work based on the Program, or the modifications to |  | ||||||
| produce it from the Program, in the form of source code under the |  | ||||||
| terms of section 4, provided that you also meet all of these conditions: |  | ||||||
|  |  | ||||||
|     a) The work must carry prominent notices stating that you modified |  | ||||||
|     it, and giving a relevant date. |  | ||||||
|  |  | ||||||
|     b) The work must carry prominent notices stating that it is |  | ||||||
|     released under this License and any conditions added under section |  | ||||||
|     7.  This requirement modifies the requirement in section 4 to |  | ||||||
|     "keep intact all notices". |  | ||||||
|  |  | ||||||
|     c) You must license the entire work, as a whole, under this |  | ||||||
|     License to anyone who comes into possession of a copy.  This |  | ||||||
|     License will therefore apply, along with any applicable section 7 |  | ||||||
|     additional terms, to the whole of the work, and all its parts, |  | ||||||
|     regardless of how they are packaged.  This License gives no |  | ||||||
|     permission to license the work in any other way, but it does not |  | ||||||
|     invalidate such permission if you have separately received it. |  | ||||||
|  |  | ||||||
|     d) If the work has interactive user interfaces, each must display |  | ||||||
|     Appropriate Legal Notices; however, if the Program has interactive |  | ||||||
|     interfaces that do not display Appropriate Legal Notices, your |  | ||||||
|     work need not make them do so. |  | ||||||
|  |  | ||||||
|   A compilation of a covered work with other separate and independent |  | ||||||
| works, which are not by their nature extensions of the covered work, |  | ||||||
| and which are not combined with it such as to form a larger program, |  | ||||||
| in or on a volume of a storage or distribution medium, is called an |  | ||||||
| "aggregate" if the compilation and its resulting copyright are not |  | ||||||
| used to limit the access or legal rights of the compilation's users |  | ||||||
| beyond what the individual works permit.  Inclusion of a covered work |  | ||||||
| in an aggregate does not cause this License to apply to the other |  | ||||||
| parts of the aggregate. |  | ||||||
|  |  | ||||||
|   6. Conveying Non-Source Forms. |  | ||||||
|  |  | ||||||
|   You may convey a covered work in object code form under the terms |  | ||||||
| of sections 4 and 5, provided that you also convey the |  | ||||||
| machine-readable Corresponding Source under the terms of this License, |  | ||||||
| in one of these ways: |  | ||||||
|  |  | ||||||
|     a) Convey the object code in, or embodied in, a physical product |  | ||||||
|     (including a physical distribution medium), accompanied by the |  | ||||||
|     Corresponding Source fixed on a durable physical medium |  | ||||||
|     customarily used for software interchange. |  | ||||||
|  |  | ||||||
|     b) Convey the object code in, or embodied in, a physical product |  | ||||||
|     (including a physical distribution medium), accompanied by a |  | ||||||
|     written offer, valid for at least three years and valid for as |  | ||||||
|     long as you offer spare parts or customer support for that product |  | ||||||
|     model, to give anyone who possesses the object code either (1) a |  | ||||||
|     copy of the Corresponding Source for all the software in the |  | ||||||
|     product that is covered by this License, on a durable physical |  | ||||||
|     medium customarily used for software interchange, for a price no |  | ||||||
|     more than your reasonable cost of physically performing this |  | ||||||
|     conveying of source, or (2) access to copy the |  | ||||||
|     Corresponding Source from a network server at no charge. |  | ||||||
|  |  | ||||||
|     c) Convey individual copies of the object code with a copy of the |  | ||||||
|     written offer to provide the Corresponding Source.  This |  | ||||||
|     alternative is allowed only occasionally and noncommercially, and |  | ||||||
|     only if you received the object code with such an offer, in accord |  | ||||||
|     with subsection 6b. |  | ||||||
|  |  | ||||||
|     d) Convey the object code by offering access from a designated |  | ||||||
|     place (gratis or for a charge), and offer equivalent access to the |  | ||||||
|     Corresponding Source in the same way through the same place at no |  | ||||||
|     further charge.  You need not require recipients to copy the |  | ||||||
|     Corresponding Source along with the object code.  If the place to |  | ||||||
|     copy the object code is a network server, the Corresponding Source |  | ||||||
|     may be on a different server (operated by you or a third party) |  | ||||||
|     that supports equivalent copying facilities, provided you maintain |  | ||||||
|     clear directions next to the object code saying where to find the |  | ||||||
|     Corresponding Source.  Regardless of what server hosts the |  | ||||||
|     Corresponding Source, you remain obligated to ensure that it is |  | ||||||
|     available for as long as needed to satisfy these requirements. |  | ||||||
|  |  | ||||||
|     e) Convey the object code using peer-to-peer transmission, provided |  | ||||||
|     you inform other peers where the object code and Corresponding |  | ||||||
|     Source of the work are being offered to the general public at no |  | ||||||
|     charge under subsection 6d. |  | ||||||
|  |  | ||||||
|   A separable portion of the object code, whose source code is excluded |  | ||||||
| from the Corresponding Source as a System Library, need not be |  | ||||||
| included in conveying the object code work. |  | ||||||
|  |  | ||||||
|   A "User Product" is either (1) a "consumer product", which means any |  | ||||||
| tangible personal property which is normally used for personal, family, |  | ||||||
| or household purposes, or (2) anything designed or sold for incorporation |  | ||||||
| into a dwelling.  In determining whether a product is a consumer product, |  | ||||||
| doubtful cases shall be resolved in favor of coverage.  For a particular |  | ||||||
| product received by a particular user, "normally used" refers to a |  | ||||||
| typical or common use of that class of product, regardless of the status |  | ||||||
| of the particular user or of the way in which the particular user |  | ||||||
| actually uses, or expects or is expected to use, the product.  A product |  | ||||||
| is a consumer product regardless of whether the product has substantial |  | ||||||
| commercial, industrial or non-consumer uses, unless such uses represent |  | ||||||
| the only significant mode of use of the product. |  | ||||||
|  |  | ||||||
|   "Installation Information" for a User Product means any methods, |  | ||||||
| procedures, authorization keys, or other information required to install |  | ||||||
| and execute modified versions of a covered work in that User Product from |  | ||||||
| a modified version of its Corresponding Source.  The information must |  | ||||||
| suffice to ensure that the continued functioning of the modified object |  | ||||||
| code is in no case prevented or interfered with solely because |  | ||||||
| modification has been made. |  | ||||||
|  |  | ||||||
|   If you convey an object code work under this section in, or with, or |  | ||||||
| specifically for use in, a User Product, and the conveying occurs as |  | ||||||
| part of a transaction in which the right of possession and use of the |  | ||||||
| User Product is transferred to the recipient in perpetuity or for a |  | ||||||
| fixed term (regardless of how the transaction is characterized), the |  | ||||||
| Corresponding Source conveyed under this section must be accompanied |  | ||||||
| by the Installation Information.  But this requirement does not apply |  | ||||||
| if neither you nor any third party retains the ability to install |  | ||||||
| modified object code on the User Product (for example, the work has |  | ||||||
| been installed in ROM). |  | ||||||
|  |  | ||||||
|   The requirement to provide Installation Information does not include a |  | ||||||
| requirement to continue to provide support service, warranty, or updates |  | ||||||
| for a work that has been modified or installed by the recipient, or for |  | ||||||
| the User Product in which it has been modified or installed.  Access to a |  | ||||||
| network may be denied when the modification itself materially and |  | ||||||
| adversely affects the operation of the network or violates the rules and |  | ||||||
| protocols for communication across the network. |  | ||||||
|  |  | ||||||
|   Corresponding Source conveyed, and Installation Information provided, |  | ||||||
| in accord with this section must be in a format that is publicly |  | ||||||
| documented (and with an implementation available to the public in |  | ||||||
| source code form), and must require no special password or key for |  | ||||||
| unpacking, reading or copying. |  | ||||||
|  |  | ||||||
|   7. Additional Terms. |  | ||||||
|  |  | ||||||
|   "Additional permissions" are terms that supplement the terms of this |  | ||||||
| License by making exceptions from one or more of its conditions. |  | ||||||
| Additional permissions that are applicable to the entire Program shall |  | ||||||
| be treated as though they were included in this License, to the extent |  | ||||||
| that they are valid under applicable law.  If additional permissions |  | ||||||
| apply only to part of the Program, that part may be used separately |  | ||||||
| under those permissions, but the entire Program remains governed by |  | ||||||
| this License without regard to the additional permissions. |  | ||||||
|  |  | ||||||
|   When you convey a copy of a covered work, you may at your option |  | ||||||
| remove any additional permissions from that copy, or from any part of |  | ||||||
| it.  (Additional permissions may be written to require their own |  | ||||||
| removal in certain cases when you modify the work.)  You may place |  | ||||||
| additional permissions on material, added by you to a covered work, |  | ||||||
| for which you have or can give appropriate copyright permission. |  | ||||||
|  |  | ||||||
|   Notwithstanding any other provision of this License, for material you |  | ||||||
| add to a covered work, you may (if authorized by the copyright holders of |  | ||||||
| that material) supplement the terms of this License with terms: |  | ||||||
|  |  | ||||||
|     a) Disclaiming warranty or limiting liability differently from the |  | ||||||
|     terms of sections 15 and 16 of this License; or |  | ||||||
|  |  | ||||||
|     b) Requiring preservation of specified reasonable legal notices or |  | ||||||
|     author attributions in that material or in the Appropriate Legal |  | ||||||
|     Notices displayed by works containing it; or |  | ||||||
|  |  | ||||||
|     c) Prohibiting misrepresentation of the origin of that material, or |  | ||||||
|     requiring that modified versions of such material be marked in |  | ||||||
|     reasonable ways as different from the original version; or |  | ||||||
|  |  | ||||||
|     d) Limiting the use for publicity purposes of names of licensors or |  | ||||||
|     authors of the material; or |  | ||||||
|  |  | ||||||
|     e) Declining to grant rights under trademark law for use of some |  | ||||||
|     trade names, trademarks, or service marks; or |  | ||||||
|  |  | ||||||
|     f) Requiring indemnification of licensors and authors of that |  | ||||||
|     material by anyone who conveys the material (or modified versions of |  | ||||||
|     it) with contractual assumptions of liability to the recipient, for |  | ||||||
|     any liability that these contractual assumptions directly impose on |  | ||||||
|     those licensors and authors. |  | ||||||
|  |  | ||||||
|   All other non-permissive additional terms are considered "further |  | ||||||
| restrictions" within the meaning of section 10.  If the Program as you |  | ||||||
| received it, or any part of it, contains a notice stating that it is |  | ||||||
| governed by this License along with a term that is a further |  | ||||||
| restriction, you may remove that term.  If a license document contains |  | ||||||
| a further restriction but permits relicensing or conveying under this |  | ||||||
| License, you may add to a covered work material governed by the terms |  | ||||||
| of that license document, provided that the further restriction does |  | ||||||
| not survive such relicensing or conveying. |  | ||||||
|  |  | ||||||
|   If you add terms to a covered work in accord with this section, you |  | ||||||
| must place, in the relevant source files, a statement of the |  | ||||||
| additional terms that apply to those files, or a notice indicating |  | ||||||
| where to find the applicable terms. |  | ||||||
|  |  | ||||||
|   Additional terms, permissive or non-permissive, may be stated in the |  | ||||||
| form of a separately written license, or stated as exceptions; |  | ||||||
| the above requirements apply either way. |  | ||||||
|  |  | ||||||
|   8. Termination. |  | ||||||
|  |  | ||||||
|   You may not propagate or modify a covered work except as expressly |  | ||||||
| provided under this License.  Any attempt otherwise to propagate or |  | ||||||
| modify it is void, and will automatically terminate your rights under |  | ||||||
| this License (including any patent licenses granted under the third |  | ||||||
| paragraph of section 11). |  | ||||||
|  |  | ||||||
|   However, if you cease all violation of this License, then your |  | ||||||
| license from a particular copyright holder is reinstated (a) |  | ||||||
| provisionally, unless and until the copyright holder explicitly and |  | ||||||
| finally terminates your license, and (b) permanently, if the copyright |  | ||||||
| holder fails to notify you of the violation by some reasonable means |  | ||||||
| prior to 60 days after the cessation. |  | ||||||
|  |  | ||||||
|   Moreover, your license from a particular copyright holder is |  | ||||||
| reinstated permanently if the copyright holder notifies you of the |  | ||||||
| violation by some reasonable means, this is the first time you have |  | ||||||
| received notice of violation of this License (for any work) from that |  | ||||||
| copyright holder, and you cure the violation prior to 30 days after |  | ||||||
| your receipt of the notice. |  | ||||||
|  |  | ||||||
|   Termination of your rights under this section does not terminate the |  | ||||||
| licenses of parties who have received copies or rights from you under |  | ||||||
| this License.  If your rights have been terminated and not permanently |  | ||||||
| reinstated, you do not qualify to receive new licenses for the same |  | ||||||
| material under section 10. |  | ||||||
|  |  | ||||||
|   9. Acceptance Not Required for Having Copies. |  | ||||||
|  |  | ||||||
|   You are not required to accept this License in order to receive or |  | ||||||
| run a copy of the Program.  Ancillary propagation of a covered work |  | ||||||
| occurring solely as a consequence of using peer-to-peer transmission |  | ||||||
| to receive a copy likewise does not require acceptance.  However, |  | ||||||
| nothing other than this License grants you permission to propagate or |  | ||||||
| modify any covered work.  These actions infringe copyright if you do |  | ||||||
| not accept this License.  Therefore, by modifying or propagating a |  | ||||||
| covered work, you indicate your acceptance of this License to do so. |  | ||||||
|  |  | ||||||
|   10. Automatic Licensing of Downstream Recipients. |  | ||||||
|  |  | ||||||
|   Each time you convey a covered work, the recipient automatically |  | ||||||
| receives a license from the original licensors, to run, modify and |  | ||||||
| propagate that work, subject to this License.  You are not responsible |  | ||||||
| for enforcing compliance by third parties with this License. |  | ||||||
|  |  | ||||||
|   An "entity transaction" is a transaction transferring control of an |  | ||||||
| organization, or substantially all assets of one, or subdividing an |  | ||||||
| organization, or merging organizations.  If propagation of a covered |  | ||||||
| work results from an entity transaction, each party to that |  | ||||||
| transaction who receives a copy of the work also receives whatever |  | ||||||
| licenses to the work the party's predecessor in interest had or could |  | ||||||
| give under the previous paragraph, plus a right to possession of the |  | ||||||
| Corresponding Source of the work from the predecessor in interest, if |  | ||||||
| the predecessor has it or can get it with reasonable efforts. |  | ||||||
|  |  | ||||||
|   You may not impose any further restrictions on the exercise of the |  | ||||||
| rights granted or affirmed under this License.  For example, you may |  | ||||||
| not impose a license fee, royalty, or other charge for exercise of |  | ||||||
| rights granted under this License, and you may not initiate litigation |  | ||||||
| (including a cross-claim or counterclaim in a lawsuit) alleging that |  | ||||||
| any patent claim is infringed by making, using, selling, offering for |  | ||||||
| sale, or importing the Program or any portion of it. |  | ||||||
|  |  | ||||||
|   11. Patents. |  | ||||||
|  |  | ||||||
|   A "contributor" is a copyright holder who authorizes use under this |  | ||||||
| License of the Program or a work on which the Program is based.  The |  | ||||||
| work thus licensed is called the contributor's "contributor version". |  | ||||||
|  |  | ||||||
|   A contributor's "essential patent claims" are all patent claims |  | ||||||
| owned or controlled by the contributor, whether already acquired or |  | ||||||
| hereafter acquired, that would be infringed by some manner, permitted |  | ||||||
| by this License, of making, using, or selling its contributor version, |  | ||||||
| but do not include claims that would be infringed only as a |  | ||||||
| consequence of further modification of the contributor version.  For |  | ||||||
| purposes of this definition, "control" includes the right to grant |  | ||||||
| patent sublicenses in a manner consistent with the requirements of |  | ||||||
| this License. |  | ||||||
|  |  | ||||||
|   Each contributor grants you a non-exclusive, worldwide, royalty-free |  | ||||||
| patent license under the contributor's essential patent claims, to |  | ||||||
| make, use, sell, offer for sale, import and otherwise run, modify and |  | ||||||
| propagate the contents of its contributor version. |  | ||||||
|  |  | ||||||
|   In the following three paragraphs, a "patent license" is any express |  | ||||||
| agreement or commitment, however denominated, not to enforce a patent |  | ||||||
| (such as an express permission to practice a patent or covenant not to |  | ||||||
| sue for patent infringement).  To "grant" such a patent license to a |  | ||||||
| party means to make such an agreement or commitment not to enforce a |  | ||||||
| patent against the party. |  | ||||||
|  |  | ||||||
|   If you convey a covered work, knowingly relying on a patent license, |  | ||||||
| and the Corresponding Source of the work is not available for anyone |  | ||||||
| to copy, free of charge and under the terms of this License, through a |  | ||||||
| publicly available network server or other readily accessible means, |  | ||||||
| then you must either (1) cause the Corresponding Source to be so |  | ||||||
| available, or (2) arrange to deprive yourself of the benefit of the |  | ||||||
| patent license for this particular work, or (3) arrange, in a manner |  | ||||||
| consistent with the requirements of this License, to extend the patent |  | ||||||
| license to downstream recipients.  "Knowingly relying" means you have |  | ||||||
| actual knowledge that, but for the patent license, your conveying the |  | ||||||
| covered work in a country, or your recipient's use of the covered work |  | ||||||
| in a country, would infringe one or more identifiable patents in that |  | ||||||
| country that you have reason to believe are valid. |  | ||||||
|  |  | ||||||
|   If, pursuant to or in connection with a single transaction or |  | ||||||
| arrangement, you convey, or propagate by procuring conveyance of, a |  | ||||||
| covered work, and grant a patent license to some of the parties |  | ||||||
| receiving the covered work authorizing them to use, propagate, modify |  | ||||||
| or convey a specific copy of the covered work, then the patent license |  | ||||||
| you grant is automatically extended to all recipients of the covered |  | ||||||
| work and works based on it. |  | ||||||
|  |  | ||||||
|   A patent license is "discriminatory" if it does not include within |  | ||||||
| the scope of its coverage, prohibits the exercise of, or is |  | ||||||
| conditioned on the non-exercise of one or more of the rights that are |  | ||||||
| specifically granted under this License.  You may not convey a covered |  | ||||||
| work if you are a party to an arrangement with a third party that is |  | ||||||
| in the business of distributing software, under which you make payment |  | ||||||
| to the third party based on the extent of your activity of conveying |  | ||||||
| the work, and under which the third party grants, to any of the |  | ||||||
| parties who would receive the covered work from you, a discriminatory |  | ||||||
| patent license (a) in connection with copies of the covered work |  | ||||||
| conveyed by you (or copies made from those copies), or (b) primarily |  | ||||||
| for and in connection with specific products or compilations that |  | ||||||
| contain the covered work, unless you entered into that arrangement, |  | ||||||
| or that patent license was granted, prior to 28 March 2007. |  | ||||||
|  |  | ||||||
|   Nothing in this License shall be construed as excluding or limiting |  | ||||||
| any implied license or other defenses to infringement that may |  | ||||||
| otherwise be available to you under applicable patent law. |  | ||||||
|  |  | ||||||
|   12. No Surrender of Others' Freedom. |  | ||||||
|  |  | ||||||
|   If conditions are imposed on you (whether by court order, agreement or |  | ||||||
| otherwise) that contradict the conditions of this License, they do not |  | ||||||
| excuse you from the conditions of this License.  If you cannot convey a |  | ||||||
| covered work so as to satisfy simultaneously your obligations under this |  | ||||||
| License and any other pertinent obligations, then as a consequence you may |  | ||||||
| not convey it at all.  For example, if you agree to terms that obligate you |  | ||||||
| to collect a royalty for further conveying from those to whom you convey |  | ||||||
| the Program, the only way you could satisfy both those terms and this |  | ||||||
| License would be to refrain entirely from conveying the Program. |  | ||||||
|  |  | ||||||
|   13. Use with the GNU Affero General Public License. |  | ||||||
|  |  | ||||||
|   Notwithstanding any other provision of this License, you have |  | ||||||
| permission to link or combine any covered work with a work licensed |  | ||||||
| under version 3 of the GNU Affero General Public License into a single |  | ||||||
| combined work, and to convey the resulting work.  The terms of this |  | ||||||
| License will continue to apply to the part which is the covered work, |  | ||||||
| but the special requirements of the GNU Affero General Public License, |  | ||||||
| section 13, concerning interaction through a network will apply to the |  | ||||||
| combination as such. |  | ||||||
|  |  | ||||||
|   14. Revised Versions of this License. |  | ||||||
|  |  | ||||||
|   The Free Software Foundation may publish revised and/or new versions of |  | ||||||
| the GNU General Public License from time to time.  Such new versions will |  | ||||||
| be similar in spirit to the present version, but may differ in detail to |  | ||||||
| address new problems or concerns. |  | ||||||
|  |  | ||||||
|   Each version is given a distinguishing version number.  If the |  | ||||||
| Program specifies that a certain numbered version of the GNU General |  | ||||||
| Public License "or any later version" applies to it, you have the |  | ||||||
| option of following the terms and conditions either of that numbered |  | ||||||
| version or of any later version published by the Free Software |  | ||||||
| Foundation.  If the Program does not specify a version number of the |  | ||||||
| GNU General Public License, you may choose any version ever published |  | ||||||
| by the Free Software Foundation. |  | ||||||
|  |  | ||||||
|   If the Program specifies that a proxy can decide which future |  | ||||||
| versions of the GNU General Public License can be used, that proxy's |  | ||||||
| public statement of acceptance of a version permanently authorizes you |  | ||||||
| to choose that version for the Program. |  | ||||||
|  |  | ||||||
|   Later license versions may give you additional or different |  | ||||||
| permissions.  However, no additional obligations are imposed on any |  | ||||||
| author or copyright holder as a result of your choosing to follow a |  | ||||||
| later version. |  | ||||||
|  |  | ||||||
|   15. Disclaimer of Warranty. |  | ||||||
|  |  | ||||||
|   THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY |  | ||||||
| APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT |  | ||||||
| HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY |  | ||||||
| OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, |  | ||||||
| THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |  | ||||||
| PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM |  | ||||||
| IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF |  | ||||||
| ALL NECESSARY SERVICING, REPAIR OR CORRECTION. |  | ||||||
|  |  | ||||||
|   16. Limitation of Liability. |  | ||||||
|  |  | ||||||
|   IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING |  | ||||||
| WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS |  | ||||||
| THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY |  | ||||||
| GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE |  | ||||||
| USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF |  | ||||||
| DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD |  | ||||||
| PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), |  | ||||||
| EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF |  | ||||||
| SUCH DAMAGES. |  | ||||||
|  |  | ||||||
|   17. Interpretation of Sections 15 and 16. |  | ||||||
|  |  | ||||||
|   If the disclaimer of warranty and limitation of liability provided |  | ||||||
| above cannot be given local legal effect according to their terms, |  | ||||||
| reviewing courts shall apply local law that most closely approximates |  | ||||||
| an absolute waiver of all civil liability in connection with the |  | ||||||
| Program, unless a warranty or assumption of liability accompanies a |  | ||||||
| copy of the Program in return for a fee. |  | ||||||
|  |  | ||||||
|                      END OF TERMS AND CONDITIONS |  | ||||||
|  |  | ||||||
|             How to Apply These Terms to Your New Programs |  | ||||||
|  |  | ||||||
|   If you develop a new program, and you want it to be of the greatest |  | ||||||
| possible use to the public, the best way to achieve this is to make it |  | ||||||
| free software which everyone can redistribute and change under these terms. |  | ||||||
|  |  | ||||||
|   To do so, attach the following notices to the program.  It is safest |  | ||||||
| to attach them to the start of each source file to most effectively |  | ||||||
| state the exclusion of warranty; and each file should have at least |  | ||||||
| the "copyright" line and a pointer to where the full notice is found. |  | ||||||
|  |  | ||||||
|     <one line to give the program's name and a brief idea of what it does.> |  | ||||||
|     Copyright (C) <year>  <name of author> |  | ||||||
|  |  | ||||||
|     This program is free software: you can redistribute it and/or modify |  | ||||||
|     it under the terms of the GNU General Public License as published by |  | ||||||
|     the Free Software Foundation, either version 3 of the License, or |  | ||||||
|     (at your option) any later version. |  | ||||||
|  |  | ||||||
|     This program is distributed in the hope that it will be useful, |  | ||||||
|     but WITHOUT ANY WARRANTY; without even the implied warranty of |  | ||||||
|     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the |  | ||||||
|     GNU General Public License for more details. |  | ||||||
|  |  | ||||||
|     You should have received a copy of the GNU General Public License |  | ||||||
|     along with this program.  If not, see <https://www.gnu.org/licenses/>. |  | ||||||
|  |  | ||||||
| Also add information on how to contact you by electronic and paper mail. |  | ||||||
|  |  | ||||||
|   If the program does terminal interaction, make it output a short |  | ||||||
| notice like this when it starts in an interactive mode: |  | ||||||
|  |  | ||||||
|     <program>  Copyright (C) <year>  <name of author> |  | ||||||
|     This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. |  | ||||||
|     This is free software, and you are welcome to redistribute it |  | ||||||
|     under certain conditions; type `show c' for details. |  | ||||||
|  |  | ||||||
| The hypothetical commands `show w' and `show c' should show the appropriate |  | ||||||
| parts of the General Public License.  Of course, your program's commands |  | ||||||
| might be different; for a GUI interface, you would use an "about box". |  | ||||||
|  |  | ||||||
|   You should also get your employer (if you work as a programmer) or school, |  | ||||||
| if any, to sign a "copyright disclaimer" for the program, if necessary. |  | ||||||
| For more information on this, and how to apply and follow the GNU GPL, see |  | ||||||
| <https://www.gnu.org/licenses/>. |  | ||||||
|  |  | ||||||
|   The GNU General Public License does not permit incorporating your program |  | ||||||
| into proprietary programs.  If your program is a subroutine library, you |  | ||||||
| may consider it more useful to permit linking proprietary applications with |  | ||||||
| the library.  If this is what you want to do, use the GNU Lesser General |  | ||||||
| Public License instead of this License.  But first, please read |  | ||||||
| <https://www.gnu.org/licenses/why-not-lgpl.html>. |  | ||||||
							
								
								
									
										195
									
								
								README.md
									
									
									
									
									
								
							
							
						
						| @@ -1,193 +1,42 @@ | |||||||
| # 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). |  | ||||||
|  |  | ||||||
| <p align="center"> | <p align="center"> | ||||||
|   <img src="https://user-images.githubusercontent.com/3297602/133869036-5c02b0d9-c2d9-4f87-8c81-e44f68cfd6ac.png"> |   <img width="300" src="https://github.com/yuezk/GlobalProtect-openconnect/assets/3297602/9242df9c-217d-42ab-8c21-8f9f69cd4eb5"> | ||||||
| </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> | ## Development | ||||||
| <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> |  | ||||||
|  |  | ||||||
|  | ### Dependencies | ||||||
|  |  | ||||||
| ## Features | The following packages will be required to build depending on your environment: | ||||||
|  |  | ||||||
| - Similar user experience as the official client in macOS. | - [Cargo](https://doc.rust-lang.org/cargo/getting-started/installation.html) | ||||||
| - Supports both SAML and non-SAML authentication modes. | - [pnpm](https://pnpm.io/installation) | ||||||
| - Supports automatically selecting the preferred gateway from the multiple gateways. | - openconnect-devel (containing `openconnect.h`): `sudo apt install libopenconnect-dev` or `sudo yum install openconnect-devel` | ||||||
| - Supports switching gateway from the system tray menu manually. | - GDK dependencies `sudo apt install libgdk3.0-cil-dev libcairo2-dev libsoup2.4-dev libgdk-pixbuf-2.0-dev libjavascriptcoregtk-4.0-dev libatk1.0-dev libpango1.0-dev libwebkit2gtk-4.0-dev` | ||||||
|  |  | ||||||
|  | ### Build the service | ||||||
| ## Install |  | ||||||
|  |  | ||||||
| |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. |  | ||||||
|  |  | ||||||
| [](https://repology.org/project/globalprotect-openconnect/versions) |  | ||||||
| [](https://repology.org/project/globalprotect-openconnect/versions) |  | ||||||
| [](https://repology.org/project/globalprotect-openconnect/versions) |  | ||||||
| [](https://repology.org/project/globalprotect-openconnect/versions) |  | ||||||
| [](https://repology.org/project/globalprotect-openconnect/versions) |  | ||||||
| [](https://repology.org/project/globalprotect-openconnect/versions) |  | ||||||
| [](https://repology.org/project/globalprotect-openconnect/versions) |  | ||||||
|  |  | ||||||
| ### Linux Mint, Ubuntu 18.04 or later |  | ||||||
|  |  | ||||||
| ```sh | ```sh | ||||||
| sudo add-apt-repository ppa:yuezk/globalprotect-openconnect | # Build the client first | ||||||
| sudo apt-get update | cargo build -p gpclient | ||||||
| sudo apt-get install globalprotect-openconnect |  | ||||||
|  | # Build the service | ||||||
|  | cargo build -p gpservice | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| > 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`. | ### Start the service | ||||||
|  |  | ||||||
| ### Arch Linux / Manjaro |  | ||||||
|  |  | ||||||
| ```sh | ```sh | ||||||
| sudo pacman -S globalprotect-openconnect | sudo ./target/debug/gpservice | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| ### AUR snapshot version | ### Start the GUI | ||||||
|  |  | ||||||
| ```sh | ```sh | ||||||
| yay -S globalprotect-openconnect-git | cd gpgui | ||||||
|  | pnpm install | ||||||
|  | pnpm tauri dev | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| ### Fedora | ### Open the DevTools | ||||||
|  |  | ||||||
| ```sh | Right-click on the GUI window and select "Inspect Element". | ||||||
| sudo dnf copr enable yuezk/globalprotect-openconnect |  | ||||||
| sudo dnf install globalprotect-openconnect |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| ### openSUSE |  | ||||||
|  |  | ||||||
| - 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 |  | ||||||
|   ``` |  | ||||||
|  |  | ||||||
| - openSUSE Leap |  | ||||||
|  |  | ||||||
|   ```sh |  | ||||||
|   sudo zypper ar https://download.opensuse.org/repositories/home:/yuezk/openSUSE_Leap_15.2/home:yuezk.repo |  | ||||||
|   sudo zypper ref |  | ||||||
|   sudo zypper install globalprotect-openconnect |  | ||||||
|   ``` |  | ||||||
| ### CentOS 8 |  | ||||||
|  |  | ||||||
| 1. Add the repository: `https://download.opensuse.org/repositories/home:/yuezk/CentOS_8/home:yuezk.repo` |  | ||||||
| 1. Install `globalprotect-openconnect` |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ## Build & Install from source code |  | ||||||
|  |  | ||||||
| Clone this repo with: |  | ||||||
|  |  | ||||||
| ```sh |  | ||||||
| git clone https://github.com/yuezk/GlobalProtect-openconnect.git |  | ||||||
| cd GlobalProtect-openconnect |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| ### MX Linux |  | ||||||
| The following instructions are for **MX-21.2.1_x64 KDE**. |  | ||||||
|  |  | ||||||
| ```sh |  | ||||||
| sudo apt install qttools5-dev libsecret-1-dev libqt5keychain1 |  | ||||||
| ./scripts/install-debian.sh |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| ### Ubuntu/Mint |  | ||||||
|  |  | ||||||
| > **⚠️ 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 |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ### NixOS |  | ||||||
|   In `configuration.nix`: |  | ||||||
|  |  | ||||||
|   ``` |  | ||||||
|   services.globalprotect = { |  | ||||||
|     enable = true; |  | ||||||
|     # if you need a Host Integrity Protection report |  | ||||||
|     csdWrapper = "${pkgs.openconnect}/libexec/openconnect/hipreport.sh"; |  | ||||||
|   }; |  | ||||||
|  |  | ||||||
|   environment.systemPackages = [ globalprotect-openconnect ]; |  | ||||||
|   ``` |  | ||||||
|  |  | ||||||
| ## Run |  | ||||||
|  |  | ||||||
| Once the software is installed, you can run `gpclient` to start the UI. |  | ||||||
|  |  | ||||||
| ## Passing the Custom Parameters to `OpenConnect` CLI |  | ||||||
|  |  | ||||||
| See [Configuration](https://github.com/yuezk/GlobalProtect-openconnect/wiki/Configuration) |  | ||||||
|  |  | ||||||
| ## Display the system tray icon on Gnome 40 |  | ||||||
|  |  | ||||||
| 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). |  | ||||||
|  |  | ||||||
| <p align="center"> |  | ||||||
|   <img src="https://user-images.githubusercontent.com/3297602/130831022-b93492fd-46dd-4a8e-94a4-13b5747120b7.png" /> |  | ||||||
| <p> |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ## Troubleshooting |  | ||||||
|  |  | ||||||
| Run `gpclient` in the Terminal and collect the logs. |  | ||||||
|  |  | ||||||
| ## [License](./LICENSE) |  | ||||||
| GPLv3 |  | ||||||
|   | |||||||
| @@ -1,27 +0,0 @@ | |||||||
| include(ExternalProject) |  | ||||||
|  |  | ||||||
| function(add_3rdparty NAME) |  | ||||||
|     set(oneValueArgs GIT_REPOSITORY GIT_TAG) |  | ||||||
|     cmake_parse_arguments(add_3rdparty_args "" "${oneValueArgs}" "" ${ARGN}) |  | ||||||
|  |  | ||||||
|     if(EXISTS "${CMAKE_SOURCE_DIR}/3rdparty/${NAME}/CMakeLists.txt") |  | ||||||
|         message(STATUS "Found third party locally for ${NAME}") |  | ||||||
|  |  | ||||||
|         ExternalProject_Add( |  | ||||||
|             ${NAME}-${PROJECT_NAME} |  | ||||||
|             PREFIX ${CMAKE_CURRENT_BINARY_DIR}/${NAME} |  | ||||||
|             SOURCE_DIR "${CMAKE_SOURCE_DIR}/3rdparty/${NAME}" |  | ||||||
|             INSTALL_COMMAND "" |  | ||||||
|             "${add_3rdparty_args_UNPARSED_ARGUMENTS}" |  | ||||||
|         ) |  | ||||||
|         return() |  | ||||||
|     endif() |  | ||||||
|  |  | ||||||
|     message(STATUS "Using ExternalProject to download ${NAME}")  |  | ||||||
|     ExternalProject_Add( |  | ||||||
|         ${NAME}-${PROJECT_NAME} |  | ||||||
|         PREFIX ${CMAKE_CURRENT_BINARY_DIR}/${NAME} |  | ||||||
|         INSTALL_COMMAND "" |  | ||||||
|         "${ARGN}" |  | ||||||
|     ) |  | ||||||
| endfunction() |  | ||||||
| @@ -1,59 +0,0 @@ | |||||||
| # - Try to find NetworkManager |  | ||||||
| # Once done this will define |  | ||||||
| # |  | ||||||
| #  NETWORKMANAGER_FOUND - system has NetworkManager |  | ||||||
| #  NETWORKMANAGER_INCLUDE_DIRS - the NetworkManager include directories |  | ||||||
| #  NETWORKMANAGER_LIBRARIES - the libraries needed to use NetworkManager |  | ||||||
| #  NETWORKMANAGER_CFLAGS - Compiler switches required for using NetworkManager |  | ||||||
| #  NETWORKMANAGER_VERSION - version number of NetworkManager |  | ||||||
|  |  | ||||||
| # Copyright (c) 2006, Alexander Neundorf, <neundorf@kde.org> |  | ||||||
| # Copyright (c) 2007, Will Stephenson, <wstephenson@kde.org> |  | ||||||
| # Copyright (c) 2015-2018, Jan Grulich, <jgrulich@redhat.com> |  | ||||||
|  |  | ||||||
| # Redistribution and use in source and binary forms, with or without |  | ||||||
| # modification, are permitted provided that the following conditions |  | ||||||
| # are met: |  | ||||||
| # 1. Redistributions of source code must retain the above copyright |  | ||||||
| #    notice, this list of conditions and the following disclaimer. |  | ||||||
| # 2. 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. |  | ||||||
| # 3. Neither the name of the University 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 THE REGENTS AND CONTRIBUTORS ``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 THE REGENTS OR CONTRIBUTORS 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. |  | ||||||
|  |  | ||||||
| IF (NETWORKMANAGER_INCLUDE_DIRS) |  | ||||||
|     # in cache already |  | ||||||
|     SET(NetworkManager_FIND_QUIETLY TRUE) |  | ||||||
| ENDIF (NETWORKMANAGER_INCLUDE_DIRS) |  | ||||||
|  |  | ||||||
| IF (NOT WIN32) |  | ||||||
|     find_package(PkgConfig) |  | ||||||
|     PKG_SEARCH_MODULE(NETWORKMANAGER libnm) |  | ||||||
|     IF (NETWORKMANAGER_FOUND) |  | ||||||
|         IF (NetworkManager_FIND_VERSION AND ("${NETWORKMANAGER_VERSION}" VERSION_LESS "${NetworkManager_FIND_VERSION}")) |  | ||||||
|             MESSAGE(FATAL_ERROR "NetworkManager ${NETWORKMANAGER_VERSION} is too old, need at least ${NetworkManager_FIND_VERSION}") |  | ||||||
|         ELSE () |  | ||||||
|             IF (NOT NetworkManager_FIND_QUIETLY) |  | ||||||
|                 MESSAGE(STATUS "Found NetworkManager: ${NETWORKMANAGER_LIBRARY_DIRS}") |  | ||||||
|             ENDIF () |  | ||||||
|         ENDIF () |  | ||||||
|     ELSE () |  | ||||||
|         MESSAGE(FATAL_ERROR "Could NOT find NetworkManager, check FindPkgConfig output above!") |  | ||||||
|     ENDIF () |  | ||||||
| ENDIF (NOT WIN32) |  | ||||||
|  |  | ||||||
| MARK_AS_ADVANCED(NETWORKMANAGER_INCLUDE_DIRS) |  | ||||||
							
								
								
									
										102
									
								
								cmakew
									
									
									
									
									
								
							
							
						
						| @@ -1,102 +0,0 @@ | |||||||
| #!/bin/bash |  | ||||||
|  |  | ||||||
| cmake_version="3.21.2" |  | ||||||
|  |  | ||||||
| arr_cmake_v=(${cmake_version//./ }) |  | ||||||
| cmake_version_major=(${arr_cmake_v[0]}) |  | ||||||
| cmake_version_minor=(${arr_cmake_v[1]}) |  | ||||||
| cmake_version_patch=(${arr_cmake_v[2]}) |  | ||||||
|  |  | ||||||
| # OS specific support (must be 'true' or 'false'). |  | ||||||
| cygwin=false |  | ||||||
| msys=false |  | ||||||
| darwin=false |  | ||||||
| nonstop=false |  | ||||||
|  |  | ||||||
| case "`uname`" in |  | ||||||
|     CYGWIN* ) |  | ||||||
|         cygwin=true |  | ||||||
|         ;; |  | ||||||
|     Darwin* ) |  | ||||||
|         darwin=true |  | ||||||
|         ;; |  | ||||||
|     MINGW* ) |  | ||||||
|         msys=true |  | ||||||
|         ;; |  | ||||||
|     NONSTOP* ) |  | ||||||
|         nonstop=true |  | ||||||
|         ;; |  | ||||||
| esac |  | ||||||
|  |  | ||||||
| # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong |  | ||||||
| if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then |  | ||||||
|     cd "$(dirname "$0")" |  | ||||||
| fi |  | ||||||
|  |  | ||||||
| cmake_base="./.cmake" |  | ||||||
| cmake_bin="${cmake_base}/cmake-$cmake_version/bin/cmake" |  | ||||||
|  |  | ||||||
| # download cmake if necessary |  | ||||||
| if [ ! -f "$cmake_bin" ]; then |  | ||||||
|     download_link="" |  | ||||||
|  |  | ||||||
|     if [ "$darwin" = true ]; then  |  | ||||||
|         download_link="https://cmake.org/files/v$cmake_version_major.$cmake_version_minor/cmake-$cmake_version-Darwin-x86_64.tar.gz"         |  | ||||||
|     else |  | ||||||
|         download_link="https://github.com/Kitware/CMake/releases/download/v${cmake_version}/cmake-${cmake_version}-linux-x86_64.tar.gz" |  | ||||||
|     fi  |  | ||||||
|  |  | ||||||
|     wget -nv --show-progress "$download_link" -O "/tmp/cmake-$cmake_version.tar.gz"   |  | ||||||
|     mkdir -p "${cmake_base}/cmake-$cmake_version"       |  | ||||||
|     tar -xzf "/tmp/cmake-$cmake_version.tar.gz" -C "${cmake_base}/cmake-$cmake_version" --strip-components=1 |  | ||||||
|     rm "/tmp/cmake-$cmake_version.tar.gz"  |  | ||||||
| fi  |  | ||||||
|  |  | ||||||
| # We build the pattern for arguments to be converted via cygpath |  | ||||||
| if [ "$cygwin" = true ]; then |  | ||||||
|     ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` |  | ||||||
|     SEP="" |  | ||||||
|  |  | ||||||
|     for dir in $ROOTDIRSRAW ; do |  | ||||||
|         ROOTDIRS="$ROOTDIRS$SEP$dir" |  | ||||||
|         SEP="|" |  | ||||||
|     done |  | ||||||
|  |  | ||||||
|     OURCYGPATTERN="(^($ROOTDIRS))" |  | ||||||
|  |  | ||||||
|     # Add a user-defined pattern to the cygpath arguments |  | ||||||
|     if [ "$GRADLE_CYGPATTERN" != "" ] ; then |  | ||||||
|         OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" |  | ||||||
|     fi |  | ||||||
|  |  | ||||||
|     # Now convert the arguments - kludge to limit ourselves to /bin/sh |  | ||||||
|     i=0 |  | ||||||
|     for arg in "$@" ; do |  | ||||||
|         CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` |  | ||||||
|         CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option |  | ||||||
|  |  | ||||||
|         if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition |  | ||||||
|             eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` |  | ||||||
|         else |  | ||||||
|             eval `echo args$i`="\"$arg\"" |  | ||||||
|         fi |  | ||||||
|  |  | ||||||
|         i=$((i+1)) |  | ||||||
|     done |  | ||||||
|  |  | ||||||
|     case $i in |  | ||||||
|         (0) set -- ;; |  | ||||||
|         (1) set -- "$args0" ;; |  | ||||||
|         (2) set -- "$args0" "$args1" ;; |  | ||||||
|         (3) set -- "$args0" "$args1" "$args2" ;; |  | ||||||
|         (4) set -- "$args0" "$args1" "$args2" "$args3" ;; |  | ||||||
|         (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; |  | ||||||
|         (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; |  | ||||||
|         (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; |  | ||||||
|         (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; |  | ||||||
|         (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; |  | ||||||
|     esac |  | ||||||
| fi |  | ||||||
|  |  | ||||||
| # run cmake |  | ||||||
| exec "$cmake_bin" "$@" |  | ||||||
							
								
								
									
										19
									
								
								com.yuezk.gp.policy
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,19 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8"?> | ||||||
|  | <!DOCTYPE policyconfig PUBLIC "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN" "http://www.freedesktop.org/standards/PolicyKit/1/policyconfig.dtd"> | ||||||
|  | <policyconfig> | ||||||
|  |   <vendor>The GlobalProtect-openconnect Project</vendor> | ||||||
|  |   <vendor_url>https://github.com/yuezk/GlobalProtect-openconnect</vendor_url> | ||||||
|  |   <icon_name>gpgui</icon_name> | ||||||
|  |   <action id="com.yuezk.gp.update-gpconf"> | ||||||
|  |     <description>Update the /etc/gpservice/gp.conf</description> | ||||||
|  |     <message>Authentication is required to update the GlobalProtect service configuration</message> | ||||||
|  |     <defaults> | ||||||
|  |       <allow_any>auth_admin</allow_any> | ||||||
|  |       <allow_inactive>auth_admin</allow_inactive> | ||||||
|  |       <allow_active>auth_admin</allow_active> | ||||||
|  |     </defaults> | ||||||
|  |     <annotate key="org.freedesktop.policykit.exec.path">/usr/bin/tee</annotate> | ||||||
|  |     <annotate key="org.freedesktop.policykit.exec.argv1">/etc/gpservice/gp.conf</annotate> | ||||||
|  |     <annotate key="org.freedesktop.policykit.exec.allow_gui">true</annotate> | ||||||
|  |   </action> | ||||||
|  | </policyconfig> | ||||||
							
								
								
									
										5
									
								
								debian/README.Debian
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,5 +0,0 @@ | |||||||
| globalprotect-openconnect for Debian |  | ||||||
|  |  | ||||||
| Added debian packaging  |  | ||||||
|  |  | ||||||
|  -- Amit Joshi <>  Fri, 29 May 2020 21:52:59 -0400 |  | ||||||
							
								
								
									
										154
									
								
								debian/changelog
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,154 +0,0 @@ | |||||||
| globalprotect-openconnect (1.4.9-1) unstable; urgency=medium |  | ||||||
|  |  | ||||||
|   * Updated VERSION, Bumped 1.4.8 –> 1.4.9 |  | ||||||
|   * fix: update cmake version |  | ||||||
|   * fix: correct the package name |  | ||||||
|   * fix: use the dev package |  | ||||||
|   * fix: use qtkeychain package |  | ||||||
|   * fix: add qt5-tools |  | ||||||
|   * fix: add libsecret-1-dev |  | ||||||
|   * fix: add pkg-config |  | ||||||
|   * fix: use cmake 3.16 |  | ||||||
|   * fix: add missing build dependency |  | ||||||
|   * ci: fix CI |  | ||||||
|   * Merge branch 'master' into develop |  | ||||||
|   * feat: expose os-version to settings |  | ||||||
|   * Add two missing dependencies for building on debian (#198) |  | ||||||
|   * ci: assert no library missing |  | ||||||
|   * fix: update qtkeychain |  | ||||||
|   * ci: run gpclient after build |  | ||||||
|   * fix: add qtkeychain |  | ||||||
|   * chore: update CMake file |  | ||||||
|   * Added install instructions for MX Linux. (#190) |  | ||||||
|   * Credentials autocompleting (secure version) (#179) |  | ||||||
|   * Read all saved Gateways (for selecting in Systray) (#181) |  | ||||||
|   * copy install script for debian (#180) |  | ||||||
|   * add es and pt support to shange status when connected to vpn (#162) |  | ||||||
|   * fix: improve the cli support |  | ||||||
|   * feat: add --reset option to gpclient |  | ||||||
|  |  | ||||||
|  -- Kevin Yue <k3vinyue@gmail.com>  Sun, 08 Jan 2023 20:58:32 +0800 |  | ||||||
|  |  | ||||||
| globalprotect-openconnect (1.4.8-1) unstable; urgency=medium |  | ||||||
|  |  | ||||||
|   * Updated VERSION, Bumped 1.4.7 –> 1.4.8 |  | ||||||
|   * fix: fix compile error |  | ||||||
|   * refactor: simplify the code |  | ||||||
|   * chore: use auto to declare variables |  | ||||||
|   * chore: use c++ 17 |  | ||||||
|   * fix: clear cookies when click the Reset button |  | ||||||
|   * fix: refine the authentication workflow |  | ||||||
|   * chore: PLOG -> LOG |  | ||||||
|  |  | ||||||
|  -- Kevin Yue <k3vinyue@gmail.com>  Sun, 12 Jun 2022 20:28:58 +0800 |  | ||||||
|  |  | ||||||
| globalprotect-openconnect (1.4.7-1) unstable; urgency=medium |  | ||||||
|  |  | ||||||
|   * Updated VERSION, Bumped 1.4.6 –> 1.4.7 |  | ||||||
|   * fix: release resources when properly |  | ||||||
|   * fix: add support for parsing tokens from HTML |  | ||||||
|   * handle html comment for saml result with okta 2fa (#156) |  | ||||||
|   * chore: use auto to declare variable |  | ||||||
|   * chore: simplify readme |  | ||||||
|  |  | ||||||
|  -- Kevin Yue <k3vinyue@gmail.com>  Tue, 07 Jun 2022 21:46:04 +0800 |  | ||||||
|  |  | ||||||
| globalprotect-openconnect (1.4.6-1) unstable; urgency=medium |  | ||||||
|  |  | ||||||
|   * Updated VERSION, Bumped 1.4.5 –> 1.4.6 |  | ||||||
|   * feat: display address in gateway menu item |  | ||||||
|   * fix: fix bug of parsing the portal response |  | ||||||
|  |  | ||||||
|  -- Kevin Yue <k3vinyue@gmail.com>  Wed, 01 Jun 2022 23:55:50 +0800 |  | ||||||
|  |  | ||||||
| globalprotect-openconnect (1.4.5-1) unstable; urgency=medium |  | ||||||
|  |  | ||||||
|   * Updated VERSION, Bumped 1.4.4 –> 1.4.5 |  | ||||||
|   * chore: refine vscode settings |  | ||||||
|   * fix: rollback dbus configuration |  | ||||||
|   * feat: add option to start minimized |  | ||||||
|   * packaging: fix postinst for debian |  | ||||||
|   * packaging: add postinst for debian |  | ||||||
|   * test: test debian packaging |  | ||||||
|   * ci: fix the folder path |  | ||||||
|   * chore: apt -> apt-get |  | ||||||
|   * ci: verify debian package |  | ||||||
|   * Revert "Revert "fix: improve the dbus security"" |  | ||||||
|   * fix: improve the portal config parsing |  | ||||||
|   * Revert "fix: improve the dbus security" |  | ||||||
|   * fix: improve the dbus security |  | ||||||
|   * fix: free resources in slots |  | ||||||
|   * chore: refine cmake files |  | ||||||
|   * fix: support high DPI screen |  | ||||||
|  |  | ||||||
|  -- Kevin Yue <k3vinyue@gmail.com>  Sun, 29 May 2022 21:15:40 +0800 |  | ||||||
|  |  | ||||||
| globalprotect-openconnect (1.4.4-1) unstable; urgency=medium |  | ||||||
|  |  | ||||||
|   * Updated VERSION, Bumped 1.4.3 –> 1.4.4 |  | ||||||
|   * fix: support the HighDPI displays |  | ||||||
|   * [misc] update the build script |  | ||||||
|   * [ci] Enable build job for master branch |  | ||||||
|   * [ci] Add ubuntu 22.04 |  | ||||||
|  |  | ||||||
|  -- Kevin Yue <k3vinyue@gmail.com>  Sat, 14 May 2022 19:21:14 +0800 |  | ||||||
|  |  | ||||||
| globalprotect-openconnect (1.4.3-1) unstable; urgency=medium |  | ||||||
|  |  | ||||||
|   * Updated VERSION, Bumped 1.4.2 –> 1.4.3 |  | ||||||
|   * refine AUR packaging |  | ||||||
|   * Prepare release 1.4.3 (#149) |  | ||||||
|  |  | ||||||
|  -- Kevin Yue <k3vinyue@gmail.com>  Mon, 09 May 2022 22:20:54 +0800 |  | ||||||
|  |  | ||||||
| globalprotect-openconnect (1.4.2-1) unstable; urgency=medium |  | ||||||
|  |  | ||||||
|   * Updated VERSION, Bumped 1.4.1 –> 1.4.2 |  | ||||||
|   * Clear SSL_OP_LEGACY_SERVER_CONNECT (#146) |  | ||||||
|  |  | ||||||
|  -- Kevin Yue <k3vinyue@gmail.com>  Fri, 06 May 2022 22:18:19 +0800 |  | ||||||
|  |  | ||||||
| globalprotect-openconnect (1.4.1-1) unstable; urgency=medium |  | ||||||
|  |  | ||||||
|   * Updated VERSION, Bumped 1.4.0 –> 1.4.1 |  | ||||||
|   * print the gpservice logs |  | ||||||
|   * update AUR packaging |  | ||||||
|  |  | ||||||
|  -- Kevin Yue <k3vinyue@gmail.com>  Thu, 03 Mar 2022 21:58:59 +0800 |  | ||||||
|  |  | ||||||
| globalprotect-openconnect (1.4.0-1) unstable; urgency=medium |  | ||||||
|  |  | ||||||
|   * Updated VERSION, Bumped 1.3.4 –> 1.4.0 |  | ||||||
|   * Fix gpservice after openconnect v8.20 (#124) |  | ||||||
|   * Add 2FA support (#112) |  | ||||||
|   * Add a scripting mode to GPClient (#110) |  | ||||||
|   * Stop saving credentials (#111) |  | ||||||
|   * update CI |  | ||||||
|   * add editorconfig |  | ||||||
|   * Update README.md |  | ||||||
|   * Add a run entry (#108) |  | ||||||
|   * update the installation instruction of Arch Linux |  | ||||||
|  |  | ||||||
|  -- Kevin Yue <k3vinyue@gmail.com>  Wed, 02 Mar 2022 21:34:19 +0800 |  | ||||||
|  |  | ||||||
| globalprotect-openconnect (1.3.4-1) unstable; urgency=medium |  | ||||||
|  |  | ||||||
|   * Updated VERSION, Bumped 1.3.3 –> 1.3.4 |  | ||||||
|   * update packaging (#100) |  | ||||||
|   * shorten the sponsor links |  | ||||||
|   * Update README.md |  | ||||||
|   * add sponsor links |  | ||||||
|   * Adding application logs location in the README (#95) |  | ||||||
|   * improve the doc |  | ||||||
|   * Add snap packaging (#93) |  | ||||||
|   * update doc |  | ||||||
|   * Migrate to cmake and refine the code structure (#92) |  | ||||||
|   * QStringView -> QString |  | ||||||
|  |  | ||||||
|  -- Kevin Yue <k3vinyue@gmail.com>  Sun, 24 Oct 2021 12:13:24 +0800 |  | ||||||
|  |  | ||||||
| globalprotect-openconnect (1.3.0-1) unstable; urgency=medium |  | ||||||
|  |  | ||||||
|   * Bump version to 1.3.0 |  | ||||||
|  |  | ||||||
|  -- Kevin Yue <k3vinyue@gmail.com>  Thu, 09 Jul 2020 10:13:46 +0800 |  | ||||||
							
								
								
									
										1
									
								
								debian/compat
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1 +0,0 @@ | |||||||
| 11 |  | ||||||
							
								
								
									
										13
									
								
								debian/control
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,13 +0,0 @@ | |||||||
| Source: globalprotect-openconnect |  | ||||||
| Section: net |  | ||||||
| Priority: optional |  | ||||||
| Maintainer: Kevin Yue <k3vinyue@gmail.com> |  | ||||||
| Build-Depends: cmake (>=3.10), pkg-config, debhelper (>=11~), qtbase5-dev, qttools5-dev, libqt5websockets5-dev (>=5.9), qtwebengine5-dev (>=5.9), qt5keychain-dev |  | ||||||
| Standards-Version: 4.1.4 |  | ||||||
| Homepage: https://github.com/yuezk/GlobalProtect-openconnect |  | ||||||
|  |  | ||||||
| Package: globalprotect-openconnect |  | ||||||
| Architecture: any |  | ||||||
| Multi-Arch: foreign |  | ||||||
| Depends: ${misc:Depends}, ${shlibs:Depends}, openconnect (>=8.0), libqt5websockets5 (>=5.9), libqt5webengine5 (>=5.9), libqt5keychain1 |  | ||||||
| Description: A GlobalProtect VPN client (GUI) based on OpenConnect. |  | ||||||
							
								
								
									
										15
									
								
								debian/copyright
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,15 +0,0 @@ | |||||||
| Files: * |  | ||||||
| Copyright: 1975-present Kevin Yue |  | ||||||
| License: GPL-3+ |  | ||||||
|  |  | ||||||
| Files: 3rdparty/plog |  | ||||||
| Copyright: 2016 Sergey Podobry (sergey.podobry at gmail.com) |  | ||||||
| License: MPL-2.0 |  | ||||||
|  |  | ||||||
| Files: 3rdparty/qt-unix-signals |  | ||||||
| Copyright: 2014 Simon Knopp |  | ||||||
| License: MIT |  | ||||||
|  |  | ||||||
| Files: 3rdparty/SingleApplication |  | ||||||
| Copyright: Itay Grudev 2015 - 2020 |  | ||||||
| License: MIT |  | ||||||
							
								
								
									
										1
									
								
								debian/patches/series
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1 +0,0 @@ | |||||||
| # You must remove unused comment lines for the released package. |  | ||||||
							
								
								
									
										13
									
								
								debian/rules
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,13 +0,0 @@ | |||||||
| #!/usr/bin/make -f |  | ||||||
| # You must remove unused comment lines for the released package. |  | ||||||
| export DH_VERBOSE = 1 |  | ||||||
| export DEB_BUILD_MAINT_OPTIONS = hardening=+all |  | ||||||
| export DEB_CFLAGS_MAINT_APPEND  = -Wall -pedantic |  | ||||||
| export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed |  | ||||||
|  |  | ||||||
| export DEBIAN_PACKAGE=1 |  | ||||||
|  |  | ||||||
| %: |  | ||||||
| 	dh $@ |  | ||||||
| override_dh_installsystemd: |  | ||||||
| 	dh_installsystemd gpservice.service |  | ||||||
							
								
								
									
										1
									
								
								debian/source/format
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1 +0,0 @@ | |||||||
| 3.0 (native) |  | ||||||
							
								
								
									
										2
									
								
								debian/source/local-options
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,2 +0,0 @@ | |||||||
| #abort-on-upstream-changes |  | ||||||
| #unapply-patches |  | ||||||