├── .github
├── CONTRIBUTING.md
├── ISSUE_TEMPLATE
│ ├── 1-bug-report.md
│ └── 2-feature-request.md
├── PULL_REQUEST_TEMPLATE.md
└── SECURITY.md
├── .gitignore
├── .vscode
└── launch.json
├── ACKNOWLEDGEMENTS.md
├── AUTHORS
├── CHANGELOG.md
├── CLA.md
├── CODE_OF_CONDUCT.md
├── LICENSE.md
├── README.md
├── References
├── Linux
│ ├── build.sh
│ ├── compile-cli.sh
│ └── package_scripts
│ │ ├── after-install.sh
│ │ ├── after-remove.sh
│ │ ├── before-install.sh
│ │ └── before-remove.sh
├── Windows
│ └── build.bat
├── config
│ ├── daemon_repo_local_path.txt
│ └── daemon_repo_local_path_abs.sh
└── macOS
│ └── build.sh
├── commands
├── account.go
├── base.go
├── config
│ ├── config.go
│ └── last_connection.go
├── connection.go
├── dns.go
├── errors.go
├── firewall.go
├── logs.go
├── servers.go
├── state.go
├── tips.go
└── wireguard.go
├── flags
├── errors.go
└── flags.go
├── go.mod
├── go.sum
├── main.go
├── main_darwin.go
├── main_linux.go
├── main_windows.go
└── protocol
├── client.go
├── client_private.go
└── receiver_channel.go
/.github/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | Thanks for your time and interest for contributing to the IVPN CLI project!
4 | As a contributor, here are the guidelines we would like you to follow:
5 |
6 | * [Contributing Code](#contributing)
7 | * [Creating an Issue](#issue)
8 | * [Pull Requests](#pr)
9 | * [Git Workflow](#git)
10 |
11 |
12 | ## Contributing Code
13 |
14 | * By contributing to this project you are agreeing to the terms stated in the [Contributor License Agreement](/CLA.md).
15 | * By contributing to this project, you share your code under the GPLv3 license, as specified in the [License](/LICENSE.md) file.
16 | * Don't forget to add yourself to the [Authors](/AUTHORS) file.
17 |
18 |
19 | ## Creating an Issue
20 |
21 | * If you want to report a security problem **DO NOT CREATE AN ISSUE**, please read our [Security Policy](/.github/SECURITY.md) on how to submit a security vulnerability.
22 | * When creating a new issue, chose a "Bug report" or "Feature request" template and fill the required information.
23 | * Please describe the steps necessary to reproduce the issue you are running into.
24 |
25 |
26 | ## Pull Requests
27 |
28 | Good pull requests - patches, improvements, new features - are a fantastic help. They should remain focused in scope and avoid containing unrelated commits.
29 |
30 | Please ask first before embarking on any significant pull request (e.g. implementing features, refactoring code), otherwise you risk spending a lot of time working on something that the developers might not want to merge into the project.
31 |
32 | Follow these steps when you want to submit a pull request:
33 |
34 | 1. Go over installation guides in the [README](/README.md#installation)
35 | 2. Follow instructions in the [PR Template](/.github/PULL_REQUEST_TEMPLATE.md)
36 | 3. Update the [README](/README.md) file with details of changes if applicable
37 |
38 |
39 | ## Git Workflow
40 |
41 | This project is using [Gitflow Workflow](https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow).
42 |
43 | ### Branch naming guidelines
44 |
45 | Naming for branches is made with following structure:
46 |
47 | ```
48 | /-
49 | ```
50 |
51 | In case when there is no issue:
52 |
53 | ```
54 | /
55 | ```
56 |
57 | Where can be `epic`, `feature`, `task`, `bugfix`, `hotfix` or `release`.
58 |
59 | ### Branches
60 |
61 | `master` - The production branch. Clone or fork this repository for the latest copy.
62 | `develop` - The active development branch. Pull requests should be directed to this branch.
63 | `` - The feature of fix branch. Pull requests should be made from this branch into `develop` brach.
64 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/1-bug-report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: "Bug report"
3 | about: Report a bug in IVPN CLI
4 | ---
5 |
6 | # Bug report
7 |
8 | ## Describe your environment
9 |
10 | * Device: _____
11 | * OS name and version: _____
12 | * IVPN app version: _____
13 |
14 | ## Describe the problem
15 |
16 | ### Steps to reproduce:
17 |
18 | 1. _____
19 | 2. _____
20 | 3. _____
21 |
22 | ### Observed Results:
23 |
24 | * What happened? This could be a description, log output, etc.
25 |
26 | ### Expected Results:
27 |
28 | * What did you expect to happen?
29 |
30 | ### Relevant Code:
31 |
32 | ```
33 | // TODO(you): code here to reproduce the problem
34 | ```
35 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/2-feature-request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: "Feature request"
3 | about: Suggest a feature for IVPN CLI
4 | ---
5 |
6 | # Feature request
7 |
8 | ## Description
9 |
10 | A clear and concise description of the problem or missing capability.
11 |
12 | ## Describe the solution you'd like
13 |
14 | If you have a solution in mind, please describe it.
15 |
16 | ## Describe alternatives you've considered
17 |
18 | Have you considered any alternative solutions or workarounds?
19 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ## PR type
2 | What kind of change does this PR introduce?
3 |
4 |
5 |
6 | - [ ] Bugfix
7 | - [ ] Feature
8 | - [ ] Code style update
9 | - [ ] Refactoring (no functional changes, no api changes)
10 | - [ ] Build related changes
11 | - [ ] Documentation content changes
12 | - [ ] Other... Please describe:
13 |
14 | ## PR checklist
15 |
16 | Please check if your PR fulfills the following requirements:
17 |
18 | - [ ] I have read the CONTRIBUTING.md doc
19 | - [ ] The Git workflow follows our guidelines: CONTRIBUTING.md#git
20 | - [ ] I have added necessary documentation (if appropriate)
21 |
22 | ## What is the current behavior?
23 |
24 |
25 |
26 | Issue number: N/A
27 |
28 | ## What is the new behavior?
29 |
30 | ## Does this PR introduce a breaking change?
31 |
32 | - [ ] Yes
33 | - [ ] No
34 |
35 |
36 |
37 | ## Other information
38 |
--------------------------------------------------------------------------------
/.github/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | ## Reporting a security vulnerability to IVPN
4 |
5 | If you believe you have found a security vulnerability, **DO NOT CREATE AN ISSUE**. Instead, please send an email to security@ivpn.net. We treat all reports with the highest priority and confidentiality.
6 |
7 | ## Incident resolution process
8 |
9 | After this type of report has been submitted, a security vulnerability will be privately discussed, fixed and then publicly disclosed in a security advisory, involving the following steps:
10 |
11 | * Confirm the problem and determine the affected versions.
12 | * Audit code to find any potential similar problems.
13 | * Prepare fixes for all releases still under maintenance. These fixes will be released as fast as possible.
14 | * Publicly disclose security problem in a security advisory.
15 |
16 | ## PGP Key
17 |
18 | ```
19 | -----BEGIN PGP PUBLIC KEY BLOCK-----
20 | mQINBFj4c/QBEAC+A64P+Cn4HPoA4HKQaxIYRXCzgw8fZ2cp4WXK4jIhe0aePExmaPs3rD9aUJrJ
21 | kFumbHgSGICIqu2xygsfp4OCkwSQXIJWU18+eRFXUqFQd9M2LOusd8x+Xopg6jECg2rlPuYPRIUm
22 | 7/APRNErq9nTRzj+MMBny+/PmLTVMFA7eUQC+mMrjLCxR8dlT8dwATBXTpi64nFKJ+On0DO2unr1
23 | i5cPONzK8IMQ09kez92F+RaFlnzlm7U7Fw5tKaNGg713Ibjk/JWEejr+fKcEjuPONEOu6wlWg4u+
24 | 3CfcrrVEu8laZOsbV74S2550qkUMEfUOvjC71pdxkytbMO9YtxVUYaSkA3mVvjB9gCSjg0/6QqrI
25 | G9igL5rDu4RvakuZXEv+q/fIHIKtMinbXKDeXcRxw42JeTtcejlEuPgfMEJlyyYEhxdNhwdaXHcI
26 | s72l10Dxkc5aITW5s82IUtUMTjJJlGOIYxMRhxbSrBjCNWnk7CQGOdxO1hcA7m7Rb4quKfGEJ9wO
27 | z8kdMsfOa8Oqcncq60SoHvs0pRbxIakqCJsp2GLyv3NEbiPxS6YczxYhbJz7jtO3Cfmoa1TUdWNQ
28 | otenIob9d7d2oEE3Ia5fx8lzWK6CTABxC0500LrZiYRM2cLisivdf5RGQVEtB+CpRnu64RoleQWL
29 | +iGBopHCiZunBwARAQABtCZJVlBOIHNlY3VyaXR5IHRlYW0gPHNlY3VyaXR5QGl2cG4ubmV0PokC
30 | VAQTAQoAPgIbAwULCQgHAwUVCgkICwUWAgMBAAIeAQIXgBYhBNKQ55Q5HQFsdLW7r2QhVAOIdW4E
31 | BQJbmQfABQkLSL1MAAoJEGQhVAOIdW4EPkQQAIMpP4y402oFh7v98aLvLuU06T1iNcg7iuDrdsFU
32 | GuBKjEtswOaEkStgpQZZ21/NIBjT+uPfz1mkAZzNtyodgiuKSrgrqpNuhNhTnrnIctk7/Bp75A/o
33 | cZfuC24kPKlpJD++2KPLP8ZFaOHw9NzzadghfQCpDnmCGbA7ovc6EA18n8A53b5uMpkmMuFnxGsi
34 | D1YCPocnUs3txgHQ+I0/gY856agKHGazC5b8yEwYxBQx8VMiAdxyAiGKpiWf0clQlr9ZY9Vaw1vh
35 | b0BcMLwPYX3BujfetmWjrLo3d7IHvnmUNyvryfmkcAm+X2H7qOFtmdCSy4xu5qJIoruZp+RIxqgt
36 | I0RTSpaVHWh4TygrqZVB1wHauMe03yPW2TjjVDQ1uC361CLM4xPBTdR9rJSyjlV7p44NKcvMp+4q
37 | rCgwmyRBssCpnnxzgky6+HLAbGtSyNd7dfPHOA0fCZb26xcx7sHcge2f+YtNX+KDSG6NkJCQmGWS
38 | mmDE+8RsuLhdHqrxTZg2fTBLINQ+dNMvzd1GS45tLiY2oS3AuZk10IK+S6pVhO6W5J/EImAbNxDP
39 | 38c7iQG9ojeMAl3HzC6D7OAoomJngpJNJoMk5UizyXKHdhAg16Jp+oTV7UFp7AzE+GvmdhpoSrJ8
40 | 0VT8h7hKayGmgB4CR7qMaZYntXGh35oTV6HduQINBFj4c/QBEADeHKNnH5jr3L6zALvAdSbSjxKQ
41 | OzSNlPPdnsn2eO5WMZ7hKeXx3XXII0IS60KDmvlL8fkG1LQ3lCCWpf5xcmhgPDh+CuTcdXIqbajx
42 | fMIy6HD3oeogv5JdpRM0HhGZOj5cAYetJDumz/AESVmjAS5ke0HQF4S7gSwDtFwbt8qp2vY3fZVD
43 | PhjImAV/u+6tIgy/LoS7++jsdQNhhGfMs2YBdfqTvCRSA+e9zUAMWHw2iSPXr2FMzVFKDhRW6tW5
44 | 0qTW7iYiwjPjBG9qeiXpPrtV/TzGAWCNyfsPPyWta21SzdclPVhC2RvRaGsdXcjLGV5igMPCfkVz
45 | LnNa3Dz06UdQspKlhBKz1jV2qkuolwlN6kUHX2MwjDREt/Q2nshqkacMwMuu/uakyYhLVWJ1zK7B
46 | fopOZPDrblsgCla9K6tCwOklvenrBCBWjOaPy7+an9Fksn1ipqu67W3N3qBlApQtt0sYUXjBg4fz
47 | 0cXck8zhQDydR6D3VRa976XWU0Vax6/7loKxFS13o87VRu+OtCWpqxHKBwsaBPFMqi/FDhDcmvZF
48 | 27dhxvj0l4LHg5cHW0+0NuEaVEiNozygU7ZZDFPJBBbqp6S0JiZUL3Rn2otXaW76ESLwAkHKn7xK
49 | ZM80YoMBvFOXsS0K2K6PnPX8PmcsnLp0taoFcnjfgx5TJe7MSQARAQABiQI8BBgBCgAmAhsMFiEE
50 | 0pDnlDkdAWx0tbuvZCFUA4h1bgQFAluZCCUFCQtIvbEACgkQZCFUA4h1bgTnNA/+MgKb6YBy9iSm
51 | BqZAjidJtGHKVq1//zatExd7ASQ/pvRlmc4/LrqesWsF8i10rJy2U//o96OtvORv7bl3URqjDWzq
52 | C4r9yJkTXNWxW8k1ZKgCMvxI440uu76ouwHPwpY+iRUH0xPWRsl9tPxsgLKcEyYGFCd42ecpkLCx
53 | i5c9v8A5sVt+Wl2cSvBOLViDzbHMmxxs0KQQ/6in+HM8NDUOP9CP8G+W3kJgT2tL0VRmGiIhxJYh
54 | 3GIMWQgxcjdqMYM3BDY8cnNLNVL90pE+8hiq84Q9d3UpkUXlJqjr2TOk5+KfLaNS7CSsOCpSQagM
55 | FpWJEQO7FisedWhVTe9RZLdcI+MK8nrqW72n0kt7A/Yafv2teWQbq3jCjamMryR/n7X8WW/bTMvd
56 | fw0vw5vWVvCDLCQx+sLeNNwWl8291JZNPaSjhuUkK7npNRHV8M3AC5UhhJjwS5K98YeZUeE/4SLN
57 | +pk3eKg8Jt9tPBGn0okULWU5IkgmEfTMBRy8ZpQp7B2wIvNdpPPmq9bK9p/uYODTetwfajkE8RlO
58 | T7Vor9NUBZb2g6gu2CwiPAhOS5TY/Oqgh3rvBcDS8X9pgOYXpnTrRLJNhZEjNawRvsqbjJV7vpzL
59 | x6/Qj0RvfminJWZZz54gHHuh/98PpmByzXLdpQ8GH+PiUd8n5AyA9FxW81kSLTE==o/fv
60 | -----END PGP PUBLIC KEY BLOCK-----
61 | ```
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | desktop-app-cli
2 | ivpn
3 | /References/Linux/_out_bin/*
4 | /References/Linux/_tmp/*
5 | *.DS_Store
6 | .deps/
7 | bin/
8 | desktop-app-cli.exe
9 | ivpn.exe
10 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": "Launch",
9 | "type": "go",
10 | "request": "launch",
11 | "mode": "auto",
12 | "program": "${workspaceRoot}",
13 | "env": {},
14 | //"args": [],
15 | "args": ["-h"],
16 | //"args": ["servers", "-ping"],
17 | //"args": ["logs"],
18 | //"args": ["login"],
19 | //"args": ["account"],
20 | //"args": ["wgkeys", "-rotation_interval", "1"],
21 | //"args": ["firewall", "-lan_allow"],
22 | //"args": ["servers", "-c", "uni"],
23 | //"args": ["connect", "sk.gw.ivpn.net"],
24 | //"args": ["connect", "-f", "-dns", "1.1.1.1", "-o", "sk.gw.ivpn.net"],
25 | //"args": ["connect","-o", "-dns", "1.1.1.1", "-antitracker_hard", "-f", "sk.gw.ivpn.net"],
26 | //"args": ["state"],
27 | //"args": ["connect", "-s", "rs.gw.ivpn.net"],
28 | "buildFlags": "-tags debug"
29 | }
30 | ]
31 | }
--------------------------------------------------------------------------------
/ACKNOWLEDGEMENTS.md:
--------------------------------------------------------------------------------
1 | # Acknowledgements
2 | This project makes use of the following third party libraries:
3 |
--------------------------------------------------------------------------------
/AUTHORS:
--------------------------------------------------------------------------------
1 | # This is the official list of IVPN CLI authors
2 |
3 | # Names should be added to this file as
4 | # Name
5 |
6 |
7 | # Individual Persons
8 |
9 | Alexandr Stelnykovych
10 |
11 |
12 | # Organizations
13 |
14 | Privatus Limited
15 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | ## Version 3.3.7 - 2021-04-08
6 |
7 | [IMPROVED] AccountID now is shown in the output of the 'ivpn account' command
8 | [IMPROVED] Overall stability and security
9 | [FIXED] (Linux) 'Allow LAN traffic' rules missing after system start
10 | [FIXED] (Linux) Always-on Firewall issue on system boot
11 |
12 | [Download IVPN Client .deb v. 3.3.7](https://repo.ivpn.net/stable/pool/ivpn_3.3.7_amd64.deb)
13 | SHA256: 6b32903eaedd3e5b922d1eb16cb97a88132cc7cce7a7793f31c50edbdadb1458
14 | [Download IVPN Client .rpm v. 3.3.7](https://repo.ivpn.net/stable/pool/ivpn-3.3.7-1.x86_64.rpm)
15 | SHA256: 49b3aabccb7a4324006b7de8607a0870dee34f84d98c8657809fbebe1fa5c6db
16 |
17 | ## Version 2.12.16 - 2020-01-29
18 |
19 | [NEW] Certificate pinning to prevent man-in-the-middle attacks
20 | [FIXED] Minor issue with WireGuard key rotation mechanism
21 |
22 | [Download IVPN Client .deb v. 2.12.16](https://cdn.ivpn.net/releases/linux/2.12.16/ivpn_2.12.16_amd64.deb)
23 | SHA256: 63786e87c8bd2847d286d41ebde5ce5c099825a071e7c0b194c9ddd5ad0391d8
24 | [Download IVPN Client .rpm v. 2.12.16](https://cdn.ivpn.net/releases/linux/2.12.16/ivpn-2.12.16-1.x86_64.rpm)
25 | SHA256: 36bd6261d6bb7e35f26cc149d5d4ce56ab61e6073fd99ea27830f828561125aa
26 |
27 | ## Version 2.12.14 - 2020-01-06
28 |
29 | [IMPROVED] (Linux) Firewall script is more robust now
30 | [IMPROVED] (Linux) Notify UI if ‘pause’ functionality not possible
31 | [FIXED] (Linux) ‘Pause’ functionality for OpenVPN v4.5
32 |
33 | [Download IVPN Client .deb v. 2.12.14](https://cdn.ivpn.net/releases/linux/2.12.14/ivpn_2.12.14_amd64.deb)
34 | SHA256: ae58c01ce4fe69edf49d3aacdb93c8cc0da4f57380b815862c0c451fbdc023c4
35 | [Download IVPN Client .rpm v. 2.12.14](https://cdn.ivpn.net/releases/linux/2.12.14/ivpn-2.12.14-1.x86_64.rpm)
36 | SHA256: 2c42dac0654e01713a2d439cbea430f3463ad6730114382833c7da6fd4f736ab
37 |
38 | ## Version 2.12.8 - 2020-11-09
39 |
40 | [FIXED] (Linux) Firewall: Allow LAN functionality
41 | [FIXED] (Linux) Determine FastestServer when IVPN Firewall enabled
42 |
43 | [Download IVPN Client .deb v. 2.12.8](https://cdn.ivpn.net/releases/linux/2.12.8/ivpn_2.12.8_amd64.deb)
44 | SHA256: c2ff205408d7c3e4fe74310e9a19ea7617e68215986c01b499f329d7744ee83b
45 | [Download IVPN Client .rpm v. 2.12.8](https://cdn.ivpn.net/releases/linux/2.12.8/ivpn-2.12.8-1.x86_64.rpm)
46 | SHA256: bd7b7a16830013388f0f8712464fc1ed63d46f2fa3dc8704f5ba645df0e3ebc0
47 |
48 | ## Version 2.12.7 - 2020-10-13
49 |
50 | [NEW] Compatibility with the new IVPN GUI client
51 |
52 | [Download IVPN Client .deb v. 2.12.7](https://cdn.ivpn.net/releases/linux/2.12.7/ivpn_2.12.7_amd64.deb)
53 | SHA256: 3f5b4765d666bc75ddbf315164b806ba029930e619ee9ac2794bb56e438344ee
54 | [Download IVPN Client .rpm v. 2.12.7](https://cdn.ivpn.net/releases/linux/2.12.7/ivpn-2.12.7-1.x86_64.rpm)
55 | SHA256: e2521307136e8c23398c068293cbf15cbbd052bf811e2daac04bb963d98f2d1a
56 |
57 | ## Version 2.12.5 - 2020-08-10
58 |
59 | [FIXED] Correct UI notification about VPN state by the daemon
60 |
61 | [Download IVPN Client .deb v. 2.12.5](https://cdn.ivpn.net/releases/linux/2.12.5/ivpn_2.12.5_amd64.deb)
62 | SHA256: 33aaf6573f1ed677dc0eeec18e1949a29517207a07f745772989a998f2ad1aed
63 | [Download IVPN Client .rpm v. 2.12.5](https://cdn.ivpn.net/releases/linux/2.12.5/ivpn-2.12.5-1.x86_64.rpm)
64 | SHA256: 2cbd746aeaf0431c019b7cc9128340907ea545e15f76d35cc797af8482074310
65 |
66 | ## Version 2.12.4 - 2020-06-30
67 |
68 | [IMPROVED] Minor improvements
69 |
70 | [Download IVPN Client .deb v. 2.12.4](https://cdn.ivpn.net/releases/linux/2.12.4/ivpn_2.12.4_amd64.deb)
71 | SHA256: c9c08b36accb6be3ee175577919f061efefd8f3d3d71598a82efa475a97b105a
72 | [Download IVPN Client .rpm v. 2.12.4](https://cdn.ivpn.net/releases/linux/2.12.4/ivpn-2.12.4-1.x86_64.rpm)
73 | SHA256: ce36c621b3729e5e8213153031f455c4d65a84c8d33ccae0cd765d980f144ccf
74 |
75 | ## Version 2.12.3 - 2020-06-05
76 |
77 | [IMPROVED] New option to stay logged-in after an upgrade (for future upgrades from DEB or RPM)
78 | [IMPROVED] User-defined extra configuration parameters for OpenVPN moved to separate file with access rights only for privileged account
79 | [FIXED] High CPU use with WireGuard connection
80 |
81 | [Download IVPN Client .deb v. 2.12.3](https://cdn.ivpn.net/releases/linux/2.12.3/ivpn_2.12.3_amd64.deb)
82 | SHA256: 4abc903a112113ec20e78ced569302eae7300e17706dbd5a963650dec3868b3a
83 | [Download IVPN Client .rpm v. 2.12.3](https://cdn.ivpn.net/releases/linux/2.12.3/ivpn-2.12.3-1.x86_64.rpm)
84 | SHA256: bdb161e8005046e1dae04996e10ce0feac49c0d72971a9a65c9fd3629014069c
85 |
86 | ## Version 2.12.2 - 2020-05-23
87 |
88 | [IMPROVED] Overall stability
89 | [FIXED] Potential disconnection when network changes
90 |
91 | [Download IVPN Client .deb v. 2.12.2](https://cdn.ivpn.net/releases/linux/2.12.2/ivpn_2.12.2_amd64.deb)
92 | SHA256: 75b21de598a1f0740b3069390d45e6f54f5ae7d0ff49e2e7387dcff7620ed21d
93 | [Download IVPN Client .rpm v. 2.12.2](https://cdn.ivpn.net/releases/linux/2.12.2/ivpn-2.12.2-1.x86_64.rpm)
94 | SHA256: c83cbafcd8ee9f8dfa3ac4494394b3db8ad2fcded7d760ab54827228f8a39d07
95 |
96 | ## Version 2.12.1 - 2020-05-21
97 |
98 | [FIXED] Potential disconnection when network changes
99 |
100 | [Download IVPN Client .deb v. 2.12.1](https://cdn.ivpn.net/releases/linux/2.12.1/ivpn_2.12.1_amd64.deb)
101 | SHA256: 6c2b10afbd1d0b4cda508af613c9b007fed4aaa15ed1b56531288ddb0edcf48a
102 | [Download IVPN Client .rpm v. 2.12.1](https://cdn.ivpn.net/releases/linux/2.12.1/ivpn-2.12.1-1.x86_64.rpm)
103 | SHA256: 2ebd0ad6893f5874bc79196f6ac217f63d2cbddbc872c6d62f63ac58e63b3f1f
104 |
105 | ## Version 2.12.0 - 2020-05-14
106 |
107 | [IMPROVED] Overall stability
108 |
109 | [Download IVPN Client .deb v. 2.12.0](https://cdn.ivpn.net/releases/linux/2.12.0/ivpn_2.12.0_amd64.deb)
110 | SHA256: 41a9045e3ff7e1b18ab80849be5a6c3d899b751f1c9a54c95ed119bfe7b980f8
111 | [Download IVPN Client .rpm v. 2.12.0](https://cdn.ivpn.net/releases/linux/2.12.0/ivpn-2.12.0-1.x86_64.rpm)
112 | SHA256: d35b9043c2b63a19feffdb756524c301565a2692de758188e76f022fa2355207
113 |
114 | ## Version 0.5.0 - 2020-04-21
115 |
116 | [NEW] First release
117 |
118 | [Download IVPN Client .deb v. 0.5.0](https://cdn.ivpn.net/releases/linux/0.5.0/ivpn_0.5.0_amd64.deb)
119 | SHA256: fd6503f85f239d2f83490c01295e27679a99f53a36f1f82b4e3d82e2aadd4f5a
120 | [Download IVPN Client .rpm v. 0.5.0](https://cdn.ivpn.net/releases/linux/0.5.0/ivpn-0.5.0-1.x86_64.rpm)
121 | SHA256: f7ddd79a91269074088780f24bec633bc0cb55050c80c774d7e9bb83b19f806e
122 |
--------------------------------------------------------------------------------
/CLA.md:
--------------------------------------------------------------------------------
1 | # Contributor License Agreement
2 |
3 | By contributing any improvement, modification, or change to this project, I hereby certify that:
4 |
5 | **(a)** The contribution was authored or created in whole or in part by me and I have the full and unrestricted ownership right and title to submit the contribution under the GPLv3 license; or
6 |
7 | **(b)** The contribution is based upon previously authored work that, to the best of my knowledge, is licensed appropriately under an open source license and I have the full and unrestricted right under that open source license to submit that work with modifications, whether created in whole or in part by me, under the GPLv3 license; or
8 |
9 | **(c)** The contribution was lawfully provided to me by a licensed third-party who certified (a), (b) or (c) and I have not modified the contribution.
10 |
11 | I understand and agree that the contents of this project and the contents of this contribution are considered to be part of the public record and that a record of the contribution (including all personal information I submit with it) shall be maintained indefinitely and may be redistributed to third-parties consistent with this terms of this project or the open source license(s) involved.
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, sex characteristics, gender identity and expression,
9 | level of experience, education, socio-economic status, nationality, personal
10 | appearance, race, religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at support@ivpn.net. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 |
73 | [homepage]: https://www.contributor-covenant.org
74 |
75 | For answers to common questions about this code of conduct, see
76 | https://www.contributor-covenant.org/faq
77 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | ==========================
3 |
4 | Version 3, 29 June 2007
5 |
6 | Copyright (C) 2007 Free Software Foundation, Inc.
7 |
8 | Everyone is permitted to copy and distribute verbatim copies of this license
9 | document, but changing it is not allowed.
10 |
11 | ## Preamble
12 |
13 | The GNU General Public License is a free, copyleft license for software and
14 | other kinds of works.
15 |
16 | The licenses for most software and other practical works are designed to
17 | take away your freedom to share and change the works. By contrast, the GNU
18 | General Public License is intended to guarantee your freedom to share and
19 | change all versions of a program--to make sure it remains free software for
20 | all its users. We, the Free Software Foundation, use the GNU General Public
21 | License for most of our software; it applies also to any other work released
22 | this way by its authors. You can apply it to your programs, too.
23 |
24 | When we speak of free software, we are referring to freedom, not price. Our
25 | General Public Licenses are designed to make sure that you have the freedom
26 | to distribute copies of free software (and charge for them if you wish),
27 | that you receive source code or can get it if you want it, that you can
28 | change the software or use pieces of it in new free programs, and that you
29 | know you can do these things.
30 |
31 | To protect your rights, we need to prevent others from denying you these
32 | rights or asking you to surrender the rights. Therefore, you have certain
33 | responsibilities if you distribute copies of the software, or if you modify
34 | it: responsibilities to respect the freedom of others.
35 |
36 | For example, if you distribute copies of such a program, whether gratis or
37 | for a fee, you must pass on to the recipients the same freedoms that you
38 | received. You must make sure that they, too, receive or can get the source
39 | code. And you must show them these terms so they know their rights.
40 |
41 | Developers that use the GNU GPL protect your rights with two steps: (1)
42 | assert copyright on the software, and (2) offer you this License giving you
43 | legal permission to copy, distribute and/or modify it.
44 |
45 | For the developers' and authors' protection, the GPL clearly explains that
46 | there is no warranty for this free software. For both users' and authors'
47 | sake, the GPL requires that modified versions be marked as changed, so that
48 | their problems will not be attributed erroneously to authors of previous
49 | versions.
50 |
51 | Some devices are designed to deny users access to install or run modified
52 | versions of the software inside them, although the manufacturer can do so.
53 | This is fundamentally incompatible with the aim of protecting users' freedom
54 | to change the software. The systematic pattern of such abuse occurs in the
55 | area of products for individuals to use, which is precisely where it is most
56 | unacceptable. Therefore, we have designed this version of the GPL to
57 | prohibit the practice for those products. If such problems arise
58 | substantially in other domains, we stand ready to extend this provision to
59 | those domains in future versions of the GPL, as needed to protect the
60 | freedom of users.
61 |
62 | Finally, every program is threatened constantly by software patents. States
63 | should not allow patents to restrict development and use of software on
64 | general-purpose computers, but in those that do, we wish to avoid the
65 | special danger that patents applied to a free program could make it
66 | effectively proprietary. To prevent this, the GPL assures that patents
67 | cannot be used to render the program non-free.
68 |
69 | The precise terms and conditions for copying, distribution and modification
70 | follow.
71 |
72 |
73 | ## TERMS AND CONDITIONS
74 |
75 | ### 0. Definitions
76 |
77 | "This License" refers to version 3 of the GNU General Public License.
78 |
79 | "Copyright" also means copyright-like laws that apply to other kinds of
80 | works, such as semiconductor masks.
81 |
82 | "The Program" refers to any copyrightable work licensed under this License.
83 | Each licensee is addressed as "you". "Licensees" and "recipients" may be
84 | individuals or organizations.
85 |
86 | To "modify" a work means to copy from or adapt all or part of the work in a
87 | fashion requiring copyright permission, other than the making of an exact
88 | copy. The resulting work is called a "modified version" of the earlier work
89 | or a work "based on" the earlier work.
90 |
91 | A "covered work" means either the unmodified Program or a work based on the
92 | Program.
93 |
94 | To "propagate" a work means to do anything with it that, without permission,
95 | would make you directly or secondarily liable for infringement under
96 | applicable copyright law, except executing it on a computer or modifying a
97 | private copy. Propagation includes copying, distribution (with or without
98 | modification), making available to the public, and in some countries other
99 | activities as well.
100 |
101 | To "convey" a work means any kind of propagation that enables other parties
102 | to make or receive copies. Mere interaction with a user through a computer
103 | network, with no transfer of a copy, is not conveying.
104 |
105 | An interactive user interface displays "Appropriate Legal Notices" to the
106 | extent that it includes a convenient and prominently visible feature that
107 | (1) displays an appropriate copyright notice, and (2) tells the user that
108 | there is no warranty for the work (except to the extent that warranties are
109 | provided), that licensees may convey the work under this License, and how to
110 | view a copy of this License. If the interface presents a list of user
111 | commands or options, such as a menu, a prominent item in the list meets this
112 | criterion.
113 |
114 |
115 | ### 1. Source Code
116 |
117 | The "source code" for a work means the preferred form of the work for making
118 | modifications to it. "Object code" means any non-source form of a work.
119 |
120 | A "Standard Interface" means an interface that either is an official
121 | standard defined by a recognized standards body, or, in the case of
122 | interfaces specified for a particular programming language, one that is
123 | widely used among developers working in that language.
124 |
125 | The "System Libraries" of an executable work include anything, other than
126 | the work as a whole, that (a) is included in the normal form of packaging a
127 | Major Component, but which is not part of that Major Component, and (b)
128 | serves only to enable use of the work with that Major Component, or to
129 | implement a Standard Interface for which an implementation is available to
130 | the public in source code form. A "Major Component", in this context, means
131 | a major essential component (kernel, window system, and so on) of the
132 | specific operating system (if any) on which the executable work runs, or a
133 | compiler used to produce the work, or an object code interpreter used to run
134 | it.
135 |
136 | The "Corresponding Source" for a work in object code form means all the
137 | source code needed to generate, install, and (for an executable work) run
138 | the object code and to modify the work, including scripts to control those
139 | activities. However, it does not include the work's System Libraries, or
140 | general-purpose tools or generally available free programs which are used
141 | unmodified in performing those activities but which are not part of the
142 | work. For example, Corresponding Source includes interface definition files
143 | associated with source files for the work, and the source code for shared
144 | libraries and dynamically linked subprograms that the work is specifically
145 | designed to require, such as by intimate data communication or control flow
146 | between those subprograms and other parts of the work.
147 |
148 | The Corresponding Source need not include anything that users can regenerate
149 | automatically from other parts of the Corresponding Source.
150 |
151 | The Corresponding Source for a work in source code form is that same work.
152 |
153 |
154 | ### 2. Basic Permissions
155 |
156 | All rights granted under this License are granted for the term of copyright
157 | on the Program, and are irrevocable provided the stated conditions are met.
158 | This License explicitly affirms your unlimited permission to run the
159 | unmodified Program. The output from running a covered work is covered by
160 | this License only if the output, given its content, constitutes a covered
161 | work. This License acknowledges your rights of fair use or other
162 | equivalent, as provided by copyright law.
163 |
164 | You may make, run and propagate covered works that you do not convey,
165 | without conditions so long as your license otherwise remains in force. You
166 | may convey covered works to others for the sole purpose of having them make
167 | modifications exclusively for you, or provide you with facilities for
168 | running those works, provided that you comply with the terms of this License
169 | in conveying all material for which you do not control copyright. Those
170 | thus making or running the covered works for you must do so exclusively on
171 | your behalf, under your direction and control, on terms that prohibit them
172 | from making any copies of your copyrighted material outside their
173 | relationship with you.
174 |
175 | Conveying under any other circumstances is permitted solely under the
176 | conditions stated below. Sublicensing is not allowed; section 10 makes it
177 | unnecessary.
178 |
179 |
180 | ### 3. Protecting Users' Legal Rights From Anti-Circumvention Law
181 |
182 | No covered work shall be deemed part of an effective technological measure
183 | under any applicable law fulfilling obligations under article 11 of the WIPO
184 | copyright treaty adopted on 20 December 1996, or similar laws prohibiting or
185 | restricting circumvention of such measures.
186 |
187 | When you convey a covered work, you waive any legal power to forbid
188 | circumvention of technological measures to the extent such circumvention is
189 | effected by exercising rights under this License with respect to the covered
190 | work, and you disclaim any intention to limit operation or modification of
191 | the work as a means of enforcing, against the work's users, your or third
192 | parties' legal rights to forbid circumvention of technological measures.
193 |
194 |
195 | ### 4. Conveying Verbatim Copies
196 |
197 | You may convey verbatim copies of the Program's source code as you receive
198 | it, in any medium, provided that you conspicuously and appropriately publish
199 | on each copy an appropriate copyright notice; keep intact all notices
200 | stating that this License and any non-permissive terms added in accord with
201 | section 7 apply to the code; keep intact all notices of the absence of any
202 | warranty; and give all recipients a copy of this License along with the
203 | Program.
204 |
205 | You may charge any price or no price for each copy that you convey, and you
206 | may offer support or warranty protection for a fee.
207 |
208 |
209 | ### 5. Conveying Modified Source Versions
210 |
211 | You may convey a work based on the Program, or the modifications to produce
212 | it from the Program, in the form of source code under the terms of section
213 | 4, provided that you also meet all of these conditions:
214 |
215 | * a) The work must carry prominent notices stating that you modified
216 | it, and giving a relevant date.
217 |
218 | * b) The work must carry prominent notices stating that it is
219 | released under this License and any conditions added under section 7.
220 | This requirement modifies the requirement in section 4 to "keep intact
221 | all notices".
222 |
223 | * c) You must license the entire work, as a whole, under this
224 | License to anyone who comes into possession of a copy. This License
225 | will therefore apply, along with any applicable section 7 additional
226 | terms, to the whole of the work, and all its parts, regardless of how
227 | they are packaged. This License gives no permission to license the
228 | work in any other way, but it does not invalidate such permission if
229 | you have separately received it.
230 |
231 | * d) If the work has interactive user interfaces, each must display
232 | Appropriate Legal Notices; however, if the Program has interactive
233 | interfaces that do not display Appropriate Legal Notices, your work
234 | need not make them do so.
235 |
236 | A compilation of a covered work with other separate and independent works,
237 | which are not by their nature extensions of the covered work, and which are
238 | not combined with it such as to form a larger program, in or on a volume of
239 | a storage or distribution medium, is called an "aggregate" if the
240 | compilation and its resulting copyright are not used to limit the access or
241 | legal rights of the compilation's users beyond what the individual works
242 | permit. Inclusion of a covered work in an aggregate does not cause this
243 | License to apply to the other parts of the aggregate.
244 |
245 |
246 | ### 6. Conveying Non-Source Forms
247 |
248 | You may convey a covered work in object code form under the terms of
249 | sections 4 and 5, provided that you also convey the machine-readable
250 | Corresponding Source under the terms of this License, in one of these ways:
251 |
252 | * a) Convey the object code in, or embodied in, a physical product
253 | (including a physical distribution medium), accompanied by the
254 | Corresponding Source fixed on a durable physical medium customarily
255 | used for software interchange.
256 |
257 | * b) Convey the object code in, or embodied in, a physical product
258 | (including a physical distribution medium), accompanied by a written
259 | offer, valid for at least three years and valid for as long as you
260 | offer spare parts or customer support for that product model, to give
261 | anyone who possesses the object code either (1) a copy of the
262 | Corresponding Source for all the software in the product that is
263 | covered by this License, on a durable physical medium customarily used
264 | for software interchange, for a price no more than your reasonable cost
265 | of physically performing this conveying of source, or (2) access to
266 | copy the Corresponding Source from a network server at no charge.
267 |
268 | * c) Convey individual copies of the object code with a copy of the
269 | written offer to provide the Corresponding Source. This alternative is
270 | allowed only occasionally and noncommercially, and only if you received
271 | the object code with such an offer, in accord with subsection 6b.
272 |
273 | * d) Convey the object code by offering access from a designated
274 | place (gratis or for a charge), and offer equivalent access to the
275 | Corresponding Source in the same way through the same place at no
276 | further charge. You need not require recipients to copy the
277 | Corresponding Source along with the object code. If the place to copy
278 | the object code is a network server, the Corresponding Source may be on
279 | a different server (operated by you or a third party) that supports
280 | equivalent copying facilities, provided you maintain clear directions
281 | next to the object code saying where to find the Corresponding Source.
282 | Regardless of what server hosts the Corresponding Source, you remain
283 | obligated to ensure that it is available for as long as needed to
284 | satisfy these requirements.
285 |
286 | * e) Convey the object code using peer-to-peer transmission, provided
287 | you inform other peers where the object code and Corresponding Source
288 | of the work are being offered to the general public at no charge under
289 | subsection 6d.
290 |
291 | A separable portion of the object code, whose source code is excluded from
292 | the Corresponding Source as a System Library, need not be included in
293 | conveying the object code work.
294 |
295 | A "User Product" is either (1) a "consumer product", which means any
296 | tangible personal property which is normally used for personal, family, or
297 | household purposes, or (2) anything designed or sold for incorporation into
298 | a dwelling. In determining whether a product is a consumer product,
299 | doubtful cases shall be resolved in favor of coverage. For a particular
300 | product received by a particular user, "normally used" refers to a typical
301 | or common use of that class of product, regardless of the status of the
302 | particular user or of the way in which the particular user actually uses, or
303 | expects or is expected to use, the product. A product is a consumer product
304 | regardless of whether the product has substantial commercial, industrial or
305 | non-consumer uses, unless such uses represent the only significant mode of
306 | use of the product.
307 |
308 | "Installation Information" for a User Product means any methods, procedures,
309 | authorization keys, or other information required to install and execute
310 | modified versions of a covered work in that User Product from a modified
311 | version of its Corresponding Source. The information must suffice to ensure
312 | that the continued functioning of the modified object code is in no case
313 | prevented or interfered with solely because modification has been made.
314 |
315 | If you convey an object code work under this section in, or with, or
316 | specifically for use in, a User Product, and the conveying occurs as part of
317 | a transaction in which the right of possession and use of the User Product
318 | is transferred to the recipient in perpetuity or for a fixed term
319 | (regardless of how the transaction is characterized), the Corresponding
320 | Source conveyed under this section must be accompanied by the Installation
321 | Information. But this requirement does not apply if neither you nor any
322 | third party retains the ability to install modified object code on the User
323 | Product (for example, the work has been installed in ROM).
324 |
325 | The requirement to provide Installation Information does not include a
326 | requirement to continue to provide support service, warranty, or updates for
327 | a work that has been modified or installed by the recipient, or for the User
328 | Product in which it has been modified or installed. Access to a network may
329 | be denied when the modification itself materially and adversely affects the
330 | operation of the network or violates the rules and protocols for
331 | communication across the network.
332 |
333 | Corresponding Source conveyed, and Installation Information provided, in
334 | accord with this section must be in a format that is publicly documented
335 | (and with an implementation available to the public in source code form),
336 | and must require no special password or key for unpacking, reading or
337 | copying.
338 |
339 |
340 | ### 7. Additional Terms
341 |
342 | "Additional permissions" are terms that supplement the terms of this License
343 | by making exceptions from one or more of its conditions. Additional
344 | permissions that are applicable to the entire Program shall be treated as
345 | though they were included in this License, to the extent that they are valid
346 | under applicable law. If additional permissions apply only to part of the
347 | Program, that part may be used separately under those permissions, but the
348 | entire Program remains governed by this License without regard to the
349 | additional permissions.
350 |
351 | When you convey a copy of a covered work, you may at your option remove any
352 | additional permissions from that copy, or from any part of it. (Additional
353 | permissions may be written to require their own removal in certain cases
354 | when you modify the work.) You may place additional permissions on material,
355 | added by you to a covered work, for which you have or can give appropriate
356 | copyright permission.
357 |
358 | Notwithstanding any other provision of this License, for material you add to
359 | a covered work, you may (if authorized by the copyright holders of that
360 | material) supplement the terms of this License with terms:
361 |
362 | * a) Disclaiming warranty or limiting liability differently from the
363 | terms of sections 15 and 16 of this License; or
364 |
365 | * b) Requiring preservation of specified reasonable legal notices or
366 | author attributions in that material or in the Appropriate Legal
367 | Notices displayed by works containing it; or
368 |
369 | * c) Prohibiting misrepresentation of the origin of that material, or
370 | requiring that modified versions of such material be marked in
371 | reasonable ways as different from the original version; or
372 |
373 | * d) Limiting the use for publicity purposes of names of licensors or
374 | authors of the material; or
375 |
376 | * e) Declining to grant rights under trademark law for use of some trade
377 | names, trademarks, or service marks; or
378 |
379 | * f) Requiring indemnification of licensors and authors of that
380 | material by anyone who conveys the material (or modified versions of
381 | it) with contractual assumptions of liability to the recipient, for any
382 | liability that these contractual assumptions directly impose on those
383 | licensors and authors.
384 |
385 | All other non-permissive additional terms are considered "further
386 | restrictions" within the meaning of section 10. If the Program as you
387 | received it, or any part of it, contains a notice stating that it is
388 | governed by this License along with a term that is a further restriction,
389 | you may remove that term. If a license document contains a further
390 | restriction but permits relicensing or conveying under this License, you may
391 | add to a covered work material governed by the terms of that license
392 | document, provided that the further restriction does not survive such
393 | relicensing or conveying.
394 |
395 | If you add terms to a covered work in accord with this section, you must
396 | place, in the relevant source files, a statement of the additional terms
397 | that apply to those files, or a notice indicating where to find the
398 | applicable terms.
399 |
400 | Additional terms, permissive or non-permissive, may be stated in the form of
401 | a separately written license, or stated as exceptions; the above
402 | requirements apply either way.
403 |
404 |
405 | ### 8. Termination
406 |
407 | You may not propagate or modify a covered work except as expressly provided
408 | under this License. Any attempt otherwise to propagate or modify it is
409 | void, and will automatically terminate your rights under this License
410 | (including any patent licenses granted under the third paragraph of section
411 | 11).
412 |
413 | However, if you cease all violation of this License, then your license from
414 | a particular copyright holder is reinstated (a) provisionally, unless and
415 | until the copyright holder explicitly and finally terminates your license,
416 | and (b) permanently, if the copyright holder fails to notify you of the
417 | violation by some reasonable means prior to 60 days after the cessation.
418 |
419 | Moreover, your license from a particular copyright holder is reinstated
420 | permanently if the copyright holder notifies you of the violation by some
421 | reasonable means, this is the first time you have received notice of
422 | violation of this License (for any work) from that copyright holder, and you
423 | cure the violation prior to 30 days after your receipt of the notice.
424 |
425 | Termination of your rights under this section does not terminate the
426 | licenses of parties who have received copies or rights from you under this
427 | License. If your rights have been terminated and not permanently
428 | reinstated, you do not qualify to receive new licenses for the same material
429 | under section 10.
430 |
431 |
432 | ### 9. Acceptance Not Required for Having Copies
433 |
434 | You are not required to accept this License in order to receive or run a
435 | copy of the Program. Ancillary propagation of a covered work occurring
436 | solely as a consequence of using peer-to-peer transmission to receive a copy
437 | likewise does not require acceptance. However, nothing other than this
438 | License grants you permission to propagate or modify any covered work.
439 | These actions infringe copyright if you do not accept this License.
440 | Therefore, by modifying or propagating a covered work, you indicate your
441 | acceptance of this License to do so.
442 |
443 |
444 | ### 10. Automatic Licensing of Downstream Recipients
445 |
446 | Each time you convey a covered work, the recipient automatically receives a
447 | license from the original licensors, to run, modify and propagate that work,
448 | subject to this License. You are not responsible for enforcing compliance
449 | by third parties with this License.
450 |
451 | An "entity transaction" is a transaction transferring control of an
452 | organization, or substantially all assets of one, or subdividing an
453 | organization, or merging organizations. If propagation of a covered work
454 | results from an entity transaction, each party to that transaction who
455 | receives a copy of the work also receives whatever licenses to the work the
456 | party's predecessor in interest had or could give under the previous
457 | paragraph, plus a right to possession of the Corresponding Source of the
458 | work from the predecessor in interest, if the predecessor has it or can get
459 | it with reasonable efforts.
460 |
461 | You may not impose any further restrictions on the exercise of the rights
462 | granted or affirmed under this License. For example, you may not impose a
463 | license fee, royalty, or other charge for exercise of rights granted under
464 | this License, and you may not initiate litigation (including a cross-claim
465 | or counterclaim in a lawsuit) alleging that any patent claim is infringed by
466 | making, using, selling, offering for sale, or importing the Program or any
467 | portion of it.
468 |
469 |
470 | ### 11. Patents
471 |
472 | A "contributor" is a copyright holder who authorizes use under this License
473 | of the Program or a work on which the Program is based. The work thus
474 | licensed is called the contributor's "contributor version".
475 |
476 | A contributor's "essential patent claims" are all patent claims owned or
477 | controlled by the contributor, whether already acquired or hereafter
478 | acquired, that would be infringed by some manner, permitted by this License,
479 | of making, using, or selling its contributor version, but do not include
480 | claims that would be infringed only as a consequence of further modification
481 | of the contributor version. For purposes of this definition, "control"
482 | includes the right to grant patent sublicenses in a manner consistent with
483 | the requirements of this License.
484 |
485 | Each contributor grants you a non-exclusive, worldwide, royalty-free patent
486 | license under the contributor's essential patent claims, to make, use, sell,
487 | offer for sale, import and otherwise run, modify and propagate the contents
488 | of its contributor version.
489 |
490 | In the following three paragraphs, a "patent license" is any express
491 | agreement or commitment, however denominated, not to enforce a patent (such
492 | as an express permission to practice a patent or covenant not to sue for
493 | patent infringement). To "grant" such a patent license to a party means to
494 | make such an agreement or commitment not to enforce a patent against the
495 | party.
496 |
497 | If you convey a covered work, knowingly relying on a patent license, and the
498 | Corresponding Source of the work is not available for anyone to copy, free
499 | of charge and under the terms of this License, through a publicly available
500 | network server or other readily accessible means, then you must either (1)
501 | cause the Corresponding Source to be so available, or (2) arrange to deprive
502 | yourself of the benefit of the patent license for this particular work, or
503 | (3) arrange, in a manner consistent with the requirements of this License,
504 | to extend the patent license to downstream recipients. "Knowingly relying"
505 | means you have actual knowledge that, but for the patent license, your
506 | conveying the covered work in a country, or your recipient's use of the
507 | covered work in a country, would infringe one or more identifiable patents
508 | in that country that you have reason to believe are valid.
509 |
510 | If, pursuant to or in connection with a single transaction or arrangement,
511 | you convey, or propagate by procuring conveyance of, a covered work, and
512 | grant a patent license to some of the parties receiving the covered work
513 | authorizing them to use, propagate, modify or convey a specific copy of the
514 | covered work, then the patent license you grant is automatically extended to
515 | all recipients of the covered work and works based on it.
516 |
517 | A patent license is "discriminatory" if it does not include within the scope
518 | of its coverage, prohibits the exercise of, or is conditioned on the
519 | non-exercise of one or more of the rights that are specifically granted
520 | under this License. You may not convey a covered work if you are a party to
521 | an arrangement with a third party that is in the business of distributing
522 | software, under which you make payment to the third party based on the
523 | extent of your activity of conveying the work, and under which the third
524 | party grants, to any of the parties who would receive the covered work from
525 | you, a discriminatory patent license (a) in connection with copies of the
526 | covered work conveyed by you (or copies made from those copies), or (b)
527 | primarily for and in connection with specific products or compilations that
528 | contain the covered work, unless you entered into that arrangement, or that
529 | patent license was granted, prior to 28 March 2007.
530 |
531 | Nothing in this License shall be construed as excluding or limiting any
532 | implied license or other defenses to infringement that may otherwise be
533 | available to you under applicable patent law.
534 |
535 |
536 | ### 12. No Surrender of Others' Freedom
537 |
538 | If conditions are imposed on you (whether by court order, agreement or
539 | otherwise) that contradict the conditions of this License, they do not
540 | excuse you from the conditions of this License. If you cannot convey a
541 | covered work so as to satisfy simultaneously your obligations under this
542 | License and any other pertinent obligations, then as a consequence you may
543 | not convey it at all. For example, if you agree to terms that obligate you
544 | to collect a royalty for further conveying from those to whom you convey the
545 | Program, the only way you could satisfy both those terms and this License
546 | would be to refrain entirely from conveying the Program.
547 |
548 |
549 | ### 13. Use with the GNU Affero General Public License
550 |
551 | Notwithstanding any other provision of this License, you have permission to
552 | link or combine any covered work with a work licensed under version 3 of the
553 | GNU Affero General Public License into a single combined work, and to convey
554 | the resulting work. The terms of this License will continue to apply to the
555 | part which is the covered work, but the special requirements of the GNU
556 | Affero General Public License, section 13, concerning interaction through a
557 | network will apply to the combination as such.
558 |
559 |
560 | ### 14. Revised Versions of this License
561 |
562 | The Free Software Foundation may publish revised and/or new versions of the
563 | GNU General Public License from time to time. Such new versions will be
564 | similar in spirit to the present version, but may differ in detail to
565 | address new problems or concerns.
566 |
567 | Each version is given a distinguishing version number. If the Program
568 | specifies that a certain numbered version of the GNU General Public License
569 | "or any later version" applies to it, you have the option of following the
570 | terms and conditions either of that numbered version or of any later version
571 | published by the Free Software Foundation. If the Program does not specify
572 | a version number of the GNU General Public License, you may choose any
573 | version ever published by the Free Software Foundation.
574 |
575 | If the Program specifies that a proxy can decide which future versions of
576 | the GNU General Public License can be used, that proxy's public statement of
577 | acceptance of a version permanently authorizes you to choose that version
578 | for the Program.
579 |
580 | Later license versions may give you additional or different permissions.
581 | However, no additional obligations are imposed on any author or copyright
582 | holder as a result of your choosing to follow a later version.
583 |
584 |
585 | ### 15. Disclaimer of Warranty
586 |
587 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE
588 | LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
589 | OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND,
590 | EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
591 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
592 | ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.
593 | SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY
594 | SERVICING, REPAIR OR CORRECTION.
595 |
596 |
597 | ### 16. Limitation of Liability
598 |
599 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL
600 | ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE
601 | PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
602 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE
603 | OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA
604 | OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
605 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
606 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
607 | SUCH DAMAGES.
608 |
609 |
610 | ### 17. Interpretation of Sections 15 and 16
611 |
612 | If the disclaimer of warranty and limitation of liability provided above
613 | cannot be given local legal effect according to their terms, reviewing
614 | courts shall apply local law that most closely approximates an absolute
615 | waiver of all civil liability in connection with the Program, unless a
616 | warranty or assumption of liability accompanies a copy of the Program in
617 | return for a fee.
618 |
619 | END OF TERMS AND CONDITIONS
620 |
621 |
622 | ## How to Apply These Terms to Your New Programs
623 |
624 | If you develop a new program, and you want it to be of the greatest possible
625 | use to the public, the best way to achieve this is to make it free software
626 | which everyone can redistribute and change under these terms.
627 |
628 | To do so, attach the following notices to the program. It is safest to
629 | attach them to the start of each source file to most effectively state the
630 | exclusion of warranty; and each file should have at least the "copyright"
631 | line and a pointer to where the full notice is found.
632 |
633 |
634 | Copyright (C)
635 |
636 | This program is free software: you can redistribute it and/or modify
637 | it under the terms of the GNU General Public License as published by
638 | the Free Software Foundation, either version 3 of the License, or
639 | (at your option) any later version.
640 |
641 | This program is distributed in the hope that it will be useful,
642 | but WITHOUT ANY WARRANTY; without even the implied warranty of
643 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
644 | GNU General Public License for more details.
645 |
646 | You should have received a copy of the GNU General Public License
647 | along with this program. If not, see .
648 |
649 | Also add information on how to contact you by electronic and paper mail.
650 |
651 | If the program does terminal interaction, make it output a short notice like
652 | this when it starts in an interactive mode:
653 |
654 | Copyright (C)
655 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w`.
656 | This is free software, and you are welcome to redistribute it
657 | under certain conditions; type `show c` for details.
658 |
659 | The hypothetical commands `show w` and `show c` should show the appropriate
660 | parts of the General Public License. Of course, your program's commands
661 | might be different; for a GUI interface, you would use an "about box".
662 |
663 | You should also get your employer (if you work as a programmer) or school,
664 | if any, to sign a "copyright disclaimer" for the program, if necessary. For
665 | more information on this, and how to apply and follow the GNU GPL, see
666 | .
667 |
668 | The GNU General Public License does not permit incorporating your program
669 | into proprietary programs. If your program is a subroutine library, you may
670 | consider it more useful to permit linking proprietary applications with the
671 | library. If this is what you want to do, use the GNU Lesser General Public
672 | License instead of this License. But first, please read
673 | .
674 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | _This is a legacy project.
2 | Development has been moved to a new repository: https://github.com/ivpn/desktop-app_
3 |
4 | # IVPN Command Line Interface (CLI)
5 |
6 | **IVPN Command Line Interface** is an official CLI for IVPN software.
7 | It is a client for IVPN daemon ([ivpn-desktop-daemon](https://github.com/ivpn/desktop-app-daemon))
8 | Can be compiled for different platforms: Windows, macOS, Linux
9 |
10 | IVPN CLI is distributed on the official site [https://www.ivpn.net](https://www.ivpn.net).
11 |
12 | * [About this Repo](#about-repo)
13 | * [Installation](#installation)
14 | * [Versioning](#versioning)
15 | * [Contributing](#contributing)
16 | * [Security Policy](#security)
17 | * [License](#license)
18 | * [Authors](#Authors)
19 | * [Acknowledgements](#acknowledgements)
20 |
21 |
22 | ## About this Repo
23 |
24 | This is the official Git repo of the [IVPN Command Line Interface](https://github.com/ivpn/desktop-app-cli).
25 |
26 |
27 | ## Installation
28 |
29 | These instructions will get you a copy of the project up and running on your local machine for development and testing purposes.
30 |
31 | ### Requirements
32 |
33 |
34 | #### Windows
35 | - [Go 1.13+](https://golang.org/)
36 | - Git
37 |
38 | #### macOS
39 | - [Go 1.13+](https://golang.org/)
40 | - Git
41 |
42 | #### Linux
43 | - [Go 1.13+](https://golang.org/)
44 | - packages: [FPM](https://fpm.readthedocs.io/en/latest/installing.html), curl, rpm, libiw-dev
45 | - Git
46 |
47 | ### Compilation
48 |
49 | #### Windows
50 |
51 | **Note!**
52 | IVPN Daemon must be installed appropriately on a target system.
53 | We recommend using [IVPN Client UI](https://github.com/ivpn/desktop-app-ui2) project to build a Windows installer for IVPN software.
54 |
55 | ```
56 | git clone https://github.com/ivpn/desktop-app-cli.git
57 | cd desktop-app-cli
58 | References\Windows\build.bat
59 | ```
60 |
61 | Compiled binaries can be found at: `bin\x86_64\cli`
62 |
63 | #### macOS
64 |
65 | **Note!**
66 | IVPN Daemon must be installed appropriately on a target system.
67 | We recommend using [IVPN Client UI](https://github.com/ivpn/desktop-app-ui2) project to build a macOS DMG package for IVPN software.
68 |
69 | ```
70 | git clone https://github.com/ivpn/desktop-app-cli.git
71 | cd desktop-app-cli/
72 | ./References/macOS/build.sh -v
73 | ```
74 |
75 | Compiled binary can be found at: `References/macOS/_out_bin/`
76 |
77 | #### Linux
78 | Instructions to build Linux DEB and RPM packages of IVPN software ('base' package: daemon + CLI):
79 |
80 | ```
81 | git clone https://github.com/ivpn/desktop-app-daemon.git
82 | git clone https://github.com/ivpn/desktop-app-cli.git
83 | cd desktop-app-cli/References/Linux/
84 | ./build.sh -v
85 | ```
86 |
87 | Compiled packages can be found at `desktop-app-cli/References/Linux/_out_bin`
88 |
89 | **Info**
90 | You may be interested also in [IVPN Client UI](https://github.com/ivpn/desktop-app-ui2) project to build a 'UI' Linux redistributable packages of IVPN software.
91 |
92 |
93 | ##### Manual installation on Linux
94 | Sometimes it is required to have the possibility to install IVPN binaries manually.
95 | It's easy to do it by following the rules described below.
96 |
97 | The ivpn-service is checking the existing of some required files (all files can be found in the repository)
98 | ```
99 | VirtualBox:/opt/ivpn/etc$ ls -l
100 | total 52
101 | -r-------- 1 root root 2358 May 25 16:50 ca.crt
102 | -rwx------ 1 root root 113 May 25 16:50 client.down
103 | -rwx------ 1 root root 1927 May 25 16:50 client.up
104 | -rwx------ 1 root root 5224 May 25 16:50 firewall.sh
105 | -rw------- 1 root root 21524 May 26 20:52 servers.json
106 | -r-------- 1 root root 636 May 25 16:50 ta.key
107 | ```
108 | 1. Build the current project to get 'ivpn service' and 'ivpn cli' binaries.
109 | 2. Create folder `/opt/ivpn/etc`
110 | 3. Copy all required files (see above).
111 | **Note!** Files owner and access rights are important!
112 | 4. Now you can start compiled service binary from the command line (just to check if it works).
113 | **Note!** The service must be started under a privileged user!
114 | **Info!** You can use the command line parameter `--logging` to enable logging for service.
115 | 4.1. Simply run compiled ivpn-cli binary to check if it successfully connects to the service (use separate terminal).
116 | 5. If everything works - you can configure your environment to start ivpn-service automatically with the system boot (we are using systemd for such purposes)
117 |
118 |
119 | ## Versioning
120 |
121 | Project is using [Semantic Versioning (SemVer)](https://semver.org) for creating release versions.
122 |
123 | SemVer is a 3-component system in the format of `x.y.z` where:
124 |
125 | `x` stands for a **major** version
126 | `y` stands for a **minor** version
127 | `z` stands for a **patch**
128 |
129 | So we have: `Major.Minor.Patch`
130 |
131 |
132 | ## Contributing
133 |
134 | If you are interested in contributing to IVPN CLI project, please read our [Contributing Guidelines](/.github/CONTRIBUTING.md).
135 |
136 |
137 | ## Security Policy
138 |
139 | If you want to report a security problem, please read our [Security Policy](/.github/SECURITY.md).
140 |
141 |
142 | ## License
143 |
144 | This project is licensed under the GPLv3 - see the [License](/LICENSE.md) file for details.
145 |
146 |
147 | ## Authors
148 |
149 | See the [Authors](/AUTHORS) file for the list of contributors who participated in this project.
150 |
151 |
152 | ## Acknowledgements
153 |
154 | See the [Acknowledgements](/ACKNOWLEDGEMENTS.md) file for the list of third party libraries used in this project.
155 |
--------------------------------------------------------------------------------
/References/Linux/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # Usage example:
4 | # build-packages.sh -v 0.0.1
5 |
6 | # To be able to build packages the 'fpm' tool shall be installed
7 | # (https://fpm.readthedocs.io/en/latest/installing.html)
8 |
9 | # Useful commands (Ubuntu):
10 | #
11 | # To view *.deb package content:
12 | # dpkg -c ivpn_1.0_amd64.deb
13 | # List of installet packets:
14 | # dpkg --list []
15 | # Install package:
16 | # apt-get install
17 | # Remove packet:
18 | # dpkg --remove
19 | # Remove (2):
20 | # apt-get remove ivpn
21 | # apt-get purge curl
22 | # apt-get autoremove
23 | # Remove repository (https://www.ostechnix.com/how-to-delete-a-repository-and-gpg-key-in-ubuntu/):
24 | # add-apt-repository -r ppa:wireguard/wireguard
25 | # apt update
26 | # List of services:
27 | # systemctl --type=service
28 | # Start service:
29 | # systemctl start ivpn-service
30 | # Remove BROKEN package (which is unable to uninstall by normal ways)
31 | # sudo mv /var/lib/dpkg/info/ivpn.* /tmp/
32 | # sudo dpkg --remove --force-remove-reinstreq ivpn
33 |
34 | cd "$(dirname "$0")"
35 |
36 | # check result of last executed command
37 | CheckLastResult()
38 | {
39 | if ! [ $? -eq 0 ]
40 | then #check result of last command
41 | if [ -n "$1" ]
42 | then
43 | echo $1
44 | else
45 | echo "FAILED"
46 | fi
47 | exit 1
48 | fi
49 | }
50 |
51 | SCRIPT_DIR="$( cd "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )"
52 | OUT_DIR="$SCRIPT_DIR/_out_bin"
53 |
54 | DAEMON_REPO_ABS_PATH=$("./../config/daemon_repo_local_path_abs.sh")
55 | CheckLastResult "Failed to determine location of IVPN Daemon sources. Plase check 'config/daemon_repo_local_path.txt'"
56 |
57 | # ---------------------------------------------------------
58 | # version info variables
59 | VERSION=""
60 | DATE="$(date "+%Y-%m-%d")"
61 | COMMIT="$(git rev-list -1 HEAD)"
62 |
63 | # reading version info from arguments
64 | while getopts ":v:" opt; do
65 | case $opt in
66 | v) VERSION="$OPTARG"
67 | ;;
68 | # \?) echo "Invalid option -$OPTARG" >&2
69 | # ;;
70 | esac
71 | done
72 |
73 | if [ -z "$VERSION" ]
74 | then
75 | echo "Usage:"
76 | echo " $0 -v "
77 | exit 1
78 | fi
79 |
80 | echo '---------------------------'
81 | echo "Building IVPN Daemon ($DAEMON_REPO_ABS_PATH)...";
82 | echo '---------------------------'
83 | $DAEMON_REPO_ABS_PATH/References/Linux/scripts/build-all.sh -v $VERSION
84 | CheckLastResult "ERROR building IVPN Daemon"
85 |
86 | echo '---------------------------'
87 | echo "Building IVPN CLI ...";
88 | echo '---------------------------'
89 | $SCRIPT_DIR/compile-cli.sh -v $VERSION
90 | CheckLastResult "ERROR building IVPN CLI"
91 |
92 | echo "======================================================"
93 | echo "============== Building packages ====================="
94 | echo "======================================================"
95 |
96 | set -e
97 |
98 | TMPDIR="$SCRIPT_DIR/_tmp"
99 | TMPDIRSRVC="$TMPDIR/srvc"
100 | if [ -d "$TMPDIR" ]; then rm -Rf $TMPDIR; fi
101 | mkdir -p $TMPDIR
102 | mkdir -p $TMPDIRSRVC
103 |
104 | cd $TMPDIRSRVC
105 |
106 | echo "Preparing service..."
107 | fpm -v $VERSION -n ivpn-service -s pleaserun -t dir --deb-no-default-config-files /usr/local/bin/ivpn-service
108 |
109 |
110 | CreatePackage()
111 | {
112 | PKG_TYPE=$1
113 | EXTRA_ARGS=$2
114 |
115 | cd $TMPDIR
116 |
117 | # Scripts order is different for different types of packages
118 | # DEB Install:
119 | # (On Install) (On Upgrade)
120 | # before_remove
121 | # before_install before_upgrade\before_install
122 | # after_remove
123 | # after_install after_upgrade\after_install
124 | #
125 | # DEB remove
126 | # before_remove
127 | # after_remove
128 | #
129 | # RPM Install:
130 | # (On Install) (On Upgrade)
131 | # before_install before_upgrade\before_install
132 | # after_install after_upgrade\after_install
133 | # before_remove
134 | # after_remove
135 | #
136 | # RPM remove
137 | # before_remove
138 | # after_remove
139 | #
140 | # NOTE! 'remove' scripts is using from old version!
141 |
142 | fpm -d openvpn $EXTRA_ARGS \
143 | --deb-no-default-config-files -s dir -t $PKG_TYPE -n ivpn -v $VERSION --url https://www.ivpn.net --license "GNU GPL3" \
144 | --template-scripts --template-value pkg=$PKG_TYPE \
145 | --vendor "Privatus Limited" --maintainer "Privatus Limited" \
146 | --description "$(printf "Client for IVPN service (https://www.ivpn.net)\nCommand line interface v$VERSION. Try 'ivpn' from command line.")" \
147 | --before-install "$SCRIPT_DIR/package_scripts/before-install.sh" \
148 | --after-install "$SCRIPT_DIR/package_scripts/after-install.sh" \
149 | --before-remove "$SCRIPT_DIR/package_scripts/before-remove.sh" \
150 | --after-remove "$SCRIPT_DIR/package_scripts/after-remove.sh" \
151 | $DAEMON_REPO_ABS_PATH/References/Linux/etc=/opt/ivpn/ \
152 | $DAEMON_REPO_ABS_PATH/References/Linux/scripts/_out_bin/ivpn-service=/usr/local/bin/ \
153 | $OUT_DIR/ivpn=/usr/local/bin/ \
154 | $TMPDIRSRVC/ivpn-service.dir/usr/share/pleaserun/=/usr/share/pleaserun
155 | }
156 |
157 | echo '---------------------------'
158 | echo "DEB package..."
159 | # to add dependency from another packet add extra arg "-d", example: "-d obfsproxy"
160 | CreatePackage "deb"
161 |
162 | echo '---------------------------'
163 | echo "RPM package..."
164 | CreatePackage "rpm"
165 |
166 | echo '---------------------------'
167 | echo "Copying compiled pachages to '$OUT_DIR'..."
168 | mkdir -p $OUT_DIR
169 | yes | cp -f $TMPDIR/*.* $OUT_DIR
170 |
171 | set +e
172 |
--------------------------------------------------------------------------------
/References/Linux/compile-cli.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | cd "$(dirname "$0")"
4 |
5 | SCRIPT_DIR="$( cd "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )"
6 | OUT_DIR="$SCRIPT_DIR/_out_bin"
7 | OUT_FILE="$OUT_DIR/ivpn"
8 |
9 | set -e
10 |
11 | # make output dir if not exists
12 | mkdir -p $OUT_DIR
13 |
14 | # version info variables
15 | VERSION=""
16 | DATE="$(date "+%Y-%m-%d")"
17 | COMMIT=""
18 |
19 | # reading version info from arguments
20 | while getopts ":v:c:" opt; do
21 | case $opt in
22 | v) VERSION="$OPTARG"
23 | ;;
24 | c) COMMIT="$OPTARG"
25 | ;;
26 | # \?) echo "Invalid option -$OPTARG" >&2
27 | # ;;
28 | esac
29 | done
30 |
31 | if [ -z "$COMMIT" ]; then
32 | COMMIT="$(git rev-list -1 HEAD)"
33 | fi
34 |
35 | echo "======================================================"
36 | echo "============== Compiling IVPN CLI ===================="
37 | echo "======================================================"
38 | echo "Version: $VERSION"
39 | echo "Date : $DATE"
40 | echo "Commit : $COMMIT"
41 |
42 | cd $SCRIPT_DIR/../../
43 |
44 | echo "* updating dependencies..."
45 | go get -v
46 |
47 | if [[ "$@" == *"-debug"* ]]
48 | then
49 | echo "Compiling in DEBUG mode"
50 | go build -tags debug -o "$OUT_FILE" -trimpath -ldflags "-X github.com/ivpn/desktop-app-daemon/version._version=$VERSION -X github.com/ivpn/desktop-app-daemon/version._commit=$COMMIT -X github.com/ivpn/desktop-app-daemon/version._time=$DATE"
51 | else
52 | go build -o "$OUT_FILE" -trimpath -ldflags "-X github.com/ivpn/desktop-app-daemon/version._version=$VERSION -X github.com/ivpn/desktop-app-daemon/version._commit=$COMMIT -X github.com/ivpn/desktop-app-daemon/version._time=$DATE"
53 | fi
54 |
55 | echo "Compiled CLI binary: '$OUT_FILE'"
56 |
57 | set +e
58 |
--------------------------------------------------------------------------------
/References/Linux/package_scripts/after-install.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | echo "[*] After install (<%= pkg %>)"
4 |
5 | NEED_TO_SAVE_INSTRUCTIONS=true
6 | IVPN_ETC="/opt/ivpn/etc"
7 | IVPN_TMP="/opt/ivpn/mutable"
8 | INSTRUCTIONS_FILE="$IVPN_TMP/service_install.txt"
9 | mkdir -p $IVPN_TMP
10 | [ -e $INSTRUCTIONS_FILE ] && rm $INSTRUCTIONS_FILE
11 |
12 |
13 | silent() {
14 | "$@" > /dev/null 2>&1
15 | }
16 |
17 | has_systemd() {
18 | # Some OS vendors put systemd in ... different places ...
19 | [ -d "/lib/systemd/system/" -o -d "/usr/lib/systemd/system" ] && silent which systemctl
20 | }
21 |
22 | try_systemd_install() {
23 | if has_systemd ; then
24 | echo "[ ] systemd detected. Trying to start service ..."
25 | echo "[+] Stopping old service (if exists)"
26 | systemctl stop ivpn-service
27 | echo "[+] Enabling service"
28 | systemctl enable ivpn-service || return 1
29 | echo "[+] Starting service"
30 | systemctl start ivpn-service || return 1
31 |
32 | NEED_TO_SAVE_INSTRUCTIONS=false
33 | return 0
34 | else
35 | echo "[-] Unable to start service automatically"
36 | fi
37 | }
38 |
39 | echo "[+] Defining access rights for files ..."
40 | silent chmod 0400 $IVPN_ETC/* # can read only owner (root)
41 | silent chmod 0600 $IVPN_ETC/servers.json # can read/wrire only owner (root)
42 | silent chmod 0700 $IVPN_ETC/*.sh # can execute only owner (root)
43 | silent chmod 0700 $IVPN_ETC/*.up # can execute only owner (root)
44 | silent chmod 0700 $IVPN_ETC/*.down # can execute only owner (root)
45 | silent chmod 0755 /usr/local/bin/ivpn # can change only owner (root)
46 | silent chmod 0755 /usr/local/bin/ivpn-service # can change only owner (root)
47 |
48 |
49 | IVPN_SAVED_DNS_FILE="/etc/resolv.conf.ivpnsave"
50 | if [ -f $IVPN_SAVED_DNS_FILE ]; then
51 | echo "[+] restoring DNS configuration from previous installation"
52 | mv $IVPN_SAVED_DNS_FILE /etc/resolv.conf || echo "[-] Restoring DNS failed"
53 | fi
54 |
55 | echo "[+] Service install start (pleaserun) ..."
56 | INSTALL_OUTPUT=$(sh /usr/share/pleaserun/ivpn-service/install.sh)
57 | if [ $? -eq 0 ]; then
58 | # Print output of the install script
59 | echo $INSTALL_OUTPUT
60 |
61 | try_systemd_install
62 | else
63 | # Print output of the install script
64 | echo $INSTALL_OUTPUT
65 | echo "[-] Service install FAILED!"
66 | fi
67 |
68 | if $NEED_TO_SAVE_INSTRUCTIONS == true ; then
69 | echo $INSTALL_OUTPUT > $INSTRUCTIONS_FILE
70 | echo "[!] Service start instructions saved into file: '$INSTRUCTIONS_FILE'"
71 | fi
72 |
73 | FILE_ACCID_TO_UPGRADE="/opt/ivpn/mutable/toUpgradeID.tmp"
74 | if [ -f $FILE_ACCID_TO_UPGRADE ]; then
75 | # It is an upgrade.
76 | # We need to re-login after installation finished.
77 | # Read account ID
78 | ACCID=$(cat $FILE_ACCID_TO_UPGRADE) || echo "[-] Finishing installation: Failed to read accountID to re-login"
79 |
80 | # do not forget to remove temporary file
81 | silent rm $FILE_ACCID_TO_UPGRADE
82 |
83 | if [ ! -z "$ACCID" ]; then
84 | # giving a chance for a daemon to fully start
85 | sleep 1
86 | echo "[+] Logging in ..."
87 | /usr/local/bin/ivpn login $ACCID #|| echo "[-] Finishing installation: Failed to to re-login (try#1)"
88 | if [ ! $? -eq 0 ]; then
89 | echo "[-] Finishing installation: Failed to to re-login (try#1)"
90 | echo "[ ] Retry ..."
91 | sleep 3
92 | /usr/local/bin/ivpn login $ACCID || echo "[-] Finishing installation: Failed to to re-login (try#2)"
93 | fi
94 | fi
95 | fi
96 |
--------------------------------------------------------------------------------
/References/Linux/package_scripts/after-remove.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | echo "[*] After remove (<%= pkg %>)"
4 |
5 | PKG_TYPE=<%= pkg %>
6 | if [ "$PKG_TYPE" = "rpm" ]; then
7 | if [ -f /opt/ivpn/mutable/rpm_upgrade.lock ]; then
8 | echo "[ ] Upgrade detected. Remove operations skipped"
9 | rm /opt/ivpn/mutable/rpm_upgrade.lock || echo "[-] Failed to remove rpm_upgrade.lock"
10 | exit 0
11 | fi
12 | fi
13 |
14 | silent() {
15 | "$@" > /dev/null 2>&1
16 | }
17 |
18 | has_systemd() {
19 | # Some OS vendors put systemd in ... different places ...
20 | [ -d "/lib/systemd/system/" -o -d "/usr/lib/systemd/system" ] && silent which systemctl
21 | }
22 |
23 | try_systemd_stop() {
24 | if has_systemd ; then
25 | echo "[ ] systemd detected. Trying to stop service ..."
26 |
27 | echo "[+] Stopping service"
28 | silent systemctl stop ivpn-service
29 |
30 | echo "[+] Disabling service"
31 | silent systemctl disable ivpn-service
32 |
33 | if [ -f "/etc/systemd/system/ivpn-service.service" ]; then
34 | echo "[+] Removing service"
35 | silent rm /etc/systemd/system/ivpn-service.service
36 | fi
37 | if [ -f "/usr/lib/systemd/system/ivpn-service.service" ]; then
38 | echo "[+] Removing service"
39 | silent rm /usr/lib/systemd/system/ivpn-service.service
40 | fi
41 | fi
42 | }
43 |
44 | FILE_ACCID_TO_UPGRADE="/opt/ivpn/mutable/toUpgradeID.tmp"
45 | if [ -f $FILE_ACCID_TO_UPGRADE ]; then
46 | # It is an upgrade.
47 | # We need to re-login after installation finished.
48 | # Therefore we should not remove info about account ID.
49 | # Read into temporary variable
50 | ACCID=$(cat $FILE_ACCID_TO_UPGRADE) || echo "[-] Failed to read accountID to re-login"
51 | fi
52 |
53 | IVPN_DIR="/opt/ivpn"
54 | IVPN_TMP="/opt/ivpn/mutable"
55 | IVPN_LOG="/opt/ivpn/log"
56 | IVPN_ETC="/opt/ivpn/etc"
57 | if [ -d $IVPN_TMP ] ; then
58 | echo "[+] Removing other files ..."
59 | # Normally, all files which were installed, deleted automatically
60 | # But ivpn-service also writing to 'mutable' additional temporary files (uninstaller know nothing about them)
61 | # Therefore, we are completely removing all content of '/opt/ivpn/mutable'
62 | rm -rf $IVPN_TMP|| echo "[-] Removing '$IVPN_TMP' folder failed"
63 | rm -rf $IVPN_LOG|| echo "[-] Removing '$IVPN_LOG' folder failed"
64 | #rm -rf $IVPN_ETC|| echo "[-] Removing '$IVPN_ETC' folder failed"
65 | #rm -rf $IVPN_DIR|| echo "[-] Removing '$IVPN_DIR' folder failed"
66 | #remove 'ivpn' folder (if empy)
67 | silent sudo rmdir $IVPN_DIR
68 | fi
69 |
70 | if [ ! -z "$ACCID" ]; then
71 | # It is an upgrade.
72 | # We need to re-login after installation finished.
73 | # Therefore we should not remove info about account ID
74 | # Save to a file from temporary variable
75 | DIR=$(dirname $FILE_ACCID_TO_UPGRADE) || echo "[-] Failed to save accountID to re-login (1)"
76 | mkdir -p $DIR || echo "[-] Failed to save accountID to re-login (2)"
77 | echo $ACCID > $FILE_ACCID_TO_UPGRADE || echo "[-] Failed to save accountID to re-login (3)"
78 | fi
79 |
80 | IVPN_SAVED_DNS_FILE="/etc/resolv.conf.ivpnsave"
81 | if [ -f $IVPN_SAVED_DNS_FILE ]; then
82 | echo "[+] restoring DNS configuration"
83 | mv $IVPN_SAVED_DNS_FILE /etc/resolv.conf || echo "[-] Restoring DNS failed"
84 | fi
85 |
86 | try_systemd_stop
--------------------------------------------------------------------------------
/References/Linux/package_scripts/before-install.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | echo "[*] Before install (<%= pkg %>)"
4 |
5 | PKG_TYPE=<%= pkg %>
6 | if [ "$PKG_TYPE" = "rpm" ]; then
7 | if [ -f /usr/local/bin/ivpn ]; then
8 | # Necessary to check if the previous version can be upgraded.
9 | # Old versions have broken installer. It is not possible to upgrade correctly.
10 | BUILD_DATE=$(/usr/local/bin/ivpn -h | grep -o date:[0-9]*-[0-9]*-[0-9]* | cut -d ':' -f 2) || echo "[-] Failed to determine build date of the old version"
11 | if [ $BUILD_DATE \< "2020-05-29" ]; then
12 | echo "[!] Old version detected (date:$BUILD_DATE)"
13 | echo "**************************************************"
14 | echo "* PLEASE, UNINSTALL THE OLD VERSION FIRST! *"
15 | echo "**************************************************"
16 | exit 1
17 | fi
18 |
19 | # Skip running 'remove' scripts when upgrading
20 | mkdir -p /opt/ivpn/mutable
21 | echo "upgrading" > /opt/ivpn/mutable/rpm_upgrade.lock || echo "[-] Failed to save rpm_upgrade.lock"
22 | fi
23 | fi
24 |
25 | if [ -f /opt/ivpn/mutable/upgradeID.tmp ]; then
26 | echo "[ ] Upgrade detected"
27 | mv /opt/ivpn/mutable/upgradeID.tmp /opt/ivpn/mutable/toUpgradeID.tmp || echo "[-] Failed to prepare accountID to re-login"
28 | fi
29 |
30 | if [ -f /usr/local/bin/ivpn ]; then
31 | echo "[+] Trying to disable firewall (before install)..."
32 | /usr/local/bin/ivpn firewall -off || echo "[-] Failed to disable firewall"
33 |
34 | echo "[+] Trying to disconnect (before install) ..."
35 | /usr/local/bin/ivpn disconnect || echo "[-] Failed to disconnect"
36 | fi
--------------------------------------------------------------------------------
/References/Linux/package_scripts/before-remove.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | echo "[*] Before remove (<%= pkg %>)"
4 |
5 | PKG_TYPE=<%= pkg %>
6 |
7 | echo "[+] Disabling firewall (before remove) ..."
8 | /usr/local/bin/ivpn firewall -off || echo "[-] Failed to disable firewall"
9 |
10 | echo "[+] Disconnecting (before remove) ..."
11 | /usr/local/bin/ivpn disconnect || echo "[-] Failed to disconnect"
12 |
13 | if [ "$PKG_TYPE" = "rpm" ]; then
14 | if [ -f /opt/ivpn/mutable/rpm_upgrade.lock ]; then
15 | echo "[ ] Upgrade detected. Remove operations skipped"
16 | exit 0
17 | fi
18 | fi
19 |
20 | if [ -f /opt/ivpn/mutable/settings.json ]; then
21 | # In case of installing new version, we have to login back with current logged-in accountID after installation finished.
22 | # Therefore we are saving accountID into temporary file (will be deleted after 'after_install' script execution)
23 | echo "[+] Preparing upgrade data ..."
24 | ACCID=$(cat /opt/ivpn/mutable/settings.json | grep -o \"AccountID\":\"[a-zA-Z0-9]*\" | cut -d '"' -f 4) || echo "[-] Failed to read accountID"
25 | if [ ! -z "$ACCID" ]; then
26 | echo $ACCID > /opt/ivpn/mutable/upgradeID.tmp || echo "[-] Failed to save accountID into temporary file"
27 | fi
28 | fi
29 |
30 | echo "[+] Logging out ..."
31 | /usr/local/bin/ivpn logout || echo "[-] Failed to log out"
32 |
33 | echo "[+] Service cleanup (pleaserun) ..."
34 | sh /usr/share/pleaserun/ivpn-service/generate-cleanup.sh || echo "[-] Service cleanup FAILED!"
--------------------------------------------------------------------------------
/References/Windows/build.bat:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 | setlocal
3 | set SCRIPTDIR=%~dp0
4 | set APPVER=%1
5 | set COMMIT=""
6 | set DATE=""
7 |
8 | echo ==================================================
9 | echo ============ BUILDING IVPN CLI ===================
10 | echo ==================================================
11 |
12 | rem Getting info about current date
13 | FOR /F "tokens=* USEBACKQ" %%F IN (`date /T`) DO SET DATE=%%F
14 | rem remove spaces
15 | set DATE=%DATE: =%
16 |
17 | rem Getting info about commit
18 | cd %SCRIPTDIR%\..\..
19 | FOR /F "tokens=* USEBACKQ" %%F IN (`git rev-list -1 HEAD`) DO SET COMMIT=%%F
20 |
21 | if "%APPVER%" == "" set APPVER=unknown
22 | rem Removing spaces from input variables
23 | if NOT "%APPVER%" == "" set APPVER=%APPVER: =%
24 | if NOT "%COMMIT%" == "" set COMMIT=%COMMIT: =%
25 | if NOT "%DATE%" == "" set DATE=%DATE: =%
26 |
27 | echo APPVER: %APPVER%
28 | echo COMMIT: %COMMIT%
29 | echo DATE : %DATE%
30 |
31 | call :build || goto :error
32 | goto :success
33 |
34 | :build
35 | echo [*] Building IVPN CLI
36 |
37 | if exist "bin\x86\cli\ivpn.exe" del "bin\x86\cli\ivpn.exe" || exit /b 1
38 | if exist "bin\x86_64\cli\ivpn.exe" del "bin\x86_64\cli\ivpn.exe" || exit /b 1
39 |
40 | set GOOS=windows
41 |
42 | echo [ ] x86 ...
43 | set GOARCH=386
44 |
45 | go build -tags release -o "bin\x86\cli\ivpn.exe" -trimpath -ldflags "-X github.com/ivpn/desktop-app-daemon/version._version=%APPVER% -X github.com/ivpn/desktop-app-daemon/version._commit=%COMMIT% -X github.com/ivpn/desktop-app-daemon/version._time=%DATE%" || exit /b 1
46 |
47 | echo [ ] x86_64 ...
48 | set GOARCH=amd64
49 | go build -tags release -o "bin\x86_64\cli\ivpn.exe" -trimpath -ldflags "-X github.com/ivpn/desktop-app-daemon/version._version=%APPVER% -X github.com/ivpn/desktop-app-daemon/version._commit=%COMMIT% -X github.com/ivpn/desktop-app-daemon/version._time=%DATE%" || exit /b 1
50 |
51 | goto :eof
52 |
53 | :success
54 | echo [*] Success.
55 | go version
56 | exit /b 0
57 |
58 | :error
59 | echo [!] IVPN Service build script FAILED with error #%errorlevel%.
60 | exit /b %errorlevel%
61 |
--------------------------------------------------------------------------------
/References/config/daemon_repo_local_path.txt:
--------------------------------------------------------------------------------
1 | ../../../desktop-app-daemon
--------------------------------------------------------------------------------
/References/config/daemon_repo_local_path_abs.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Script prints absolute path to local repository of IVPN Daemon sources
4 | # It reads relative path info from 'config/daemon_repo_local_path.txt'
5 | # How to use in subscripts:
6 | # DAEMON_REPO_ABS_PATH=$("./daemon_repo_local_path_abs.sh")
7 |
8 | # Exit immediately if a command exits with a non-zero status.
9 | set -e
10 |
11 | cd "$(dirname "$0")"
12 | RELATIVE_PATH=$(<'daemon_repo_local_path.txt')
13 | cd $RELATIVE_PATH
14 |
15 | pwd
16 |
--------------------------------------------------------------------------------
/References/macOS/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | cd "$(dirname "$0")"
4 |
5 | SCRIPT_DIR="$( cd "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )"
6 | OUT_DIR="$SCRIPT_DIR/_out_bin"
7 | OUT_FILE="$OUT_DIR/ivpn"
8 |
9 | set -e
10 |
11 | # make output dir if not exists
12 | mkdir -p $OUT_DIR
13 |
14 | # version info variables
15 | VERSION=""
16 | DATE="$(date "+%Y-%m-%d")"
17 | COMMIT="$(git rev-list -1 HEAD)"
18 |
19 | # reading version info from arguments
20 | while getopts ":v:" opt; do
21 | case $opt in
22 | v) VERSION="$OPTARG"
23 | ;;
24 | # \?) echo "Invalid option -$OPTARG" >&2
25 | # ;;
26 | esac
27 | done
28 |
29 | echo "======================================================"
30 | echo "============== Compiling IVPN CLI ===================="
31 | echo "======================================================"
32 | echo "Version: $VERSION"
33 | echo "Date : $DATE"
34 | echo "Commit : $COMMIT"
35 |
36 | cd $SCRIPT_DIR/../../
37 |
38 | if [[ "$@" == *"-debug"* ]]
39 | then
40 | echo "Compiling in DEBUG mode"
41 | go build -tags debug -o "$OUT_FILE" -trimpath -ldflags "-X github.com/ivpn/desktop-app-daemon/version._version=$VERSION -X github.com/ivpn/desktop-app-daemon/version._commit=$COMMIT -X github.com/ivpn/desktop-app-daemon/version._time=$DATE"
42 | else
43 | go build -o "$OUT_FILE" -trimpath -ldflags "-X github.com/ivpn/desktop-app-daemon/version._version=$VERSION -X github.com/ivpn/desktop-app-daemon/version._commit=$COMMIT -X github.com/ivpn/desktop-app-daemon/version._time=$DATE"
44 | fi
45 |
46 | echo "Compiled CLI binary: '$OUT_FILE'"
47 |
48 | set +e
49 |
--------------------------------------------------------------------------------
/commands/account.go:
--------------------------------------------------------------------------------
1 | //
2 | // IVPN command line interface (CLI)
3 | // https://github.com/ivpn/desktop-app-cli
4 | //
5 | // Created by Stelnykovych Alexandr.
6 | // Copyright (c) 2020 Privatus Limited.
7 | //
8 | // This file is part of the IVPN command line interface.
9 | //
10 | // The IVPN command line interface is free software: you can redistribute it and/or
11 | // modify it under the terms of the GNU General Public License as published by the Free
12 | // Software Foundation, either version 3 of the License, or (at your option) any later version.
13 | //
14 | // The IVPN command line interface is distributed in the hope that it will be useful,
15 | // but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16 | // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
17 | // details.
18 | //
19 | // You should have received a copy of the GNU General Public License
20 | // along with the IVPN command line interface. If not, see .
21 | //
22 |
23 | package commands
24 |
25 | import (
26 | "fmt"
27 | "os"
28 | "text/tabwriter"
29 | "time"
30 |
31 | "github.com/ivpn/desktop-app-cli/flags"
32 | "github.com/ivpn/desktop-app-daemon/api/types"
33 | "github.com/ivpn/desktop-app-daemon/service"
34 | "github.com/ivpn/desktop-app-daemon/vpn"
35 | "golang.org/x/crypto/ssh/terminal"
36 | )
37 |
38 | type CmdLogout struct {
39 | flags.CmdInfo
40 | }
41 |
42 | func (c *CmdLogout) Init() {
43 | c.Initialize("logout", "Logout from this device (if logged-in)")
44 | }
45 |
46 | func (c *CmdLogout) Run() error {
47 | return doLogout()
48 | }
49 |
50 | //----------------------------------------------------------------------------------------
51 | type CmdLogin struct {
52 | flags.CmdInfo
53 | accountID string
54 | force bool
55 | }
56 |
57 | func (c *CmdLogin) Init() {
58 | c.Initialize("login", "Login operation (register ACCOUNT_ID on this device)")
59 | c.DefaultStringVar(&c.accountID, "ACCOUNT_ID")
60 | c.BoolVar(&c.force, "force", false, "Log out from all other devices (applicable only with 'login' option)")
61 | }
62 |
63 | func (c *CmdLogin) Run() error {
64 | return doLogin(c.accountID, c.force)
65 | }
66 |
67 | func doLogin(accountID string, force bool) error {
68 | // checking if we are logged-in
69 | _proto.SessionStatus() // do not check error response (could be received 'not logged in' errors)
70 | helloResp := _proto.GetHelloResponse()
71 | if len(helloResp.Session.Session) != 0 {
72 | fmt.Println("Already logged in")
73 | PrintTips([]TipType{TipLogout})
74 | return fmt.Errorf("unable login (please, log out first)")
75 | }
76 |
77 | // login
78 | if len(accountID) == 0 {
79 | fmt.Print("Enter your Account ID: ")
80 | data, err := terminal.ReadPassword(0)
81 | if err != nil {
82 | return fmt.Errorf("failed to read accountID: %w", err)
83 | }
84 | accountID = string(data)
85 | }
86 |
87 | apiStatus, err := _proto.SessionNew(accountID, force)
88 | if err != nil {
89 | if apiStatus == types.CodeSessionsLimitReached {
90 | PrintTips([]TipType{TipForceLogin})
91 | }
92 | return err
93 | }
94 |
95 | fmt.Println("Logged in")
96 | PrintTips([]TipType{TipServers, TipConnectHelp})
97 |
98 | return nil
99 | }
100 |
101 | //----------------------------------------------------------------------------------------
102 |
103 | type CmdAccount struct {
104 | flags.CmdInfo
105 | }
106 |
107 | func (c *CmdAccount) Init() {
108 | c.Initialize("account", "Get info about current account")
109 | }
110 |
111 | func (c *CmdAccount) Run() error {
112 | return checkStatus()
113 | }
114 |
115 | //----------------------------------------------------------------------------------------
116 |
117 | func doLogout() error {
118 | // checking if we are logged-in
119 | _proto.SessionStatus() // do not check error response (could be received 'not logged in' errors)
120 | helloResp := _proto.GetHelloResponse()
121 | if len(helloResp.Session.Session) == 0 {
122 | return fmt.Errorf("already logged out")
123 | }
124 |
125 | // do not allow to logout if VPN connected
126 | state, _, err := _proto.GetVPNState()
127 | if err != nil {
128 | return err
129 | }
130 | if state != vpn.DISCONNECTED {
131 | PrintTips([]TipType{TipDisconnect})
132 | return fmt.Errorf("unable to log out (please, disconnect VPN first)")
133 | }
134 |
135 | // delete session
136 | err = _proto.SessionDelete()
137 | if err != nil {
138 | return err
139 | }
140 |
141 | fmt.Println("Logged out")
142 | PrintTips([]TipType{TipLogin})
143 |
144 | return nil
145 | }
146 |
147 | func checkStatus() error {
148 | stat, err := _proto.SessionStatus()
149 |
150 | helloResp := _proto.GetHelloResponse()
151 | if len(helloResp.Command) > 0 && (len(helloResp.Session.Session) == 0) {
152 | // We received 'hello' response but no session info - print tips to login
153 | fmt.Printf("Error: Not logged in")
154 |
155 | fmt.Println()
156 | PrintTips([]TipType{TipLogin})
157 |
158 | return service.ErrorNotLoggedIn{}
159 | }
160 |
161 | if err != nil {
162 | return err
163 | }
164 |
165 | if stat.APIStatus != types.CodeSuccess {
166 | return fmt.Errorf("API error: %v %v", stat.APIStatus, stat.APIErrorMessage)
167 | }
168 |
169 | acc := stat.Account
170 | w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
171 |
172 | fmt.Fprintln(w, fmt.Sprintf("Account ID:\t%v", helloResp.Session.AccountID))
173 |
174 | if acc.IsFreeTrial {
175 | fmt.Fprintln(w, fmt.Sprintf("Plan:\tFree Trial"))
176 | } else {
177 | fmt.Fprintln(w, fmt.Sprintf("Plan:\t%v", acc.CurrentPlan))
178 | }
179 | fmt.Fprintln(w, fmt.Sprintf("Active until:\t%v", time.Unix(acc.ActiveUntil, 0)))
180 | if stat.Account.Limit > 0 {
181 | fmt.Fprintln(w, fmt.Sprintf("Devices limit:\t%v", acc.Limit))
182 | }
183 | if acc.Upgradable == true && len(acc.UpgradeToPlan) > 0 && len(acc.UpgradeToURL) > 0 {
184 | fmt.Fprintln(w, fmt.Sprintf("Upgrade to:\t%v (%v)", acc.UpgradeToPlan, acc.UpgradeToURL))
185 | }
186 | w.Flush()
187 |
188 | return nil
189 | }
190 |
--------------------------------------------------------------------------------
/commands/base.go:
--------------------------------------------------------------------------------
1 | //
2 | // IVPN command line interface (CLI)
3 | // https://github.com/ivpn/desktop-app-cli
4 | //
5 | // Created by Stelnykovych Alexandr.
6 | // Copyright (c) 2020 Privatus Limited.
7 | //
8 | // This file is part of the IVPN command line interface.
9 | //
10 | // The IVPN command line interface is free software: you can redistribute it and/or
11 | // modify it under the terms of the GNU General Public License as published by the Free
12 | // Software Foundation, either version 3 of the License, or (at your option) any later version.
13 | //
14 | // The IVPN command line interface is distributed in the hope that it will be useful,
15 | // but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16 | // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
17 | // details.
18 | //
19 | // You should have received a copy of the GNU General Public License
20 | // along with the IVPN command line interface. If not, see .
21 | //
22 |
23 | package commands
24 |
25 | import (
26 | "fmt"
27 | "os"
28 | "strings"
29 | "text/tabwriter"
30 | "time"
31 |
32 | "github.com/ivpn/desktop-app-cli/protocol"
33 | apitypes "github.com/ivpn/desktop-app-daemon/api/types"
34 | "github.com/ivpn/desktop-app-daemon/protocol/types"
35 | "github.com/ivpn/desktop-app-daemon/vpn"
36 | )
37 |
38 | var _proto *protocol.Client
39 |
40 | // Initialize initializes commands. Must be called before using any command.
41 | func Initialize(proto *protocol.Client) {
42 | _proto = proto
43 | }
44 |
45 | func printAccountInfo(w *tabwriter.Writer, accountID string) *tabwriter.Writer {
46 | if w == nil {
47 | w = tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
48 | }
49 |
50 | if len(accountID) > 0 {
51 | return w // Do nothing in case of logged in
52 | }
53 |
54 | fmt.Fprintln(w, fmt.Sprintf("Account\t:\t%v", "Not logged in"))
55 |
56 | return w
57 | }
58 |
59 | func printState(w *tabwriter.Writer, state vpn.State, connected types.ConnectedResp, serverInfo string, exitServerInfo string) *tabwriter.Writer {
60 |
61 | if w == nil {
62 | w = tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
63 | }
64 |
65 | fmt.Fprintln(w, fmt.Sprintf("VPN\t:\t%v", state))
66 |
67 | if len(serverInfo) > 0 {
68 | fmt.Fprintln(w, fmt.Sprintf("\t\t%v", serverInfo))
69 | if len(exitServerInfo) > 0 {
70 | fmt.Fprintln(w, fmt.Sprintf("\t\t%v (Multi-Hop exit server)", exitServerInfo))
71 | }
72 | }
73 |
74 | if state != vpn.CONNECTED {
75 | return w
76 | }
77 | since := time.Unix(connected.TimeSecFrom1970, 0)
78 | fmt.Fprintln(w, fmt.Sprintf(" Protocol\t:\t%v", connected.VpnType))
79 | fmt.Fprintln(w, fmt.Sprintf(" Local IP\t:\t%v", connected.ClientIP))
80 | fmt.Fprintln(w, fmt.Sprintf(" Server IP\t:\t%v", connected.ServerIP))
81 | fmt.Fprintln(w, fmt.Sprintf(" Connected\t:\t%v", since))
82 |
83 | return w
84 | }
85 |
86 | func printDNSState(w *tabwriter.Writer, dns string, servers *apitypes.ServersInfoResponse) *tabwriter.Writer {
87 | if w == nil {
88 | w = tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
89 | }
90 |
91 | dns = strings.TrimSpace(dns)
92 | if len(dns) == 0 {
93 | fmt.Fprintln(w, fmt.Sprintf("DNS\t:\tDefault (auto)"))
94 | return w
95 | }
96 |
97 | antitrackerText := strings.Builder{}
98 |
99 | isAntitracker, isAtHardcore := IsAntiTrackerIP(dns, servers)
100 | if isAtHardcore {
101 | antitrackerText.WriteString("Enabled (Hardcore)")
102 | } else if isAntitracker {
103 | antitrackerText.WriteString("Enabled")
104 | }
105 |
106 | if antitrackerText.Len() > 0 {
107 | fmt.Fprintln(w, fmt.Sprintf("AntiTracker\t:\t%v", antitrackerText.String()))
108 | } else {
109 | fmt.Fprintln(w, fmt.Sprintf("DNS\t:\t%v", dns))
110 | }
111 |
112 | return w
113 | }
114 |
115 | func printFirewallState(w *tabwriter.Writer, isEnabled, isPersistent, isAllowLAN, isAllowMulticast bool) *tabwriter.Writer {
116 | if w == nil {
117 | w = tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
118 | }
119 |
120 | fwState := "Disabled"
121 | if isEnabled {
122 | fwState = "Enabled"
123 | }
124 |
125 | fmt.Fprintln(w, fmt.Sprintf("Firewall\t:\t%v", fwState))
126 | fmt.Fprintln(w, fmt.Sprintf(" Allow LAN\t:\t%v", isAllowLAN))
127 | if isPersistent {
128 | fmt.Fprintln(w, fmt.Sprintf(" Persistent\t:\t%v", isPersistent))
129 | }
130 |
131 | return w
132 | }
133 |
--------------------------------------------------------------------------------
/commands/config/config.go:
--------------------------------------------------------------------------------
1 | //
2 | // IVPN command line interface (CLI)
3 | // https://github.com/ivpn/desktop-app-cli
4 | //
5 | // Created by Stelnykovych Alexandr.
6 | // Copyright (c) 2020 Privatus Limited.
7 | //
8 | // This file is part of the IVPN command line interface.
9 | //
10 | // The IVPN command line interface is free software: you can redistribute it and/or
11 | // modify it under the terms of the GNU General Public License as published by the Free
12 | // Software Foundation, either version 3 of the License, or (at your option) any later version.
13 | //
14 | // The IVPN command line interface is distributed in the hope that it will be useful,
15 | // but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16 | // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
17 | // details.
18 | //
19 | // You should have received a copy of the GNU General Public License
20 | // along with the IVPN command line interface. If not, see .
21 | //
22 |
23 | package config
24 |
25 | import (
26 | "encoding/json"
27 | "fmt"
28 | "io/ioutil"
29 | "os"
30 | "path/filepath"
31 | "sync"
32 | )
33 |
34 | var _mutex sync.Mutex
35 |
36 | // Configuration - local configuration
37 | type Configuration struct {
38 | CustomDNS string
39 | Antitracker bool
40 | AntitrackerHardcore bool
41 | }
42 |
43 | // configDir is the path to configuration dirrectory
44 | func configDir() (string, error) {
45 | home, err := os.UserHomeDir()
46 | if err != nil {
47 | return "", err
48 | }
49 |
50 | dir := filepath.Join(home, ".ivpn")
51 | if err := os.MkdirAll(dir, os.ModePerm); err != nil {
52 | return "", err
53 | }
54 | return dir, nil
55 | }
56 |
57 | func filePath() (string, error) {
58 | dir, err := configDir()
59 | if err != nil {
60 | return "", fmt.Errorf("unable to find configuration folder: %w", err)
61 | }
62 | return filepath.Join(dir, "config"), nil
63 | }
64 |
65 | // SaveConfig saves configuration to local storage
66 | func SaveConfig(conf Configuration) error {
67 | _mutex.Lock()
68 | defer _mutex.Unlock()
69 |
70 | data, err := json.Marshal(conf)
71 | if err != nil {
72 | return err
73 | }
74 |
75 | file, err := filePath()
76 | if err != nil {
77 | return fmt.Errorf("error determining configuration file location: %w", err)
78 | }
79 | return ioutil.WriteFile(file, data, 0600) // read only for owner
80 | }
81 |
82 | // GetConfig returns local configuration
83 | func GetConfig() (Configuration, error) {
84 | _mutex.Lock()
85 | defer _mutex.Unlock()
86 |
87 | conf := Configuration{}
88 |
89 | file, err := filePath()
90 | if err != nil {
91 | return conf, fmt.Errorf("error determining configuration file destination: %w", err)
92 | }
93 |
94 | _, err = os.Stat(file)
95 | if os.IsNotExist(err) {
96 | return conf, nil
97 | }
98 |
99 | data, err := ioutil.ReadFile(file)
100 | if err != nil {
101 | return conf, fmt.Errorf("failed to read configuration: %w", err)
102 | }
103 |
104 | err = json.Unmarshal(data, &conf)
105 | if err != nil {
106 | return conf, fmt.Errorf("failed to deserialize configuration: %w", err)
107 | }
108 |
109 | return conf, nil
110 | }
111 |
--------------------------------------------------------------------------------
/commands/config/last_connection.go:
--------------------------------------------------------------------------------
1 | //
2 | // IVPN command line interface (CLI)
3 | // https://github.com/ivpn/desktop-app-cli
4 | //
5 | // Created by Stelnykovych Alexandr.
6 | // Copyright (c) 2020 Privatus Limited.
7 | //
8 | // This file is part of the IVPN command line interface.
9 | //
10 | // The IVPN command line interface is free software: you can redistribute it and/or
11 | // modify it under the terms of the GNU General Public License as published by the Free
12 | // Software Foundation, either version 3 of the License, or (at your option) any later version.
13 | //
14 | // The IVPN command line interface is distributed in the hope that it will be useful,
15 | // but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16 | // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
17 | // details.
18 | //
19 | // You should have received a copy of the GNU General Public License
20 | // along with the IVPN command line interface. If not, see .
21 | //
22 |
23 | package config
24 |
25 | import (
26 | "encoding/json"
27 | "io/ioutil"
28 | "os"
29 | "path/filepath"
30 | )
31 |
32 | func lastConnectionInfoFile() string {
33 | dir, err := configDir()
34 | if err != nil {
35 | return ""
36 | }
37 | return filepath.Join(dir, "last_connection")
38 | }
39 |
40 | // LastConnectionInfo information about last connection parameters
41 | type LastConnectionInfo struct {
42 | Gateway string
43 | Port string
44 | Obfsproxy bool
45 | FirewallOff bool
46 | DNS string
47 | Antitracker bool
48 | AntitrackerHard bool
49 |
50 | MultiopExitSvr string // variable name spelling error -> 'MultihopExitSvr' (keeped as is for compatibility with previous versions)
51 | }
52 |
53 | // LastConnectionExist - returns 'true' if available info about last successful connection
54 | func LastConnectionExist() bool {
55 | if _, err := os.Stat(lastConnectionInfoFile()); err == nil {
56 | return true
57 | }
58 | return false
59 | }
60 |
61 | // SaveLastConnectionInfo save last connection parameters in local storage
62 | func SaveLastConnectionInfo(ci LastConnectionInfo) {
63 | data, err := json.Marshal(ci)
64 | if err != nil {
65 | return
66 | }
67 |
68 | if file := lastConnectionInfoFile(); len(file) > 0 {
69 | ioutil.WriteFile(file, data, 0600) // read only for owner
70 | }
71 | }
72 |
73 | // RestoreLastConnectionInfo restore last connection info from local storage
74 | func RestoreLastConnectionInfo() *LastConnectionInfo {
75 | ci := LastConnectionInfo{}
76 |
77 | file := ""
78 | if file = lastConnectionInfoFile(); len(file) == 0 {
79 | return nil
80 | }
81 |
82 | data, err := ioutil.ReadFile(file)
83 | if err != nil {
84 | return nil
85 | }
86 |
87 | err = json.Unmarshal(data, &ci)
88 | if err != nil {
89 | return nil
90 | }
91 |
92 | return &ci
93 | }
94 |
--------------------------------------------------------------------------------
/commands/connection.go:
--------------------------------------------------------------------------------
1 | //
2 | // IVPN command line interface (CLI)
3 | // https://github.com/ivpn/desktop-app-cli
4 | //
5 | // Created by Stelnykovych Alexandr.
6 | // Copyright (c) 2020 Privatus Limited.
7 | //
8 | // This file is part of the IVPN command line interface.
9 | //
10 | // The IVPN command line interface is free software: you can redistribute it and/or
11 | // modify it under the terms of the GNU General Public License as published by the Free
12 | // Software Foundation, either version 3 of the License, or (at your option) any later version.
13 | //
14 | // The IVPN command line interface is distributed in the hope that it will be useful,
15 | // but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16 | // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
17 | // details.
18 | //
19 | // You should have received a copy of the GNU General Public License
20 | // along with the IVPN command line interface. If not, see .
21 | //
22 |
23 | package commands
24 |
25 | import (
26 | "fmt"
27 | "net"
28 | "strconv"
29 | "strings"
30 |
31 | "github.com/ivpn/desktop-app-cli/commands/config"
32 | "github.com/ivpn/desktop-app-cli/flags"
33 | apitypes "github.com/ivpn/desktop-app-daemon/api/types"
34 | "github.com/ivpn/desktop-app-daemon/protocol/types"
35 | "github.com/ivpn/desktop-app-daemon/service"
36 | "github.com/ivpn/desktop-app-daemon/vpn"
37 | )
38 |
39 | type port struct {
40 | port int
41 | tcp bool
42 | }
43 |
44 | func (p *port) IsTCP() int {
45 | if p.tcp {
46 | return 1
47 | }
48 | return 0
49 | }
50 |
51 | func (p *port) String() string {
52 | protoName := "UDP"
53 | if p.tcp {
54 | protoName = "TCP"
55 | }
56 | return fmt.Sprintf("%s:%d", protoName, p.port)
57 | }
58 |
59 | var (
60 | portsWireGuard = [...]port{
61 | port{port: 2049},
62 | port{port: 2050},
63 | port{port: 53},
64 | port{port: 1194},
65 | port{port: 30587},
66 | port{port: 41893},
67 | port{port: 48574},
68 | port{port: 58237}}
69 |
70 | portsOpenVpn = [...]port{
71 | port{port: 2049},
72 | port{port: 2050},
73 | port{port: 53},
74 | port{port: 1194},
75 | port{port: 443, tcp: true},
76 | port{port: 1443, tcp: true},
77 | port{port: 80, tcp: true}}
78 | )
79 |
80 | type CmdDisconnect struct {
81 | flags.CmdInfo
82 | }
83 |
84 | func (c *CmdDisconnect) Init() {
85 | c.Initialize("disconnect", "Disconnect active VPN connection (if connected)")
86 | }
87 | func (c *CmdDisconnect) Run() error {
88 | if err := _proto.DisconnectVPN(); err != nil {
89 | return err
90 | }
91 |
92 | showState()
93 |
94 | return nil
95 | }
96 |
97 | //-----------------------------------------------
98 |
99 | type CmdConnect struct {
100 | flags.CmdInfo
101 | last bool
102 | gateway string
103 | port string
104 | any bool
105 | obfsproxy bool
106 | firewallOff bool
107 | dns string
108 | antitracker bool
109 | antitrackerHard bool
110 |
111 | filter_proto string
112 | filter_location bool
113 | filter_city bool
114 | filter_country bool
115 | filter_countryCode bool
116 | filter_invert bool
117 |
118 | multihopExitSvr string
119 |
120 | fastest bool
121 | }
122 |
123 | func (c *CmdConnect) Init() {
124 | c.Initialize("connect", "Establish new VPN connection\nLOCATION can be a mask for filtering servers (see 'servers' command)")
125 | c.DefaultStringVar(&c.gateway, "LOCATION")
126 |
127 | c.StringVar(&c.port, "port", "", "PROTOCOL:PORT", fmt.Sprintf("Port to connect to (default: %s - OpenVPN, %s - WireGuard)\nOpenVPN: %s\nWireGuard: %s",
128 | portsOpenVpn[0].String(), portsWireGuard[0].String(),
129 | allPortsString(portsOpenVpn[:]), allPortsString(portsWireGuard[:])))
130 |
131 | c.BoolVar(&c.any, "any", false, "When LOCATION points to more than one server, use first found server to connect")
132 |
133 | c.BoolVar(&c.obfsproxy, "o", false, "OpenVPN only: Use obfsproxy")
134 | c.BoolVar(&c.obfsproxy, "obfsproxy", false, "OpenVPN only: Use obfsproxy")
135 |
136 | c.StringVar(&c.multihopExitSvr, "exit_svr", "", "LOCATION", "OpenVPN only: Exit-server for Multi-Hop connection\n(use full serverID as a parameter, servers filtering not applicable for it)")
137 |
138 | c.BoolVar(&c.firewallOff, "fw_off", false, "Do not enable firewall for this connection\n(has effect only if Firewall not enabled before)")
139 |
140 | c.StringVar(&c.dns, "dns", "", "DNS_IP", "Use custom DNS for this connection\n(if 'antitracker' is enabled - this parameter will be ignored)")
141 |
142 | c.BoolVar(&c.antitracker, "antitracker", false, "Enable AntiTracker for this connection")
143 | c.BoolVar(&c.antitrackerHard, "antitracker_hard", false, "Enable 'Hard Core' AntiTracker for this connection")
144 |
145 | // filters
146 | c.StringVar(&c.filter_proto, "p", "", "PROTOCOL", "Protocol type OpenVPN|ovpn|WireGuard|wg")
147 | c.StringVar(&c.filter_proto, "protocol", "", "PROTOCOL", "Protocol type OpenVPN|ovpn|WireGuard|wg")
148 |
149 | c.BoolVar(&c.filter_location, "l", false, "Apply LOCATION as a filter to server location (Hostname)")
150 | c.BoolVar(&c.filter_location, "location", false, "Apply LOCATION as a filter to server location (Hostname)")
151 |
152 | c.BoolVar(&c.filter_country, "c", false, "Apply LOCATION as a filter to country name")
153 | c.BoolVar(&c.filter_country, "country", false, "Apply LOCATION as a filter to country name")
154 |
155 | c.BoolVar(&c.filter_countryCode, "cc", false, "Apply LOCATION as a filter to country code")
156 | c.BoolVar(&c.filter_countryCode, "country_code", false, "Apply LOCATION as a filter to country code")
157 |
158 | c.BoolVar(&c.filter_city, "city", false, "Apply LOCATION as a filter to city name")
159 |
160 | c.BoolVar(&c.filter_invert, "filter_invert", false, "Invert filtering")
161 |
162 | c.BoolVar(&c.fastest, "fastest", false, "Connect to fastest server")
163 |
164 | c.BoolVar(&c.last, "last", false, "Connect with last successful connection parameters")
165 | }
166 |
167 | // Run executes command
168 | func (c *CmdConnect) Run() (retError error) {
169 | if len(c.gateway) == 0 && c.fastest == false && c.last == false {
170 | return flags.BadParameter{}
171 | }
172 |
173 | // show current state after on finished
174 | defer func() {
175 | if retError == nil {
176 | showState()
177 | }
178 | }()
179 |
180 | // connection request
181 | req := types.Connect{}
182 |
183 | // get servers list from daemon
184 | serverFound := false
185 | servers, err := _proto.GetServers()
186 | if err != nil {
187 | return err
188 | }
189 |
190 | // check is logged-in
191 | helloResp := _proto.GetHelloResponse()
192 | if len(helloResp.Command) > 0 && (len(helloResp.Session.Session) == 0) {
193 | // We received 'hello' response but no session info - print tips to login
194 | fmt.Println("Error: Not logged in")
195 |
196 | fmt.Println()
197 | PrintTips([]TipType{TipLogin})
198 | fmt.Println()
199 |
200 | return service.ErrorNotLoggedIn{}
201 | }
202 |
203 | // requesting servers list
204 | svrs := serversList(servers)
205 |
206 | // check which VPN protocols can be used
207 | isWgDisabled := len(helloResp.DisabledFunctions.WireGuardError) > 0
208 | isOpenVPNDisabled := len(helloResp.DisabledFunctions.OpenVPNError) > 0
209 | funcWarnDisabledProtocols := func() {
210 | if isOpenVPNDisabled {
211 | fmt.Println("WARNING: OpenVPN functionality disabled:\n\t", helloResp.DisabledFunctions.OpenVPNError)
212 | }
213 | if isWgDisabled {
214 | fmt.Println("WARNING: WireGuard functionality disabled:\n\t", helloResp.DisabledFunctions.WireGuardError)
215 | }
216 | }
217 |
218 | // do we need to connect with last successful connection parameters
219 | if c.last {
220 | fmt.Println("Enabled '-last' parameter. Using parameters from last successful connection")
221 | ci := config.RestoreLastConnectionInfo()
222 | if ci == nil {
223 | return fmt.Errorf("no information about last connection")
224 | }
225 |
226 | // reset filters
227 | c.filter_proto = ""
228 | c.filter_location = false
229 | c.filter_city = false
230 | c.filter_countryCode = false
231 | c.filter_country = false
232 | c.filter_invert = false
233 |
234 | // load last connection parameters
235 | c.gateway = ci.Gateway
236 | c.port = ci.Port
237 | c.obfsproxy = ci.Obfsproxy
238 | c.firewallOff = ci.FirewallOff
239 | c.dns = ci.DNS
240 | c.antitracker = ci.Antitracker
241 | c.antitrackerHard = ci.AntitrackerHard
242 | c.multihopExitSvr = ci.MultiopExitSvr
243 | }
244 |
245 | if c.obfsproxy && len(helloResp.DisabledFunctions.ObfsproxyError) > 0 {
246 | return fmt.Errorf(helloResp.DisabledFunctions.ObfsproxyError)
247 | }
248 |
249 | // MULTI\SINGLE -HOP
250 | if len(c.multihopExitSvr) > 0 {
251 | if isOpenVPNDisabled {
252 | return fmt.Errorf(helloResp.DisabledFunctions.OpenVPNError)
253 | }
254 | // MULTI-HOP
255 | if c.fastest {
256 | return flags.BadParameter{Message: "'fastest' flag is not applicable for Multi-Hop connection [exit_svr]"}
257 | }
258 |
259 | if len(c.filter_proto) > 0 {
260 | pType, err := getVpnTypeByFlag(c.filter_proto)
261 | if err != nil || pType != vpn.OpenVPN {
262 | return flags.BadParameter{Message: "protocol flag [fp] is not applicable for Multi-Hop connection [exit_svr], only OpenVPN connection allowed"}
263 | }
264 | }
265 |
266 | if c.filter_location || c.filter_city || c.filter_countryCode || c.filter_country || c.filter_invert {
267 | fmt.Println("WARNING: filtering flags are ignored for Multi-Hop connection [exit_svr]")
268 | }
269 |
270 | entrySvrs := serversFilter(isWgDisabled, isOpenVPNDisabled, svrs, c.gateway, ProtoName_OpenVPN, false, false, false, false, false)
271 | if len(entrySvrs) == 0 || len(entrySvrs) > 1 {
272 | return flags.BadParameter{Message: "specify correct entry server ID for multi-hop connection"}
273 | }
274 |
275 | exitSvrs := serversFilter(isWgDisabled, isOpenVPNDisabled, svrs, c.multihopExitSvr, ProtoName_OpenVPN, false, false, false, false, false)
276 | if len(exitSvrs) == 0 || len(exitSvrs) > 1 {
277 | return flags.BadParameter{Message: "specify correct exit server ID for multi-hop connection"}
278 | }
279 | entrySvr := entrySvrs[0]
280 | exitSvr := exitSvrs[0]
281 |
282 | if entrySvr.gateway == exitSvr.gateway || entrySvr.countryCode == exitSvr.countryCode {
283 | return flags.BadParameter{Message: "unable to use entry- and exit- servers from the same country for multi-hop connection"}
284 | }
285 |
286 | c.gateway = entrySvr.gateway
287 | c.multihopExitSvr = exitSvr.gateway
288 | } else {
289 | //SINGLE-HOP
290 | svrs = serversFilter(isWgDisabled, isOpenVPNDisabled, svrs, c.gateway, c.filter_proto, c.filter_location, c.filter_city, c.filter_countryCode, c.filter_country, c.filter_invert)
291 |
292 | srvID := ""
293 |
294 | // Fastest server
295 | if c.fastest && len(svrs) > 1 {
296 | if err := serversPing(svrs, true); err != nil && c.any == false {
297 | if c.any {
298 | fmt.Printf("Error: Failed to ping servers to determine fastest: %s\n", err)
299 | } else {
300 | return err
301 | }
302 | }
303 | fastestSrv := svrs[len(svrs)-1]
304 | if fastestSrv.pingMs == 0 {
305 | fmt.Println("WARNING! Servers pinging problem.")
306 | }
307 | srvID = fastestSrv.gateway
308 | }
309 |
310 | // if we not found required server before (by 'fastest' option)
311 | if len(srvID) == 0 {
312 | showTipsServerFilterError := func() {
313 | fmt.Println()
314 |
315 | tips := []TipType{TipServers, TipConnectHelp}
316 | if config.LastConnectionExist() {
317 | tips = append(tips, TipLastConnection)
318 | }
319 | PrintTips(tips)
320 | }
321 |
322 | // no servers found
323 | if len(svrs) == 0 {
324 | fmt.Println("No servers found by your filter")
325 | fmt.Println("Please specify server more correctly")
326 |
327 | funcWarnDisabledProtocols() // print info about disabled functionality
328 | showTipsServerFilterError()
329 | return fmt.Errorf("no servers found by your filter")
330 | }
331 |
332 | // 'any' option
333 | if len(svrs) > 1 {
334 | fmt.Println("More than one server found")
335 |
336 | if c.any == false {
337 | fmt.Println("Please specify server more correctly or use flag '-any'")
338 | showTipsServerFilterError()
339 | return fmt.Errorf("more than one server found")
340 | }
341 | fmt.Printf("Taking first found server\n")
342 | }
343 | srvID = svrs[0].gateway
344 | }
345 | c.gateway = srvID
346 | }
347 |
348 | // Firewall for current connection
349 | req.FirewallOnDuringConnection = true
350 | if c.firewallOff {
351 | // check current FW state
352 | state, err := _proto.FirewallStatus()
353 | if err != nil {
354 | return fmt.Errorf("unable to check Firewall state: %w", err)
355 | }
356 | if state.IsEnabled == false {
357 | req.FirewallOnDuringConnection = false
358 | } else {
359 | fmt.Println("WARNING! Firewall option ignored (Firewall already enabled manually)")
360 | }
361 | }
362 | // get configuration
363 | cfg, _ := config.GetConfig()
364 |
365 | // set antitracker DNS (if defined). It will overwrite 'custom DNS' parameter
366 | if c.antitracker == false && c.antitrackerHard == false {
367 | // AntiTracker parameters not defined for current connection
368 | // Taking default configuration parameters (if defined)
369 | if cfg.Antitracker || cfg.AntitrackerHardcore {
370 | // print info
371 | printAntitrackerConfigInfo(nil, cfg.Antitracker, cfg.AntitrackerHardcore).Flush()
372 | // set values
373 | c.antitracker = cfg.Antitracker
374 | c.antitrackerHard = cfg.AntitrackerHardcore
375 | }
376 | }
377 | if c.antitracker || c.antitrackerHard {
378 | atDNS, err := GetAntitrackerIP(c.antitrackerHard, len(c.multihopExitSvr) > 0, &servers)
379 | if err != nil {
380 | return err
381 | }
382 | req.CurrentDNS = atDNS
383 |
384 | if len(c.dns) > 0 {
385 | fmt.Println("WARNING! Manual DNS configuration ignored due to AntiTracker")
386 | }
387 | }
388 |
389 | // set Manual DNS if defined (only in case if AntiTracker not defined)
390 | if len(req.CurrentDNS) == 0 {
391 | if len(c.dns) > 0 {
392 | dns := net.ParseIP(c.dns)
393 | if dns == nil {
394 | return flags.BadParameter{}
395 | }
396 | req.CurrentDNS = dns.String()
397 | } else if len(cfg.CustomDNS) > 0 {
398 | // using default DNS configuration
399 | printDNSConfigInfo(nil, cfg.CustomDNS).Flush()
400 | req.CurrentDNS = cfg.CustomDNS
401 | }
402 | }
403 |
404 | // looking for connection server
405 | // WireGuard
406 | for _, s := range servers.WireguardServers {
407 | if s.Gateway == c.gateway {
408 |
409 | serverFound = true
410 | host := s.Hosts[0]
411 | req.VpnType = vpn.WireGuard
412 | req.WireGuardParameters.EntryVpnServer.Hosts = []types.WGHost{{Host: host.Host, PublicKey: host.PublicKey, LocalIP: host.LocalIP}}
413 |
414 | // port
415 | p, err := getPort(vpn.WireGuard, c.port)
416 | if err != nil {
417 | return err
418 | }
419 | req.WireGuardParameters.Port.Port = p.port
420 |
421 | fmt.Printf("[WireGuard] Connecting to: %s, %s (%s) %s %s...\n", s.City, s.CountryCode, s.Country, s.Gateway, p.String())
422 |
423 | break
424 | }
425 | }
426 | // OpenVPN
427 | if serverFound == false {
428 | var entrySvr *apitypes.OpenvpnServerInfo = nil
429 | var exitSvr *apitypes.OpenvpnServerInfo = nil
430 |
431 | // exit server
432 | if len(c.multihopExitSvr) > 0 {
433 | for _, s := range servers.OpenvpnServers {
434 | if s.Gateway == c.multihopExitSvr {
435 | exitSvr = &s
436 | break
437 | }
438 | }
439 | }
440 |
441 | var destPort port
442 | // entry server
443 | for _, s := range servers.OpenvpnServers {
444 | if s.Gateway == c.gateway {
445 | entrySvr = &s
446 | // TODO: obfsproxy configuration for this connection must be sent in 'Connect' request (avoid using daemon preferences)
447 | if err = _proto.SetPreferences("enable_obfsproxy", fmt.Sprint(c.obfsproxy)); err != nil {
448 | return err
449 | }
450 |
451 | serverFound = true
452 | req.VpnType = vpn.OpenVPN
453 | req.OpenVpnParameters.EntryVpnServer.IPAddresses = s.IPAddresses
454 |
455 | // port
456 | destPort, err = getPort(vpn.OpenVPN, c.port)
457 | if err != nil {
458 | return err
459 | }
460 | req.OpenVpnParameters.Port.Port = destPort.port
461 | req.OpenVpnParameters.Port.Protocol = destPort.IsTCP()
462 |
463 | if len(c.multihopExitSvr) > 0 {
464 | // get Multi-Hop ID
465 | req.OpenVpnParameters.MultihopExitSrvID = strings.Split(c.multihopExitSvr, ".")[0]
466 | }
467 | break
468 | }
469 | if len(c.multihopExitSvr) == 0 {
470 | if entrySvr != nil {
471 | break
472 | }
473 | if entrySvr != nil && exitSvr != nil {
474 | break
475 | }
476 | }
477 | }
478 |
479 | if entrySvr == nil {
480 | return fmt.Errorf("serverID not found in servers list (%s)", c.gateway)
481 | }
482 | if len(c.multihopExitSvr) > 0 && exitSvr == nil {
483 | return fmt.Errorf("serverID not found in servers list (%s)", c.multihopExitSvr)
484 | }
485 |
486 | if len(c.multihopExitSvr) == 0 {
487 | fmt.Printf("[OpenVPN] Connecting to: %s, %s (%s) %s %s...\n", entrySvr.City, entrySvr.CountryCode, entrySvr.Country, entrySvr.Gateway, destPort.String())
488 | } else {
489 | fmt.Printf("[OpenVPN] Connecting Multi-Hop...\n")
490 | fmt.Printf("\tentry server: %s, %s (%s) %s %s\n", entrySvr.City, entrySvr.CountryCode, entrySvr.Country, entrySvr.Gateway, destPort.String())
491 | fmt.Printf("\texit server : %s, %s (%s) %s\n", exitSvr.City, exitSvr.CountryCode, exitSvr.Country, exitSvr.Gateway)
492 | }
493 | }
494 |
495 | if serverFound == false {
496 | return fmt.Errorf("serverID not found in servers list (%s)", c.gateway)
497 | }
498 |
499 | fmt.Println("Connecting...")
500 | _, err = _proto.ConnectVPN(req)
501 | if err != nil {
502 | err = fmt.Errorf("failed to connect: %w", err)
503 | fmt.Printf("Disconnecting...\n")
504 | if err2 := _proto.DisconnectVPN(); err2 != nil {
505 | fmt.Printf("Failed to disconnect: %v\n", err2)
506 | }
507 | return err
508 | }
509 |
510 | // save last connection parameters
511 | config.SaveLastConnectionInfo(config.LastConnectionInfo{
512 | Gateway: c.gateway,
513 | Port: c.port,
514 | Obfsproxy: c.obfsproxy,
515 | FirewallOff: c.firewallOff,
516 | DNS: c.dns,
517 | Antitracker: c.antitracker,
518 | AntitrackerHard: c.antitrackerHard,
519 | MultiopExitSvr: c.multihopExitSvr})
520 |
521 | return nil
522 | }
523 |
524 | func getPort(vpnType vpn.Type, portInfo string) (port, error) {
525 | var err error
526 | var portPtr *int
527 | var isTCPPtr *bool
528 | if len(portInfo) > 0 {
529 | portPtr, isTCPPtr, err = parsePort(portInfo)
530 | if err != nil {
531 | return port{}, err
532 | }
533 | }
534 |
535 | var retPort port
536 | if vpnType == vpn.WireGuard {
537 | if isTCPPtr != nil && *isTCPPtr {
538 | return port{}, flags.BadParameter{Message: "port"}
539 | }
540 | retPort = portsWireGuard[0] // default port
541 | } else {
542 | retPort = portsOpenVpn[0] // default port
543 | }
544 |
545 | if portPtr != nil {
546 | retPort.port = *portPtr
547 | }
548 | if isTCPPtr != nil {
549 | retPort.tcp = *isTCPPtr
550 | }
551 |
552 | // ckeck is port allowed
553 | if vpnType == vpn.WireGuard {
554 | if isPortAllowed(portsWireGuard[:], retPort) == false {
555 | fmt.Printf("WARNING: using non-standard port '%s' (allowed ports: %s)\n", retPort.String(), allPortsString(portsWireGuard[:]))
556 | }
557 | } else {
558 | if isPortAllowed(portsOpenVpn[:], retPort) == false {
559 | fmt.Printf("WARNING: using non-standard port '%s' (allowed ports: %s)\n", retPort.String(), allPortsString(portsOpenVpn[:]))
560 | }
561 | }
562 |
563 | return retPort, nil
564 | }
565 |
566 | func isPortAllowed(ports []port, thePort port) bool {
567 | for _, p := range ports {
568 | if p.port == thePort.port && p.tcp == thePort.tcp {
569 | return true
570 | }
571 | }
572 | return false
573 | }
574 |
575 | func allPortsString(ports []port) string {
576 | s := make([]string, 0, len(ports))
577 | for _, p := range ports {
578 | s = append(s, p.String())
579 | }
580 | return strings.Join(s, ", ")
581 | }
582 |
583 | // parsing port info from string in format "PROTOCOL:PORT"
584 | func parsePort(portInfo string) (pPort *int, pIsTCP *bool, err error) {
585 |
586 | var port int
587 | var isTCP bool
588 |
589 | if len(portInfo) == 0 {
590 | return nil, nil, nil
591 | }
592 |
593 | portInfo = strings.ToLower(portInfo)
594 |
595 | fields := strings.Split(portInfo, ":")
596 | if len(fields) > 2 {
597 | return nil, nil, flags.BadParameter{Message: "port"}
598 | }
599 |
600 | protoStr := ""
601 | portStr := ""
602 | if len(fields) == 2 {
603 | protoStr = fields[0]
604 | portStr = fields[1]
605 | } else {
606 | if _, err := strconv.Atoi(fields[0]); err != nil {
607 | protoStr = fields[0]
608 | } else {
609 | portStr = fields[0]
610 | }
611 | }
612 |
613 | if len(protoStr) > 0 {
614 | if protoStr == "tcp" {
615 | isTCP = true
616 | } else if protoStr == "udp" {
617 | isTCP = false
618 | } else {
619 | return nil, nil, flags.BadParameter{Message: "port"}
620 | }
621 | }
622 |
623 | if len(portStr) > 0 {
624 | p, err := strconv.Atoi(portStr)
625 | if err != nil {
626 | return nil, nil, flags.BadParameter{Message: "port"}
627 | }
628 | port = p
629 | }
630 |
631 | return &port, &isTCP, nil
632 | }
633 |
--------------------------------------------------------------------------------
/commands/dns.go:
--------------------------------------------------------------------------------
1 | //
2 | // IVPN command line interface (CLI)
3 | // https://github.com/ivpn/desktop-app-cli
4 | //
5 | // Created by Stelnykovych Alexandr.
6 | // Copyright (c) 2020 Privatus Limited.
7 | //
8 | // This file is part of the IVPN command line interface.
9 | //
10 | // The IVPN command line interface is free software: you can redistribute it and/or
11 | // modify it under the terms of the GNU General Public License as published by the Free
12 | // Software Foundation, either version 3 of the License, or (at your option) any later version.
13 | //
14 | // The IVPN command line interface is distributed in the hope that it will be useful,
15 | // but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16 | // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
17 | // details.
18 | //
19 | // You should have received a copy of the GNU General Public License
20 | // along with the IVPN command line interface. If not, see .
21 | //
22 |
23 | package commands
24 |
25 | import (
26 | "fmt"
27 | "os"
28 | "strings"
29 | "text/tabwriter"
30 |
31 | "github.com/ivpn/desktop-app-cli/commands/config"
32 | "github.com/ivpn/desktop-app-cli/flags"
33 | apitypes "github.com/ivpn/desktop-app-daemon/api/types"
34 | "github.com/ivpn/desktop-app-daemon/vpn"
35 | )
36 |
37 | type CmdDns struct {
38 | flags.CmdInfo
39 | reset bool
40 | dns string
41 | }
42 |
43 | func (c *CmdDns) Init() {
44 | c.Initialize("dns", "Default 'custom DNS' management for VPN connection\nDNS_IP - optional parameter used to set custom dns value (ignored when AntiTracker enabled)")
45 | c.DefaultStringVar(&c.dns, "DNS_IP")
46 | c.BoolVar(&c.reset, "off", false, "Reset DNS server to a default")
47 | }
48 |
49 | func (c *CmdDns) Run() error {
50 | if c.reset && len(c.dns) > 0 {
51 | return flags.BadParameter{}
52 | }
53 |
54 | cfg, err := config.GetConfig()
55 | if err != nil {
56 | return err
57 | }
58 |
59 | var servers *apitypes.ServersInfoResponse
60 | // do we have to change custom DNS configuration ?
61 | if c.reset || len(c.dns) > 0 {
62 | cfg.CustomDNS = ""
63 | if len(c.dns) > 0 {
64 | cfg.CustomDNS = c.dns
65 | }
66 |
67 | err = config.SaveConfig(cfg)
68 | if err != nil {
69 | return err
70 | }
71 |
72 | // update DNS if VPN is connected
73 | state, connectedInfo, err := _proto.GetVPNState()
74 | if err != nil {
75 | return err
76 | }
77 | if state == vpn.CONNECTED {
78 | svrs, _ := _proto.GetServers()
79 | servers = &svrs
80 | isAntitracker, isAtHardcore := IsAntiTrackerIP(connectedInfo.ManualDNS, servers)
81 | if c.reset && (isAntitracker || isAtHardcore) {
82 | fmt.Println("Nothing to disable")
83 | } else {
84 | if err := _proto.SetManualDNS(cfg.CustomDNS); err != nil {
85 | return err
86 | }
87 | fmt.Println("Custom DNS successfully changed for current VPN connection")
88 | }
89 | }
90 | }
91 |
92 | // print state
93 | var w *tabwriter.Writer
94 |
95 | state, connected, err := _proto.GetVPNState()
96 | if err != nil {
97 | return err
98 | }
99 |
100 | if state == vpn.CONNECTED {
101 | if servers == nil {
102 | svrs, _ := _proto.GetServers()
103 | servers = &svrs
104 | }
105 | w = printDNSState(w, connected.ManualDNS, servers)
106 | }
107 |
108 | w = printDNSConfigInfo(w, cfg.CustomDNS)
109 | w.Flush()
110 |
111 | return nil
112 | }
113 |
114 | //----------------------------------------------------------------------------------------
115 |
116 | type CmdAntitracker struct {
117 | flags.CmdInfo
118 | def bool
119 | off bool
120 | hardcore bool
121 | }
122 |
123 | func (c *CmdAntitracker) Init() {
124 | c.Initialize("antitracker", "Default AntiTracker configuration management for VPN connection")
125 | c.BoolVar(&c.def, "on", false, "Enable AntiTracker")
126 | c.BoolVar(&c.hardcore, "on_hardcore", false, "Enable AntiTracker 'hardcore' mode")
127 | c.BoolVar(&c.off, "off", false, "Disable AntiTracker")
128 | }
129 |
130 | func (c *CmdAntitracker) Run() error {
131 | if c.NFlag() > 1 {
132 | return flags.BadParameter{Message: "Not allowed to use more than one argument for this command"}
133 | }
134 |
135 | cfg, err := config.GetConfig()
136 | if err != nil {
137 | return err
138 | }
139 |
140 | var servers apitypes.ServersInfoResponse
141 | var dns string
142 |
143 | servers, err = _proto.GetServers()
144 | if err != nil {
145 | return err
146 | }
147 |
148 | // do we have to change antitracker configuration ?
149 | if c.off || c.def || c.hardcore {
150 | cfg.Antitracker = false
151 | cfg.AntitrackerHardcore = false
152 |
153 | if c.hardcore {
154 | cfg.AntitrackerHardcore = true
155 | } else if c.def {
156 | cfg.Antitracker = true
157 | }
158 |
159 | err = config.SaveConfig(cfg)
160 | if err != nil {
161 | return err
162 | }
163 |
164 | // update DNS if VPN is connected
165 | state, connectInfo, err := _proto.GetVPNState()
166 | if err != nil {
167 | return err
168 | }
169 | if state == vpn.CONNECTED {
170 | isAntitracker, isAtHardcore := IsAntiTrackerIP(connectInfo.ManualDNS, &servers)
171 | if c.off && !(isAntitracker || isAtHardcore) {
172 | fmt.Println("AntiTracker already disabled")
173 | } else {
174 | if cfg.Antitracker || cfg.AntitrackerHardcore {
175 | dns, err = GetAntitrackerIP(cfg.AntitrackerHardcore, len(connectInfo.ExitServerID) > 0, &servers)
176 | }
177 | if err := _proto.SetManualDNS(dns); err != nil {
178 | return err
179 | }
180 | fmt.Println("AntiTracker successfully updated for current VPN connection")
181 | }
182 | }
183 | }
184 |
185 | // print state
186 | var w *tabwriter.Writer
187 |
188 | state, connected, err := _proto.GetVPNState()
189 | if err != nil {
190 | return err
191 | }
192 |
193 | if state == vpn.CONNECTED {
194 | servers, _ := _proto.GetServers()
195 | w = printDNSState(w, connected.ManualDNS, &servers)
196 | }
197 |
198 | w = printAntitrackerConfigInfo(w, cfg.Antitracker, cfg.AntitrackerHardcore)
199 | w.Flush()
200 |
201 | return nil
202 | }
203 |
204 | //----------------------------------------------------------------------------------------
205 |
206 | func printDNSConfigInfo(w *tabwriter.Writer, customDNS string) *tabwriter.Writer {
207 | if w == nil {
208 | w = tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
209 | }
210 |
211 | if len(customDNS) > 0 {
212 | fmt.Fprintln(w, fmt.Sprintf("Default config\t:\tCustom DNS %v", customDNS))
213 | } else {
214 | fmt.Fprintln(w, fmt.Sprintf("Default config\t:\tCustom DNS not defined"))
215 | }
216 |
217 | return w
218 | }
219 |
220 | func printAntitrackerConfigInfo(w *tabwriter.Writer, antitracker, antitrackerHardcore bool) *tabwriter.Writer {
221 | if w == nil {
222 | w = tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
223 | }
224 |
225 | if antitrackerHardcore {
226 | fmt.Fprintln(w, fmt.Sprintf("Default config\t:\tAntiTracker Enabled (Hardcore)"))
227 | } else if antitracker {
228 | fmt.Fprintln(w, fmt.Sprintf("Default config\t:\tAntiTracker Enabled"))
229 | } else {
230 | fmt.Fprintln(w, fmt.Sprintf("Default config\t:\tAntiTracker Disabled"))
231 | }
232 |
233 | return w
234 | }
235 |
236 | //----------------------------------------------------------------------------------------
237 |
238 | // GetAntitrackerIP - returns IP of antitracker DNS
239 | func GetAntitrackerIP(isHardcore, isMultihop bool, servers *apitypes.ServersInfoResponse) (string, error) {
240 |
241 | if isHardcore {
242 | if isMultihop {
243 | return servers.Config.Antitracker.Hardcore.MultihopIP, nil
244 | }
245 | return servers.Config.Antitracker.Hardcore.IP, nil
246 | }
247 |
248 | if isMultihop {
249 | return servers.Config.Antitracker.Default.MultihopIP, nil
250 | }
251 | return servers.Config.Antitracker.Default.IP, nil
252 | }
253 |
254 | // IsAntiTrackerIP returns info 'is this IP equals to antitracker IP'
255 | func IsAntiTrackerIP(dns string, servers *apitypes.ServersInfoResponse) (antitracker, antitrackerHardcore bool) {
256 | if servers == nil {
257 | return false, false
258 | }
259 |
260 | dns = strings.TrimSpace(dns)
261 | if len(dns) == 0 {
262 | return false, false
263 | }
264 |
265 | if dns == servers.Config.Antitracker.Default.IP || dns == servers.Config.Antitracker.Default.MultihopIP {
266 | return true, false
267 | } else if dns == servers.Config.Antitracker.Hardcore.IP || dns == servers.Config.Antitracker.Hardcore.MultihopIP {
268 | return true, true
269 | }
270 |
271 | return false, false
272 | }
273 |
--------------------------------------------------------------------------------
/commands/errors.go:
--------------------------------------------------------------------------------
1 | //
2 | // IVPN command line interface (CLI)
3 | // https://github.com/ivpn/desktop-app-cli
4 | //
5 | // Created by Stelnykovych Alexandr.
6 | // Copyright (c) 2020 Privatus Limited.
7 | //
8 | // This file is part of the IVPN command line interface.
9 | //
10 | // The IVPN command line interface is free software: you can redistribute it and/or
11 | // modify it under the terms of the GNU General Public License as published by the Free
12 | // Software Foundation, either version 3 of the License, or (at your option) any later version.
13 | //
14 | // The IVPN command line interface is distributed in the hope that it will be useful,
15 | // but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16 | // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
17 | // details.
18 | //
19 | // You should have received a copy of the GNU General Public License
20 | // along with the IVPN command line interface. If not, see .
21 | //
22 |
23 | package commands
24 |
25 | // NotImplemented error
26 | type NotImplemented struct {
27 | Message string
28 | }
29 |
30 | func (e NotImplemented) Error() string {
31 | if len(e.Message) == 0 {
32 | return "not implemented"
33 | }
34 | return e.Message
35 | }
36 |
--------------------------------------------------------------------------------
/commands/firewall.go:
--------------------------------------------------------------------------------
1 | //
2 | // IVPN command line interface (CLI)
3 | // https://github.com/ivpn/desktop-app-cli
4 | //
5 | // Created by Stelnykovych Alexandr.
6 | // Copyright (c) 2020 Privatus Limited.
7 | //
8 | // This file is part of the IVPN command line interface.
9 | //
10 | // The IVPN command line interface is free software: you can redistribute it and/or
11 | // modify it under the terms of the GNU General Public License as published by the Free
12 | // Software Foundation, either version 3 of the License, or (at your option) any later version.
13 | //
14 | // The IVPN command line interface is distributed in the hope that it will be useful,
15 | // but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16 | // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
17 | // details.
18 | //
19 | // You should have received a copy of the GNU General Public License
20 | // along with the IVPN command line interface. If not, see .
21 | //
22 |
23 | package commands
24 |
25 | import "github.com/ivpn/desktop-app-cli/flags"
26 |
27 | type CmdFirewall struct {
28 | flags.CmdInfo
29 | status bool
30 | on bool
31 | off bool
32 | allowLan bool
33 | blockLan bool
34 | //allowLanMulticast bool
35 | //blockLanMulticast bool
36 | }
37 |
38 | func (c *CmdFirewall) Init() {
39 | c.Initialize("firewall", "Firewall management")
40 | c.BoolVar(&c.status, "status", false, "(default) Show info about current firewall status")
41 | c.BoolVar(&c.off, "off", false, "Switch-off firewall")
42 | c.BoolVar(&c.on, "on", false, "Switch-on firewall")
43 | c.BoolVar(&c.allowLan, "lan_allow", false, "Set configuration: allow LAN communication (take effect when firewall enabled)")
44 | c.BoolVar(&c.blockLan, "lan_block", false, "Set configuration: block LAN communication (take effect when firewall enabled)")
45 | //c.BoolVar(&c.allowLanMulticast, "lan_multicast_allow", false, "Same as 'lan_allow' + allow multicast communication ")
46 | //c.BoolVar(&c.blockLanMulticast, "lan_multicast_block", false, "Same as 'lan_block' + block multicast communication")
47 | }
48 | func (c *CmdFirewall) Run() error {
49 | if c.on && c.off {
50 | return flags.BadParameter{}
51 | }
52 |
53 | if c.allowLan && c.blockLan {
54 | return flags.BadParameter{}
55 | }
56 |
57 | //if c.allowLanMulticast && c.blockLanMulticast {
58 | // return flags.BadParameter{}
59 | //}
60 |
61 | if c.allowLan {
62 | if err := _proto.FirewallAllowLan(true); err != nil {
63 | return err
64 | }
65 | } else if c.blockLan {
66 | if err := _proto.FirewallAllowLan(false); err != nil {
67 | return err
68 | }
69 | }
70 |
71 | //if c.allowLanMulticast {
72 | // if err := _proto.FirewallAllowLanMulticast(true); err != nil {
73 | // return err
74 | // }
75 | //} else if c.blockLanMulticast {
76 | // if err := _proto.FirewallAllowLanMulticast(false); err != nil {
77 | // return err
78 | // }
79 | //}
80 |
81 | if c.on {
82 | if err := _proto.FirewallSet(true); err != nil {
83 | return err
84 | }
85 | } else if c.off {
86 | if err := _proto.FirewallSet(false); err != nil {
87 | return err
88 | }
89 | }
90 |
91 | state, err := _proto.FirewallStatus()
92 | if err != nil {
93 | return err
94 | }
95 |
96 | w := printFirewallState(nil, state.IsEnabled, state.IsPersistent, state.IsAllowLAN, state.IsAllowMulticast)
97 | w.Flush()
98 |
99 | // TIPS
100 | tips := make([]TipType, 0, 2)
101 | if state.IsEnabled == false {
102 | tips = append(tips, TipFirewallEnable)
103 | } else {
104 | tips = append(tips, TipFirewallDisable)
105 | }
106 | PrintTips(tips)
107 | return nil
108 | }
109 |
--------------------------------------------------------------------------------
/commands/logs.go:
--------------------------------------------------------------------------------
1 | //
2 | // IVPN command line interface (CLI)
3 | // https://github.com/ivpn/desktop-app-cli
4 | //
5 | // Created by Stelnykovych Alexandr.
6 | // Copyright (c) 2020 Privatus Limited.
7 | //
8 | // This file is part of the IVPN command line interface.
9 | //
10 | // The IVPN command line interface is free software: you can redistribute it and/or
11 | // modify it under the terms of the GNU General Public License as published by the Free
12 | // Software Foundation, either version 3 of the License, or (at your option) any later version.
13 | //
14 | // The IVPN command line interface is distributed in the hope that it will be useful,
15 | // but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16 | // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
17 | // details.
18 | //
19 | // You should have received a copy of the GNU General Public License
20 | // along with the IVPN command line interface. If not, see .
21 | //
22 |
23 | package commands
24 |
25 | import (
26 | "fmt"
27 | "io"
28 | "os"
29 |
30 | "github.com/ivpn/desktop-app-cli/flags"
31 | "github.com/ivpn/desktop-app-daemon/service/platform"
32 | )
33 |
34 | type CmdLogs struct {
35 | flags.CmdInfo
36 | show bool
37 | enable bool
38 | disable bool
39 | }
40 |
41 | func (c *CmdLogs) Init() {
42 | c.Initialize("logs", "Logging management")
43 | c.BoolVar(&c.show, "show", false, "(default) Show logs")
44 | c.BoolVar(&c.enable, "on", false, "Enable logging")
45 | c.BoolVar(&c.disable, "off", false, "Disable logging")
46 | }
47 | func (c *CmdLogs) Run() error {
48 | if c.enable && c.disable {
49 | return flags.BadParameter{}
50 | }
51 |
52 | var err error
53 | if c.enable {
54 | err = c.setSetLogging(true)
55 | } else if c.disable {
56 | err = c.setSetLogging(false)
57 | }
58 |
59 | if err != nil || c.enable || c.disable {
60 | return err
61 | }
62 | return c.doShow()
63 | }
64 |
65 | func (c *CmdLogs) setSetLogging(enable bool) error {
66 | if enable {
67 | return _proto.SetPreferences("enable_logging", "true")
68 | }
69 | return _proto.SetPreferences("enable_logging", "false")
70 | }
71 |
72 | func (c *CmdLogs) doShow() error {
73 |
74 | isPartOfFile := false
75 | isSomethingPrinted := false
76 |
77 | fname := platform.LogFile()
78 | file, err := os.Open(fname)
79 | if err != nil {
80 | return err
81 | }
82 | defer func() {
83 | file.Close()
84 | if isSomethingPrinted {
85 | fmt.Println("##############")
86 | }
87 | if isPartOfFile {
88 | fmt.Println("Printed the last part of the log.")
89 | }
90 | fmt.Println("Log file:", fname)
91 | }()
92 |
93 | stat, err := os.Stat(fname)
94 | size := stat.Size()
95 |
96 | maxBytesToRead := int64(60 * 50)
97 | if size > maxBytesToRead {
98 | isPartOfFile = true
99 | if _, err := file.Seek(-maxBytesToRead, io.SeekEnd); err != nil {
100 | return err
101 | }
102 | }
103 |
104 | buff := make([]byte, maxBytesToRead)
105 | if _, err := file.Read(buff); err != nil {
106 | return err
107 | }
108 |
109 | fmt.Println(string(buff))
110 | isSomethingPrinted = true
111 |
112 | if isPartOfFile {
113 | fmt.Println("##############")
114 | fmt.Println("To view full log, please refer to file:", fname)
115 | }
116 |
117 | return nil
118 | }
119 |
--------------------------------------------------------------------------------
/commands/servers.go:
--------------------------------------------------------------------------------
1 | //
2 | // IVPN command line interface (CLI)
3 | // https://github.com/ivpn/desktop-app-cli
4 | //
5 | // Created by Stelnykovych Alexandr.
6 | // Copyright (c) 2020 Privatus Limited.
7 | //
8 | // This file is part of the IVPN command line interface.
9 | //
10 | // The IVPN command line interface is free software: you can redistribute it and/or
11 | // modify it under the terms of the GNU General Public License as published by the Free
12 | // Software Foundation, either version 3 of the License, or (at your option) any later version.
13 | //
14 | // The IVPN command line interface is distributed in the hope that it will be useful,
15 | // but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16 | // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
17 | // details.
18 | //
19 | // You should have received a copy of the GNU General Public License
20 | // along with the IVPN command line interface. If not, see .
21 | //
22 |
23 | package commands
24 |
25 | import (
26 | "fmt"
27 | "os"
28 | "sort"
29 | "strings"
30 | "text/tabwriter"
31 |
32 | apitypes "github.com/ivpn/desktop-app-daemon/api/types"
33 | "github.com/ivpn/desktop-app-daemon/vpn"
34 |
35 | "github.com/ivpn/desktop-app-cli/flags"
36 | )
37 |
38 | const (
39 | ProtoName_OpenVPN = "OpenVPN"
40 | ProtoName_WireGuard = "WireGuard"
41 | )
42 |
43 | type CmdServers struct {
44 | flags.CmdInfo
45 | proto string
46 | location bool
47 | city bool
48 | country bool
49 | countryCode bool
50 | filter string
51 | ping bool
52 | filterInvert bool
53 | }
54 |
55 | func (c *CmdServers) Init() {
56 | c.Initialize("servers", "Show servers list\n(FILTER - optional parameter: show only servers which contains FILTER in server description)")
57 | c.DefaultStringVar(&c.filter, "FILTER")
58 |
59 | c.StringVar(&c.proto, "p", "", "PROTOCOL", "Protocol type OpenVPN|ovpn|WireGuard|wg")
60 | c.StringVar(&c.proto, "protocol", "", "PROTOCOL", "Protocol type OpenVPN|ovpn|WireGuard|wg")
61 |
62 | c.BoolVar(&c.location, "l", false, "Apply FILTER to server location (Hostname)")
63 | c.BoolVar(&c.location, "location", false, "Apply FILTER to server location (Hostname)")
64 |
65 | c.BoolVar(&c.country, "c", false, "Apply FILTER to country name")
66 | c.BoolVar(&c.country, "country", false, "Apply FILTER to country name")
67 |
68 | c.BoolVar(&c.countryCode, "cc", false, "Apply FILTER to country code")
69 | c.BoolVar(&c.countryCode, "country_code", false, "Apply FILTER to country code")
70 |
71 | c.BoolVar(&c.city, "city", false, "Apply FILTER to city name")
72 |
73 | c.BoolVar(&c.ping, "ping", false, "Ping servers and view ping result")
74 | c.BoolVar(&c.filterInvert, "filter_invert", false, "Invert filtering result")
75 | }
76 | func (c *CmdServers) Run() error {
77 | servers, err := _proto.GetServers()
78 | if err != nil {
79 | return err
80 | }
81 |
82 | slist := serversList(servers)
83 |
84 | if c.ping {
85 | if err := serversPing(slist, true); err != nil {
86 | return err
87 | }
88 | }
89 |
90 | w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', tabwriter.AlignRight|tabwriter.Debug)
91 |
92 | if c.ping {
93 | fmt.Fprintln(w, "PROTOCOL\tLOCATION\tCITY\tCOUNTRY\tPING\t")
94 | } else {
95 | fmt.Fprintln(w, "PROTOCOL\tLOCATION\tCITY\tCOUNTRY\t")
96 | }
97 |
98 | helloResp := _proto.GetHelloResponse()
99 | isWgDisabled := len(helloResp.DisabledFunctions.WireGuardError) > 0
100 | isOpenVPNDisabled := len(helloResp.DisabledFunctions.OpenVPNError) > 0
101 |
102 | svrs := serversFilter(isWgDisabled, isOpenVPNDisabled,
103 | slist, c.filter, c.proto, c.location, c.city, c.countryCode, c.country, c.filterInvert)
104 | for _, s := range svrs {
105 | str := ""
106 | if c.ping {
107 | pingStr := " ? "
108 | if s.pingMs > 0 {
109 | pingStr = fmt.Sprintf("%dms", s.pingMs)
110 | }
111 | str = fmt.Sprintf("%s\t%s\t%s (%s)\t %s\t%s\t", s.protocol, s.gateway, s.city, s.countryCode, s.country, pingStr)
112 | } else {
113 | str = fmt.Sprintf("%s\t%s\t%s (%s)\t %s\t", s.protocol, s.gateway, s.city, s.countryCode, s.country)
114 | }
115 | fmt.Fprintln(w, str)
116 | }
117 |
118 | w.Flush()
119 |
120 | if isOpenVPNDisabled {
121 | fmt.Println("WARNING: OpenVPN servers were not shown because OpenVPN functionality disabled:\n\t", helloResp.DisabledFunctions.OpenVPNError)
122 | }
123 | if isWgDisabled {
124 | fmt.Println("WARNING: WireGuard servers were not shown because WireGuard functionality disabled:\n\t", helloResp.DisabledFunctions.WireGuardError)
125 | }
126 |
127 | return nil
128 | }
129 |
130 | // ---------------------
131 |
132 | func getVpnTypeByFlag(proto string) (t vpn.Type, err error) {
133 | proto = strings.ToLower(proto)
134 |
135 | if len(proto) == 0 {
136 | return t, fmt.Errorf("parameter is empty")
137 | }
138 |
139 | if proto == "wg" || proto == strings.ToLower(ProtoName_WireGuard) {
140 | return vpn.WireGuard, nil
141 | }
142 |
143 | if proto == "ovpn" || proto == strings.ToLower(ProtoName_OpenVPN) {
144 | return vpn.OpenVPN, nil
145 | }
146 |
147 | return t, flags.BadParameter{Message: "protocol definition not correct"}
148 | }
149 |
150 | func serversList(servers apitypes.ServersInfoResponse) []serverDesc {
151 | svrs := serversListByVpnType(servers, vpn.WireGuard)
152 | svrs = append(svrs, serversListByVpnType(servers, vpn.OpenVPN)...)
153 | return svrs
154 | }
155 |
156 | func serversListByVpnType(servers apitypes.ServersInfoResponse, t vpn.Type) []serverDesc {
157 |
158 | var ret []serverDesc
159 | if t == vpn.WireGuard {
160 | ret = make([]serverDesc, 0, len(servers.WireguardServers))
161 |
162 | for _, s := range servers.WireguardServers {
163 | hosts := make(map[string]struct{}, len(s.Hosts))
164 | for _, h := range s.Hosts {
165 | hosts[strings.ToLower(strings.TrimSpace(h.Host))] = struct{}{}
166 | }
167 | ret = append(ret, serverDesc{protocol: ProtoName_WireGuard, gateway: s.Gateway, city: s.City, countryCode: s.CountryCode, country: s.Country, hosts: hosts})
168 | }
169 | } else {
170 | ret = make([]serverDesc, 0, len(servers.OpenvpnServers))
171 |
172 | for _, s := range servers.OpenvpnServers {
173 | hosts := make(map[string]struct{}, len(s.IPAddresses))
174 | for _, h := range s.IPAddresses {
175 | hosts[strings.ToLower(strings.TrimSpace(h))] = struct{}{}
176 | }
177 | ret = append(ret, serverDesc{protocol: ProtoName_OpenVPN, gateway: s.Gateway, city: s.City, countryCode: s.CountryCode, country: s.Country, hosts: hosts})
178 | }
179 | }
180 | return ret
181 | }
182 |
183 | func serversFilter(isWgDisabled bool, isOvpnDisabled bool, servers []serverDesc, mask string, proto string, useGw, useCity, useCCode, useCountry, invertFilter bool) []serverDesc {
184 | if isWgDisabled || isOvpnDisabled {
185 | oldSvrs := servers
186 | servers = make([]serverDesc, 0, len(oldSvrs))
187 | for _, s := range oldSvrs {
188 | if isWgDisabled && s.protocol == ProtoName_WireGuard {
189 | continue
190 | }
191 | if isOvpnDisabled && s.protocol == ProtoName_OpenVPN {
192 | continue
193 | }
194 | servers = append(servers, s)
195 | }
196 | }
197 |
198 | if len(mask) == 0 && len(proto) == 0 {
199 | return servers
200 | }
201 | mask = strings.ToLower(mask)
202 | checkAll := !(useGw || useCity || useCCode || useCountry)
203 |
204 | ret := make([]serverDesc, 0, len(servers))
205 | for _, s := range servers {
206 | isOK := false
207 |
208 | if len(proto) > 0 {
209 | sProto, err1 := getVpnTypeByFlag(s.protocol)
210 | fProto, err2 := getVpnTypeByFlag(proto)
211 | if sProto != fProto || err1 != nil || err2 != nil {
212 | continue
213 | }
214 | }
215 |
216 | if len(mask) == 0 {
217 | isOK = true
218 | }
219 |
220 | if (checkAll || useGw) && strings.ToLower(s.gateway) == mask {
221 | isOK = true
222 | }
223 | if (checkAll || useCity) && strings.Contains(strings.ToLower(s.city), mask) {
224 | isOK = true
225 | }
226 | if (checkAll || useCCode) && strings.ToLower(s.countryCode) == mask {
227 | isOK = true
228 | }
229 | if (checkAll || useCountry) && strings.Contains(strings.ToLower(s.country), mask) {
230 | isOK = true
231 | }
232 |
233 | if invertFilter {
234 | isOK = !isOK
235 | }
236 | if isOK {
237 | ret = append(ret, s)
238 | }
239 | }
240 | return ret
241 | }
242 |
243 | func serversPing(servers []serverDesc, needSort bool) error {
244 | fmt.Println("Pinging servers ...")
245 | pingRes, err := _proto.PingServers()
246 | if err != nil {
247 | return err
248 | }
249 | if len(pingRes) == 0 {
250 | return fmt.Errorf("failed to ping servers")
251 | }
252 |
253 | for _, pr := range pingRes {
254 | for i, s := range servers {
255 | if _, ok := s.hosts[pr.Host]; ok {
256 | s.pingMs = pr.Ping
257 | servers[i] = s
258 | }
259 | }
260 | }
261 |
262 | if needSort {
263 | sort.Slice(servers, func(i, j int) bool {
264 | if servers[i].pingMs == 0 && servers[j].pingMs == 0 {
265 | return strings.Compare(servers[i].city, servers[j].city) < 0
266 | } else if servers[i].pingMs <= 0 {
267 | return true
268 | } else if servers[j].pingMs <= 0 {
269 | return false
270 | }
271 |
272 | return servers[i].pingMs > servers[j].pingMs
273 | })
274 | }
275 |
276 | return nil
277 | }
278 |
279 | type serverDesc struct {
280 | protocol string
281 | gateway string
282 | city string
283 | countryCode string
284 | country string
285 | hosts map[string]struct{}
286 | pingMs int
287 | }
288 |
289 | func (s *serverDesc) String() string {
290 | return fmt.Sprintf("%s, %s (%s), %s", s.gateway, s.city, s.countryCode, s.country)
291 | }
292 |
--------------------------------------------------------------------------------
/commands/state.go:
--------------------------------------------------------------------------------
1 | //
2 | // IVPN command line interface (CLI)
3 | // https://github.com/ivpn/desktop-app-cli
4 | //
5 | // Created by Stelnykovych Alexandr.
6 | // Copyright (c) 2020 Privatus Limited.
7 | //
8 | // This file is part of the IVPN command line interface.
9 | //
10 | // The IVPN command line interface is free software: you can redistribute it and/or
11 | // modify it under the terms of the GNU General Public License as published by the Free
12 | // Software Foundation, either version 3 of the License, or (at your option) any later version.
13 | //
14 | // The IVPN command line interface is distributed in the hope that it will be useful,
15 | // but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16 | // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
17 | // details.
18 | //
19 | // You should have received a copy of the GNU General Public License
20 | // along with the IVPN command line interface. If not, see .
21 | //
22 |
23 | package commands
24 |
25 | import (
26 | "strings"
27 |
28 | "github.com/ivpn/desktop-app-cli/flags"
29 | apitypes "github.com/ivpn/desktop-app-daemon/api/types"
30 | "github.com/ivpn/desktop-app-daemon/service"
31 | "github.com/ivpn/desktop-app-daemon/vpn"
32 | )
33 |
34 | type CmdState struct {
35 | flags.CmdInfo
36 | }
37 |
38 | func (c *CmdState) Init() {
39 | c.Initialize("status", "Prints full info about IVPN state")
40 | }
41 | func (c *CmdState) Run() error {
42 | return showState()
43 | }
44 |
45 | func showState() error {
46 | fwstate, err := _proto.FirewallStatus()
47 | if err != nil {
48 | return err
49 | }
50 |
51 | state, connected, err := _proto.GetVPNState()
52 | if err != nil {
53 | return err
54 | }
55 |
56 | serverInfo := ""
57 | exitServerInfo := ""
58 |
59 | var servers apitypes.ServersInfoResponse
60 | if state == vpn.CONNECTED {
61 | servers, err = _proto.GetServers()
62 | if err == nil {
63 | slist := serversListByVpnType(servers, connected.VpnType)
64 |
65 | serverInfo = getServerInfoByIP(slist, connected.ServerIP)
66 | exitServerInfo = getServerInfoByID(slist, connected.ExitServerID)
67 | }
68 | }
69 |
70 | w := printAccountInfo(nil, _proto.GetHelloResponse().Session.AccountID)
71 | printState(w, state, connected, serverInfo, exitServerInfo)
72 | if state == vpn.CONNECTED {
73 | printDNSState(w, connected.ManualDNS, &servers)
74 | }
75 | printFirewallState(w, fwstate.IsEnabled, fwstate.IsPersistent, fwstate.IsAllowLAN, fwstate.IsAllowMulticast)
76 | w.Flush()
77 |
78 | // TIPS
79 | tips := make([]TipType, 0, 3)
80 | if len(_proto.GetHelloResponse().Session.Session) == 0 {
81 | tips = append(tips, TipLogin)
82 | }
83 | if state == vpn.CONNECTED {
84 | tips = append(tips, TipDisconnect)
85 | if fwstate.IsEnabled == false {
86 | tips = append(tips, TipFirewallEnable)
87 | }
88 | } else if fwstate.IsEnabled {
89 | tips = append(tips, TipFirewallDisable)
90 | }
91 | if len(tips) > 0 {
92 | PrintTips(tips)
93 | }
94 |
95 | if len(_proto.GetHelloResponse().Session.Session) == 0 {
96 | return service.ErrorNotLoggedIn{}
97 | }
98 | return nil
99 | }
100 |
101 | func getServerInfoByIP(servers []serverDesc, ip string) string {
102 | ip = strings.TrimSpace(ip)
103 | for _, s := range servers {
104 | for h := range s.hosts {
105 | if ip == strings.TrimSpace(h) {
106 | return s.String()
107 | }
108 | }
109 | }
110 | return ""
111 | }
112 |
113 | func getServerInfoByID(servers []serverDesc, id string) string {
114 | id = strings.ToLower(strings.TrimSpace(id))
115 | if len(id) == 0 {
116 | return ""
117 | }
118 |
119 | for _, s := range servers {
120 | sID := strings.ToLower(strings.TrimSpace(s.gateway))
121 | if strings.HasPrefix(sID, id) {
122 | return s.String()
123 | }
124 | }
125 | return ""
126 | }
127 |
--------------------------------------------------------------------------------
/commands/tips.go:
--------------------------------------------------------------------------------
1 | //
2 | // IVPN command line interface (CLI)
3 | // https://github.com/ivpn/desktop-app-cli
4 | //
5 | // Created by Stelnykovych Alexandr.
6 | // Copyright (c) 2020 Privatus Limited.
7 | //
8 | // This file is part of the IVPN command line interface.
9 | //
10 | // The IVPN command line interface is free software: you can redistribute it and/or
11 | // modify it under the terms of the GNU General Public License as published by the Free
12 | // Software Foundation, either version 3 of the License, or (at your option) any later version.
13 | //
14 | // The IVPN command line interface is distributed in the hope that it will be useful,
15 | // but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16 | // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
17 | // details.
18 | //
19 | // You should have received a copy of the GNU General Public License
20 | // along with the IVPN command line interface. If not, see .
21 | //
22 |
23 | package commands
24 |
25 | import (
26 | "fmt"
27 | "os"
28 | "path/filepath"
29 | "text/tabwriter"
30 | )
31 |
32 | type TipType uint
33 |
34 | const (
35 | TipHelp TipType = iota
36 | TipHelpFull TipType = iota
37 | TipHelpCommand TipType = iota
38 | TipLogout TipType = iota
39 | TipLogin TipType = iota
40 | TipForceLogin TipType = iota
41 | TipServers TipType = iota
42 | TipConnectHelp TipType = iota
43 | TipDisconnect TipType = iota
44 | TipFirewallDisable TipType = iota
45 | TipFirewallEnable TipType = iota
46 | TipLastConnection TipType = iota
47 | )
48 |
49 | func PrintTips(tips []TipType) {
50 | if len(tips) == 0 {
51 | return
52 | }
53 |
54 | writer := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
55 |
56 | fmt.Println("")
57 | fmt.Fprintln(writer, "Tips:")
58 | for _, t := range tips {
59 | PrintTip(writer, t)
60 | }
61 |
62 | writer.Flush()
63 | fmt.Println("")
64 | }
65 |
66 | func PrintTip(w *tabwriter.Writer, tip TipType) {
67 |
68 | str := ""
69 | switch tip {
70 | case TipHelp:
71 | str = newTip("-h", "Show all commands")
72 | break
73 | case TipHelpFull:
74 | str = newTip("-h -full", "Show detailed description about all commands")
75 | break
76 | case TipHelpCommand:
77 | str = newTip("COMMAND -h", "Show detailed description of command")
78 | break
79 | case TipLogout:
80 | str = newTip("logout", "Logout from this device")
81 | break
82 | case TipLogin:
83 | str = newTip("login ACCOUNT_ID", "Log in with your Account ID")
84 | break
85 | case TipForceLogin:
86 | str = newTip("login -force ACCOUNT_ID", "Log in with your Account ID and logout from all other devices")
87 | break
88 | case TipServers:
89 | str = newTip("servers", "Show servers list")
90 | break
91 | case TipConnectHelp:
92 | str = newTip("connect -h", "Show usage of 'connect' command")
93 | break
94 | case TipDisconnect:
95 | str = newTip("disconnect", "Stop current VPN connection")
96 | break
97 | case TipFirewallDisable:
98 | str = newTip("firewall -off", "Disable firewall (to allow connectivity outside VPN)")
99 | break
100 | case TipFirewallEnable:
101 | str = newTip("firewall -on", "Enable firewall (to block all connectivity outside VPN)")
102 | break
103 | case TipLastConnection:
104 | str = newTip("connect -last", "Connect with last successful connection parameters")
105 | break
106 | }
107 |
108 | if len(str) > 0 {
109 | fmt.Fprintln(w, str)
110 | }
111 | }
112 |
113 | func newTip(command string, description string) string {
114 | return fmt.Sprintf("\t%s %s\t %s", filepath.Base(os.Args[0]), command, description)
115 | }
116 |
--------------------------------------------------------------------------------
/commands/wireguard.go:
--------------------------------------------------------------------------------
1 | //
2 | // IVPN command line interface (CLI)
3 | // https://github.com/ivpn/desktop-app-cli
4 | //
5 | // Created by Stelnykovych Alexandr.
6 | // Copyright (c) 2020 Privatus Limited.
7 | //
8 | // This file is part of the IVPN command line interface.
9 | //
10 | // The IVPN command line interface is free software: you can redistribute it and/or
11 | // modify it under the terms of the GNU General Public License as published by the Free
12 | // Software Foundation, either version 3 of the License, or (at your option) any later version.
13 | //
14 | // The IVPN command line interface is distributed in the hope that it will be useful,
15 | // but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16 | // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
17 | // details.
18 | //
19 | // You should have received a copy of the GNU General Public License
20 | // along with the IVPN command line interface. If not, see .
21 | //
22 |
23 | package commands
24 |
25 | import (
26 | "fmt"
27 | "os"
28 | "text/tabwriter"
29 | "time"
30 |
31 | "github.com/ivpn/desktop-app-cli/flags"
32 | "github.com/ivpn/desktop-app-daemon/service"
33 | )
34 |
35 | type CmdWireGuard struct {
36 | flags.CmdInfo
37 | state bool
38 | regenerate bool
39 | rotationInterval int
40 | }
41 |
42 | func (c *CmdWireGuard) Init() {
43 | c.Initialize("wgkeys", "WireGuard keys management")
44 | c.BoolVar(&c.state, "status", false, "(default) Show WireGuard configuration")
45 | c.IntVar(&c.rotationInterval, "rotation_interval", 0, "DAYS", "Set WireGuard keys rotation interval. [1-30] days (default = 7 days)")
46 | c.BoolVar(&c.regenerate, "regenerate", false, "Regenerate WireGuard keys")
47 | }
48 | func (c *CmdWireGuard) Run() error {
49 | if c.rotationInterval < 0 || c.rotationInterval > 30 {
50 | fmt.Println("Error: keys rotation interval should be in diapasone [1-30] days")
51 | return flags.BadParameter{}
52 | }
53 |
54 | defer func() {
55 | helloResp := _proto.GetHelloResponse()
56 | if len(helloResp.Session.Session) == 0 {
57 | fmt.Println(service.ErrorNotLoggedIn{})
58 |
59 | PrintTips([]TipType{TipLogin})
60 | }
61 | }()
62 |
63 | resp, err := _proto.SendHello()
64 | if err != nil {
65 | return err
66 | }
67 | if len(resp.DisabledFunctions.WireGuardError) > 0 {
68 | return fmt.Errorf("WireGuard functionality disabled:\n\t" + resp.DisabledFunctions.WireGuardError)
69 | }
70 |
71 | if c.regenerate {
72 | fmt.Println("Regenerating WG keys...")
73 | if err := c.generate(); err != nil {
74 | return err
75 | }
76 | }
77 |
78 | if c.rotationInterval > 0 {
79 | interval := time.Duration(time.Hour * 24 * time.Duration(c.rotationInterval))
80 | fmt.Printf("Changing WG keys rotation interval to %v ...\n", interval)
81 | if err := c.setRotateInterval(int64(interval / time.Second)); err != nil {
82 | return err
83 | }
84 | }
85 |
86 | if err := c.getState(); err != nil {
87 | return err
88 | }
89 |
90 | return nil
91 | }
92 |
93 | func (c *CmdWireGuard) generate() error {
94 | return _proto.WGKeysGenerate()
95 | }
96 |
97 | func (c *CmdWireGuard) setRotateInterval(interval int64) error {
98 | return _proto.WGKeysRotationInterval(interval)
99 | }
100 |
101 | func (c *CmdWireGuard) getState() error {
102 | resp, err := _proto.SendHello()
103 | if err != nil {
104 | return err
105 | }
106 |
107 | if len(resp.Session.Session) == 0 {
108 | return nil
109 | }
110 |
111 | w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
112 | fmt.Fprintln(w, fmt.Sprintf("Local IP:\t%v", resp.Session.WgLocalIP))
113 | fmt.Fprintln(w, fmt.Sprintf("Public KEY:\t%v", resp.Session.WgPublicKey))
114 | fmt.Fprintln(w, fmt.Sprintf("Generated:\t%v", time.Unix(resp.Session.WgKeyGenerated, 0)))
115 | fmt.Fprintln(w, fmt.Sprintf("Rotation interval:\t%v", time.Duration(time.Second*time.Duration(resp.Session.WgKeysRegenInerval))))
116 | w.Flush()
117 |
118 | return nil
119 | }
120 |
--------------------------------------------------------------------------------
/flags/errors.go:
--------------------------------------------------------------------------------
1 | //
2 | // IVPN command line interface (CLI)
3 | // https://github.com/ivpn/desktop-app-cli
4 | //
5 | // Created by Stelnykovych Alexandr.
6 | // Copyright (c) 2020 Privatus Limited.
7 | //
8 | // This file is part of the IVPN command line interface.
9 | //
10 | // The IVPN command line interface is free software: you can redistribute it and/or
11 | // modify it under the terms of the GNU General Public License as published by the Free
12 | // Software Foundation, either version 3 of the License, or (at your option) any later version.
13 | //
14 | // The IVPN command line interface is distributed in the hope that it will be useful,
15 | // but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16 | // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
17 | // details.
18 | //
19 | // You should have received a copy of the GNU General Public License
20 | // along with the IVPN command line interface. If not, see .
21 | //
22 |
23 | package flags
24 |
25 | // BadParameter error
26 | type BadParameter struct {
27 | Message string
28 | }
29 |
30 | func (e BadParameter) Error() string {
31 | if len(e.Message) == 0 {
32 | return "bad parameter"
33 | }
34 | return "bad parameter (" + e.Message + ")"
35 | }
36 |
--------------------------------------------------------------------------------
/flags/flags.go:
--------------------------------------------------------------------------------
1 | //
2 | // IVPN command line interface (CLI)
3 | // https://github.com/ivpn/desktop-app-cli
4 | //
5 | // Created by Stelnykovych Alexandr.
6 | // Copyright (c) 2020 Privatus Limited.
7 | //
8 | // This file is part of the IVPN command line interface.
9 | //
10 | // The IVPN command line interface is free software: you can redistribute it and/or
11 | // modify it under the terms of the GNU General Public License as published by the Free
12 | // Software Foundation, either version 3 of the License, or (at your option) any later version.
13 | //
14 | // The IVPN command line interface is distributed in the hope that it will be useful,
15 | // but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16 | // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
17 | // details.
18 | //
19 | // You should have received a copy of the GNU General Public License
20 | // along with the IVPN command line interface. If not, see .
21 | //
22 |
23 | package flags
24 |
25 | import (
26 | "flag"
27 | "fmt"
28 | "os"
29 | "sort"
30 | "strings"
31 | "text/tabwriter"
32 | )
33 |
34 | // NewFlagSetEx - create new command object
35 | func NewFlagSetEx(name, description string) *CmdInfo {
36 | ret := &CmdInfo{}
37 | ret.Initialize(name, description)
38 | return ret
39 | }
40 |
41 | // CmdInfo contains info about single command with arguments
42 | type CmdInfo struct {
43 | description string
44 | fs *flag.FlagSet
45 | defaultArg *string
46 | defaultArgName string
47 | argNames map[string]string // variable name -> argument description
48 | }
49 |
50 | // Initialize initialises object
51 | func (c *CmdInfo) Initialize(name, description string) {
52 | c.argNames = make(map[string]string)
53 | c.description = description
54 | c.fs = flag.NewFlagSet(name, flag.ExitOnError)
55 | c.fs.Usage = func() {
56 | c.Usage(false)
57 | }
58 | }
59 |
60 | // Parse parses flag definitions from the argument list
61 | // see description of Flagset.Parse()
62 | func (c *CmdInfo) Parse(arguments []string) error {
63 | if err := c.fs.Parse(arguments); err != nil {
64 | return err
65 | }
66 |
67 | if c.defaultArg != nil {
68 | // Looking for default argument
69 | // Only one argument allowed.
70 | if len(c.fs.Args()) > 1 {
71 | return BadParameter{}
72 | }
73 | if len(c.fs.Args()) > 0 {
74 | *c.defaultArg = c.fs.Args()[0]
75 | }
76 | } else if len(c.fs.Args()) > 0 {
77 | return BadParameter{}
78 | }
79 | return nil
80 | }
81 |
82 | // NFlag returns the number of flags that have been set.
83 | func (c *CmdInfo) NFlag() int { return c.fs.NFlag() }
84 |
85 | // Name - command name
86 | func (c *CmdInfo) Name() string { return c.fs.Name() }
87 |
88 | // Description - command name
89 | func (c *CmdInfo) Description() string { return c.description }
90 |
91 | // Usage - prints command usage
92 | func (c *CmdInfo) Usage(short bool) {
93 | fmt.Printf("Command usage:\n")
94 | c.usage(nil, short)
95 | }
96 |
97 | // UsageFormetted - prints command usage into tabwriter
98 | func (c *CmdInfo) UsageFormetted(w *tabwriter.Writer, short bool) {
99 | c.usage(w, short)
100 | }
101 |
102 | func (c *CmdInfo) usage(w *tabwriter.Writer, short bool) {
103 |
104 | type flagInfo struct {
105 | DetailedName string
106 | ArgDesc string
107 | }
108 |
109 | tmpmap := make(map[string]flagInfo)
110 |
111 | // collect output date
112 | flagIterator := func(f *flag.Flag) {
113 | if flags, ok := tmpmap[f.Usage]; ok == false {
114 | tmpmap[f.Usage] = flagInfo{DetailedName: "-" + f.Name, ArgDesc: c.argNames[f.Name]}
115 | } else {
116 | flags.DetailedName = flags.DetailedName + "|-" + f.Name
117 | tmpmap[f.Usage] = flags
118 | }
119 | }
120 | c.fs.VisitAll(flagIterator)
121 |
122 | writer := w
123 | // create local writer (if not defined)
124 | if writer == nil {
125 | writer = tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
126 | }
127 |
128 | // sorting keys (map is not sorted)
129 | keys := make([]string, 0, len(tmpmap))
130 | for k := range tmpmap {
131 | keys = append(keys, k)
132 | }
133 | sort.Strings(keys)
134 |
135 | // Format output
136 | // command
137 | lines := strings.Split(c.Description(), "\n")
138 | fmt.Fprintln(writer, fmt.Sprintf("%s %s\t%s", c.Name(), c.defaultArgName, lines[0]))
139 | if short {
140 | return
141 | }
142 | if len(lines) > 1 {
143 | for i := 1; i < len(lines); i++ {
144 | fmt.Fprintln(writer, fmt.Sprintf(" %s %s\t%s", "", "", lines[i]))
145 | }
146 | }
147 |
148 | // loop trough flags map
149 | for _, usage := range keys {
150 | flag, _ := tmpmap[usage]
151 | lines := strings.Split(usage, "\n")
152 | fmt.Fprintln(writer, fmt.Sprintf(" %s %s\t- %s", flag.DetailedName, flag.ArgDesc, lines[0]))
153 | if len(lines) > 1 {
154 | for i := 1; i < len(lines); i++ {
155 | fmt.Fprintln(writer, fmt.Sprintf(" %s %s\t%s", "", "", lines[i]))
156 | }
157 | }
158 | }
159 |
160 | // in case of just created writer - flush it now
161 | if w == nil {
162 | writer.Flush()
163 | }
164 | }
165 |
166 | // DefaultStringVar defines default string argument
167 | func (c *CmdInfo) DefaultStringVar(p *string, usage string) {
168 | c.defaultArgName = usage
169 | c.defaultArg = p
170 | }
171 |
172 | // StringVar defines a string flag with specified name, default value, and usage string.
173 | // The argument p points to a string variable in which to store the value of the flag.
174 | func (c *CmdInfo) StringVar(p *string, name string, defValue string, argNAme string, usage string) {
175 | c.fs.StringVar(p, name, defValue, usage)
176 | c.argNames[name] = argNAme
177 | }
178 |
179 | // IntVar defines an int flag with specified name, default value, and usage string.
180 | // The argument p points to an int variable in which to store the value of the flag.
181 | func (c *CmdInfo) IntVar(p *int, name string, defValue int, argNAme string, usage string) {
182 | c.fs.IntVar(p, name, defValue, usage)
183 | c.argNames[name] = argNAme
184 | }
185 |
186 | // BoolVar defines a bool flag with specified name, default value, and usage string.
187 | // The argument p points to a bool variable in which to store the value of the flag.
188 | func (c *CmdInfo) BoolVar(p *bool, name string, defValue bool, usage string) {
189 | c.fs.BoolVar(p, name, defValue, usage)
190 | }
191 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/ivpn/desktop-app-cli
2 |
3 | go 1.13
4 |
5 | require (
6 | github.com/ivpn/desktop-app-daemon v0.0.0-20200512121529-01ef9a020b36
7 | golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37
8 | )
9 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
2 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
3 | github.com/ivpn/desktop-app-daemon v0.0.0-20200512121529-01ef9a020b36 h1:/kLyr/gWrjCbKs2srqwJ+tdlhQpA79LrvjCKeH7Sv+Q=
4 | github.com/ivpn/desktop-app-daemon v0.0.0-20200512121529-01ef9a020b36/go.mod h1:ameqFKtjY5/nj29lcBBZOgXQ5ecJOGk5kB2NP2Yvumc=
5 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
6 | golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37 h1:cg5LA/zNPRzIXIWSCxQW10Rvpy94aQh3LT/ShoCpkHw=
7 | golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
8 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
9 | golang.org/x/net v0.0.0-20200226051749-491c5fce7268 h1:fnuNgko6vrkrxuKfTMd+0eOz50ziv+Wi+t38KUT3j+E=
10 | golang.org/x/net v0.0.0-20200226051749-491c5fce7268/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
11 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
12 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
13 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
14 | golang.org/x/sys v0.0.0-20200327173247-9dae0f8f5775 h1:TC0v2RSO1u2kn1ZugjrFXkRZAEaqMN/RW+OTZkBzmLE=
15 | golang.org/x/sys v0.0.0-20200327173247-9dae0f8f5775/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
16 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
17 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | //
2 | // IVPN command line interface (CLI)
3 | // https://github.com/ivpn/desktop-app-cli
4 | //
5 | // Created by Stelnykovych Alexandr.
6 | // Copyright (c) 2020 Privatus Limited.
7 | //
8 | // This file is part of the IVPN command line interface.
9 | //
10 | // The IVPN command line interface is free software: you can redistribute it and/or
11 | // modify it under the terms of the GNU General Public License as published by the Free
12 | // Software Foundation, either version 3 of the License, or (at your option) any later version.
13 | //
14 | // The IVPN command line interface is distributed in the hope that it will be useful,
15 | // but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16 | // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
17 | // details.
18 | //
19 | // You should have received a copy of the GNU General Public License
20 | // along with the IVPN command line interface. If not, see .
21 | //
22 |
23 | package main
24 |
25 | import (
26 | "fmt"
27 | "io/ioutil"
28 | "log"
29 | "os"
30 | "path/filepath"
31 | "strconv"
32 | "strings"
33 | "text/tabwriter"
34 |
35 | "github.com/ivpn/desktop-app-cli/commands"
36 | "github.com/ivpn/desktop-app-cli/flags"
37 | "github.com/ivpn/desktop-app-cli/protocol"
38 | "github.com/ivpn/desktop-app-daemon/service/platform"
39 | "github.com/ivpn/desktop-app-daemon/version"
40 | )
41 |
42 | // ICommand interface for command line command
43 | type ICommand interface {
44 | Init()
45 | Parse(arguments []string) error
46 | Run() error
47 |
48 | Name() string
49 | Description() string
50 | Usage(short bool)
51 | UsageFormetted(w *tabwriter.Writer, short bool)
52 | }
53 |
54 | var (
55 | _commands []ICommand
56 | )
57 |
58 | func addCommand(cmd ICommand) {
59 | cmd.Init()
60 | _commands = append(_commands, cmd)
61 | }
62 |
63 | func printHeader() {
64 | fmt.Println("Command-line interface for IVPN client (www.ivpn.net)")
65 | fmt.Println("version:" + version.GetFullVersion() + "\n")
66 | }
67 |
68 | func printUsageAll(short bool) {
69 | printHeader()
70 | fmt.Printf("Usage: %s COMMAND [OPTIONS...] [COMMAND_PARAMETER] [-h|-help]\n\n", filepath.Base(os.Args[0]))
71 |
72 | fmt.Println("COMMANDS:")
73 | writer := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
74 | for _, c := range _commands {
75 | c.UsageFormetted(writer, short)
76 | if short == false {
77 | fmt.Fprintln(writer, "\t")
78 | }
79 | }
80 | writer.Flush()
81 |
82 | if short {
83 | commands.PrintTips([]commands.TipType{commands.TipHelpCommand, commands.TipHelpFull})
84 | }
85 | }
86 |
87 | func main() {
88 | // initialize all possible commands
89 | stateCmd := commands.CmdState{}
90 | addCommand(&stateCmd)
91 | addCommand(&commands.CmdConnect{})
92 | addCommand(&commands.CmdDisconnect{})
93 | addCommand(&commands.CmdServers{})
94 | addCommand(&commands.CmdFirewall{})
95 | addCommand(&commands.CmdWireGuard{})
96 | addCommand(&commands.CmdDns{})
97 | addCommand(&commands.CmdAntitracker{})
98 | addCommand(&commands.CmdLogs{})
99 | addCommand(&commands.CmdLogin{})
100 | addCommand(&commands.CmdLogout{})
101 | addCommand(&commands.CmdAccount{})
102 |
103 | if len(os.Args) >= 2 {
104 | if os.Args[1] == "?" || os.Args[1] == "-?" || os.Args[1] == "-h" || os.Args[1] == "--h" || os.Args[1] == "-help" || os.Args[1] == "--help" {
105 | if len(os.Args) >= 3 && strings.ToLower(os.Args[2]) == "-full" {
106 | printUsageAll(false) // detailed commans descriptions
107 | } else {
108 | printUsageAll(true) // short commands descriptions
109 | }
110 | os.Exit(0)
111 | }
112 | }
113 |
114 | // initialize command handler
115 | port, secret, err := readDaemonPort()
116 | if err != nil {
117 | fmt.Fprintf(os.Stderr, "ERROR: Unable to connect to service: %s\n", err)
118 | printServStartInstructions()
119 | os.Exit(1)
120 | }
121 |
122 | proto := protocol.CreateClient(port, secret)
123 | if err := proto.Connect(); err != nil {
124 | fmt.Fprintf(os.Stderr, "ERROR: Failed to connect to service : %s\n", err)
125 | printServStartInstructions()
126 | os.Exit(1)
127 | }
128 |
129 | commands.Initialize(proto)
130 |
131 | if len(os.Args) < 2 {
132 | if err := stateCmd.Run(); err != nil {
133 | fmt.Fprintf(os.Stderr, "\n%v\n", err)
134 | os.Exit(1)
135 | }
136 | return
137 | }
138 |
139 | // process command
140 | isProcessed := false
141 | for _, c := range _commands {
142 | if c.Name() == os.Args[1] {
143 | isProcessed = true
144 | runCommand(c, os.Args[2:])
145 | break
146 | }
147 | }
148 |
149 | // unknown command
150 | if isProcessed == false {
151 | fmt.Fprintf(os.Stderr, "Error. Unexpected command %s\n", os.Args[1])
152 | printUsageAll(true)
153 | os.Exit(1)
154 | }
155 | }
156 |
157 | func runCommand(c ICommand, args []string) {
158 | if err := c.Parse(args); err != nil {
159 | fmt.Fprintf(os.Stderr, "Error: %v\n", err)
160 | if _, ok := err.(flags.BadParameter); ok == true {
161 | c.Usage(false)
162 | }
163 | os.Exit(1)
164 | }
165 |
166 | if err := c.Run(); err != nil {
167 | fmt.Fprintf(os.Stderr, "Error: %v\n", err)
168 | if _, ok := err.(flags.BadParameter); ok == true {
169 | c.Usage(false)
170 | }
171 | os.Exit(1)
172 | }
173 | }
174 |
175 | // read port+secret to be able to connect to a daemon
176 | func readDaemonPort() (port int, secret uint64, err error) {
177 | file := platform.ServicePortFile()
178 | if len(file) == 0 {
179 | return 0, 0, fmt.Errorf("connection-info file not defined")
180 | }
181 |
182 | if _, err := os.Stat(file); err != nil {
183 | if os.IsNotExist(err) {
184 | return 0, 0, fmt.Errorf("please, ensure IVPN daemon is running (connection-info not exists)")
185 | }
186 | return 0, 0, fmt.Errorf("connection-info check error: %s", err)
187 | }
188 |
189 | data, err := ioutil.ReadFile(file)
190 | if err != nil {
191 | log.Fatal(err)
192 | }
193 |
194 | vars := strings.Split(string(data), ":")
195 | if len(vars) != 2 {
196 | return 0, 0, fmt.Errorf("failed to parse connection-info")
197 | }
198 |
199 | port, err = strconv.Atoi(strings.TrimSpace(vars[0]))
200 | if err != nil {
201 | return 0, 0, fmt.Errorf("failed to parse connection-info: %w", err)
202 | }
203 |
204 | secret, err = strconv.ParseUint(strings.TrimSpace(vars[1]), 16, 64)
205 | if err != nil {
206 | return 0, 0, fmt.Errorf("failed to parse connection-info: %w", err)
207 | }
208 |
209 | return port, secret, nil
210 | }
211 |
--------------------------------------------------------------------------------
/main_darwin.go:
--------------------------------------------------------------------------------
1 | //
2 | // IVPN command line interface (CLI)
3 | // https://github.com/ivpn/desktop-app-cli
4 | //
5 | // Created by Stelnykovych Alexandr.
6 | // Copyright (c) 2020 Privatus Limited.
7 | //
8 | // This file is part of the IVPN command line interface.
9 | //
10 | // The IVPN command line interface is free software: you can redistribute it and/or
11 | // modify it under the terms of the GNU General Public License as published by the Free
12 | // Software Foundation, either version 3 of the License, or (at your option) any later version.
13 | //
14 | // The IVPN command line interface is distributed in the hope that it will be useful,
15 | // but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16 | // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
17 | // details.
18 | //
19 | // You should have received a copy of the GNU General Public License
20 | // along with the IVPN command line interface. If not, see .
21 | //
22 |
23 | package main
24 |
25 | func printServStartInstructions() {
26 | }
27 |
--------------------------------------------------------------------------------
/main_linux.go:
--------------------------------------------------------------------------------
1 | //
2 | // IVPN command line interface (CLI)
3 | // https://github.com/ivpn/desktop-app-cli
4 | //
5 | // Created by Stelnykovych Alexandr.
6 | // Copyright (c) 2020 Privatus Limited.
7 | //
8 | // This file is part of the IVPN command line interface.
9 | //
10 | // The IVPN command line interface is free software: you can redistribute it and/or
11 | // modify it under the terms of the GNU General Public License as published by the Free
12 | // Software Foundation, either version 3 of the License, or (at your option) any later version.
13 | //
14 | // The IVPN command line interface is distributed in the hope that it will be useful,
15 | // but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16 | // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
17 | // details.
18 | //
19 | // You should have received a copy of the GNU General Public License
20 | // along with the IVPN command line interface. If not, see .
21 | //
22 |
23 | package main
24 |
25 | import (
26 | "fmt"
27 | "io/ioutil"
28 | "path"
29 | )
30 |
31 | func printServStartInstructions() {
32 | fmt.Printf("Please, restart 'ivpn-service'\n")
33 | tmpDir := "/opt/ivpn/mutable"
34 | // print service install instructions (if exists)
35 | content, err := ioutil.ReadFile(path.Join(tmpDir, "service_install.txt"))
36 | if err == nil {
37 | fmt.Println(string(content))
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/main_windows.go:
--------------------------------------------------------------------------------
1 | //
2 | // IVPN command line interface (CLI)
3 | // https://github.com/ivpn/desktop-app-cli
4 | //
5 | // Created by Stelnykovych Alexandr.
6 | // Copyright (c) 2020 Privatus Limited.
7 | //
8 | // This file is part of the IVPN command line interface.
9 | //
10 | // The IVPN command line interface is free software: you can redistribute it and/or
11 | // modify it under the terms of the GNU General Public License as published by the Free
12 | // Software Foundation, either version 3 of the License, or (at your option) any later version.
13 | //
14 | // The IVPN command line interface is distributed in the hope that it will be useful,
15 | // but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16 | // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
17 | // details.
18 | //
19 | // You should have received a copy of the GNU General Public License
20 | // along with the IVPN command line interface. If not, see .
21 | //
22 |
23 | package main
24 |
25 | import "fmt"
26 |
27 | func printServStartInstructions() {
28 | fmt.Printf("Please, restart 'IVPN Client' service\n")
29 | }
30 |
--------------------------------------------------------------------------------
/protocol/client.go:
--------------------------------------------------------------------------------
1 | //
2 | // IVPN command line interface (CLI)
3 | // https://github.com/ivpn/desktop-app-cli
4 | //
5 | // Created by Stelnykovych Alexandr.
6 | // Copyright (c) 2020 Privatus Limited.
7 | //
8 | // This file is part of the IVPN command line interface.
9 | //
10 | // The IVPN command line interface is free software: you can redistribute it and/or
11 | // modify it under the terms of the GNU General Public License as published by the Free
12 | // Software Foundation, either version 3 of the License, or (at your option) any later version.
13 | //
14 | // The IVPN command line interface is distributed in the hope that it will be useful,
15 | // but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16 | // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
17 | // details.
18 | //
19 | // You should have received a copy of the GNU General Public License
20 | // along with the IVPN command line interface. If not, see .
21 | //
22 |
23 | package protocol
24 |
25 | import (
26 | "errors"
27 | "fmt"
28 | "net"
29 | "sync"
30 | "time"
31 |
32 | apitypes "github.com/ivpn/desktop-app-daemon/api/types"
33 | "github.com/ivpn/desktop-app-daemon/logger"
34 | "github.com/ivpn/desktop-app-daemon/protocol/types"
35 | "github.com/ivpn/desktop-app-daemon/vpn"
36 | )
37 |
38 | // Client for IVPN daemon
39 | type Client struct {
40 | _port int
41 | _secret uint64
42 | _conn net.Conn
43 |
44 | _requestIdx int
45 |
46 | _defaultTimeout time.Duration
47 | _receivers map[*receiverChannel]struct{}
48 | _receiversLocker sync.Mutex
49 |
50 | _helloResponse types.HelloResp
51 | }
52 |
53 | // ResponseTimeout error
54 | type ResponseTimeout struct {
55 | }
56 |
57 | func (e ResponseTimeout) Error() string {
58 | return "response timeout"
59 | }
60 |
61 | // CreateClient initialising new client for IVPN daemon
62 | func CreateClient(port int, secret uint64) *Client {
63 | return &Client{
64 | _port: port,
65 | _secret: secret,
66 | _defaultTimeout: time.Second * 30,
67 | _receivers: make(map[*receiverChannel]struct{})}
68 | }
69 |
70 | // Connect is connecting to daemon
71 | func (c *Client) Connect() (err error) {
72 | if c._conn != nil {
73 | return fmt.Errorf("already connected")
74 | }
75 |
76 | logger.Info("Connecting...")
77 |
78 | c._conn, err = net.Dial("tcp", fmt.Sprintf(":%d", c._port))
79 | if err != nil {
80 | return fmt.Errorf("failed to connect to IVPN daemon (does IVPN daemon/service running?): %w", err)
81 | }
82 |
83 | logger.Info("Connected")
84 |
85 | // start receiver
86 | go c.receiverRoutine()
87 |
88 | if _, err := c.SendHello(); err != nil {
89 | return err
90 | }
91 |
92 | return nil
93 | }
94 |
95 | // SendHello - send initial message and get current status
96 | func (c *Client) SendHello() (helloResponse types.HelloResp, err error) {
97 | if err := c.ensureConnected(); err != nil {
98 | return helloResponse, err
99 | }
100 |
101 | helloReq := types.Hello{Secret: c._secret, KeepDaemonAlone: true, GetStatus: true, Version: "1.0"}
102 |
103 | if err := c.sendRecvTimeOut(&helloReq, &c._helloResponse, time.Second*7); err != nil {
104 | if _, ok := errors.Unwrap(err).(ResponseTimeout); ok {
105 | return helloResponse, fmt.Errorf("Failed to send 'Hello' request: %w", err)
106 | }
107 | return helloResponse, fmt.Errorf("Failed to send 'Hello' request: %w", err)
108 | }
109 | return c._helloResponse, nil
110 | }
111 |
112 | // GetHelloResponse returns initialization response from daemon
113 | func (c *Client) GetHelloResponse() types.HelloResp {
114 | return c._helloResponse
115 | }
116 |
117 | // SessionNew creates new session
118 | func (c *Client) SessionNew(accountID string, forceLogin bool) (apiStatus int, err error) {
119 | if err := c.ensureConnected(); err != nil {
120 | return 0, err
121 | }
122 |
123 | req := types.SessionNew{AccountID: accountID, ForceLogin: forceLogin}
124 | var resp types.SessionNewResp
125 |
126 | if err := c.sendRecv(&req, &resp); err != nil {
127 | return 0, err
128 | }
129 |
130 | if len(resp.Session.Session) <= 0 {
131 | return resp.APIStatus, fmt.Errorf("[%d] %s", resp.APIStatus, resp.APIErrorMessage)
132 | }
133 |
134 | return resp.APIStatus, nil
135 | }
136 |
137 | // SessionDelete remove session
138 | func (c *Client) SessionDelete() error {
139 | if err := c.ensureConnected(); err != nil {
140 | return err
141 | }
142 |
143 | req := types.SessionDelete{}
144 | var resp types.EmptyResp
145 |
146 | if err := c.sendRecv(&req, &resp); err != nil {
147 | return err
148 | }
149 |
150 | return nil
151 | }
152 |
153 | // SessionStatus get session status
154 | func (c *Client) SessionStatus() (ret types.AccountStatusResp, err error) {
155 | if err := c.ensureConnected(); err != nil {
156 | return ret, err
157 | }
158 |
159 | req := types.AccountStatus{}
160 | var resp types.AccountStatusResp
161 |
162 | if err := c.sendRecv(&req, &resp); err != nil {
163 | return ret, err
164 | }
165 |
166 | return resp, nil
167 | }
168 |
169 | // SetPreferences sends config parameter to daemon
170 | // TODO: avoid using keys as a strings
171 | func (c *Client) SetPreferences(key, value string) error {
172 | if err := c.ensureConnected(); err != nil {
173 | return err
174 | }
175 |
176 | req := types.SetPreference{Key: key, Value: value}
177 |
178 | // TODO: daemon have to return confirmation
179 | if err := c.send(&req, 0); err != nil {
180 | return err
181 | }
182 |
183 | return nil
184 | }
185 |
186 | // FirewallSet change firewall state
187 | func (c *Client) FirewallSet(isOn bool) error {
188 | if err := c.ensureConnected(); err != nil {
189 | return err
190 | }
191 |
192 | // changing killswitch state
193 | req := types.KillSwitchSetEnabled{IsEnabled: isOn}
194 | var resp types.EmptyResp
195 | if err := c.sendRecv(&req, &resp); err != nil {
196 | return err
197 | }
198 |
199 | // requesting status
200 | state, err := c.FirewallStatus()
201 | if err != nil {
202 | return err
203 | }
204 |
205 | if state.IsEnabled != isOn {
206 | return fmt.Errorf("firewall state did not change [isEnabled=%v]", state.IsEnabled)
207 | }
208 |
209 | return nil
210 | }
211 |
212 | // FirewallAllowLan set configuration 'allow LAN'
213 | func (c *Client) FirewallAllowLan(allow bool) error {
214 | if err := c.ensureConnected(); err != nil {
215 | return err
216 | }
217 |
218 | // changing killswitch configuration
219 | req := types.KillSwitchSetAllowLAN{AllowLAN: allow, Synchronously: true}
220 | var resp types.EmptyResp
221 | if err := c.sendRecv(&req, &resp); err != nil {
222 | return err
223 | }
224 |
225 | return nil
226 | }
227 |
228 | // FirewallAllowLanMulticast set configuration 'allow LAN multicast'
229 | func (c *Client) FirewallAllowLanMulticast(allow bool) error {
230 | if err := c.ensureConnected(); err != nil {
231 | return err
232 | }
233 |
234 | // changing killswitch configuration
235 | req := types.KillSwitchSetAllowLANMulticast{AllowLANMulticast: allow, Synchronously: true}
236 | var resp types.EmptyResp
237 | if err := c.sendRecv(&req, &resp); err != nil {
238 | return err
239 | }
240 |
241 | return nil
242 | }
243 |
244 | // FirewallStatus get firewall state
245 | func (c *Client) FirewallStatus() (state types.KillSwitchStatusResp, err error) {
246 | if err := c.ensureConnected(); err != nil {
247 | return state, err
248 | }
249 |
250 | // requesting status
251 | statReq := types.KillSwitchGetStatus{}
252 | if err := c.sendRecv(&statReq, &state); err != nil {
253 | return state, err
254 | }
255 |
256 | return state, nil
257 | }
258 |
259 | // GetServers gets servers list
260 | func (c *Client) GetServers() (apitypes.ServersInfoResponse, error) {
261 | if err := c.ensureConnected(); err != nil {
262 | return apitypes.ServersInfoResponse{}, err
263 | }
264 |
265 | req := types.GetServers{}
266 | var resp types.ServerListResp
267 |
268 | if err := c.sendRecv(&req, &resp); err != nil {
269 | return resp.VpnServers, err
270 | }
271 |
272 | return resp.VpnServers, nil
273 | }
274 |
275 | // GetVPNState returns current VPN connection state
276 | func (c *Client) GetVPNState() (vpn.State, types.ConnectedResp, error) {
277 | respConnected := types.ConnectedResp{}
278 | respDisconnected := types.DisconnectedResp{}
279 | respState := types.VpnStateResp{}
280 |
281 | if err := c.ensureConnected(); err != nil {
282 | return vpn.DISCONNECTED, respConnected, err
283 | }
284 |
285 | req := types.GetVPNState{}
286 |
287 | _, _, err := c.sendRecvAny(&req, &respConnected, &respDisconnected, &respState)
288 | if err != nil {
289 | return vpn.DISCONNECTED, respConnected, err
290 | }
291 |
292 | if len(respConnected.Command) > 0 {
293 | return vpn.CONNECTED, respConnected, nil
294 | }
295 |
296 | if len(respDisconnected.Command) > 0 {
297 | return vpn.DISCONNECTED, respConnected, nil
298 | }
299 |
300 | if len(respState.Command) > 0 {
301 | return respState.StateVal, respConnected, nil
302 | }
303 |
304 | return vpn.DISCONNECTED, respConnected, fmt.Errorf("failed to receive VPN state (not expected return type)")
305 | }
306 |
307 | // DisconnectVPN disconnect active VPN connection
308 | func (c *Client) DisconnectVPN() error {
309 | if err := c.ensureConnected(); err != nil {
310 | return err
311 | }
312 |
313 | req := types.Disconnect{}
314 | respEmpty := types.EmptyResp{}
315 | respDisconnected := types.DisconnectedResp{}
316 |
317 | _, _, err := c.sendRecvAny(&req, &respDisconnected, &respEmpty)
318 | if err != nil {
319 | return err
320 | }
321 |
322 | if len(respDisconnected.Command) == 0 && len(respEmpty.Command) == 0 {
323 | return fmt.Errorf("disconnect request failed (not expected return type)")
324 | }
325 |
326 | return nil
327 | }
328 |
329 | // ConnectVPN - establish new VPN connection
330 | func (c *Client) ConnectVPN(req types.Connect) (types.ConnectedResp, error) {
331 | respConnected := types.ConnectedResp{}
332 | respDisconnected := types.DisconnectedResp{}
333 |
334 | if err := c.ensureConnected(); err != nil {
335 | return respConnected, err
336 | }
337 |
338 | _, _, err := c.sendRecvAny(&req, &respConnected, &respDisconnected)
339 | if err != nil {
340 | return respConnected, err
341 | }
342 |
343 | if len(respConnected.Command) > 0 {
344 | return respConnected, nil
345 | }
346 |
347 | if len(respDisconnected.Command) > 0 {
348 | return respConnected, fmt.Errorf("%s", respDisconnected.ReasonDescription)
349 | }
350 |
351 | return respConnected, fmt.Errorf("connect request failed (not expected return type)")
352 | }
353 |
354 | // WGKeysGenerate regenerate WG keys
355 | func (c *Client) WGKeysGenerate() error {
356 | if err := c.ensureConnected(); err != nil {
357 | return err
358 | }
359 |
360 | req := types.WireGuardGenerateNewKeys{}
361 | var resp types.EmptyResp
362 | if err := c.sendRecv(&req, &resp); err != nil {
363 | return err
364 | }
365 |
366 | return nil
367 | }
368 |
369 | // WGKeysRotationInterval changes WG keys rotation interval
370 | func (c *Client) WGKeysRotationInterval(uinxTimeInterval int64) error {
371 | if err := c.ensureConnected(); err != nil {
372 | return err
373 | }
374 |
375 | req := types.WireGuardSetKeysRotationInterval{Interval: uinxTimeInterval}
376 | var resp types.EmptyResp
377 | if err := c.sendRecv(&req, &resp); err != nil {
378 | return err
379 | }
380 |
381 | return nil
382 | }
383 |
384 | // PingServers changes WG keys rotation interval
385 | func (c *Client) PingServers() (pingResults []types.PingResultType, err error) {
386 | if err := c.ensureConnected(); err != nil {
387 | return pingResults, err
388 | }
389 |
390 | req := types.PingServers{RetryCount: 4, TimeOutMs: 6000}
391 | var resp types.PingServersResp
392 | if err := c.sendRecv(&req, &resp); err != nil {
393 | return pingResults, err
394 | }
395 |
396 | return resp.PingResults, nil
397 | }
398 |
399 | // SetManualDNS - sets manual DNS for current VPN connection
400 | func (c *Client) SetManualDNS(dns string) error {
401 | if err := c.ensureConnected(); err != nil {
402 | return err
403 | }
404 |
405 | req := types.SetAlternateDns{DNS: dns}
406 | var resp types.SetAlternateDNSResp
407 | if err := c.sendRecv(&req, &resp); err != nil {
408 | return err
409 | }
410 |
411 | if resp.IsSuccess == false {
412 | return fmt.Errorf("DNS not changed")
413 | }
414 |
415 | return nil
416 | }
417 |
--------------------------------------------------------------------------------
/protocol/client_private.go:
--------------------------------------------------------------------------------
1 | //
2 | // IVPN command line interface (CLI)
3 | // https://github.com/ivpn/desktop-app-cli
4 | //
5 | // Created by Stelnykovych Alexandr.
6 | // Copyright (c) 2020 Privatus Limited.
7 | //
8 | // This file is part of the IVPN command line interface.
9 | //
10 | // The IVPN command line interface is free software: you can redistribute it and/or
11 | // modify it under the terms of the GNU General Public License as published by the Free
12 | // Software Foundation, either version 3 of the License, or (at your option) any later version.
13 | //
14 | // The IVPN command line interface is distributed in the hope that it will be useful,
15 | // but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16 | // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
17 | // details.
18 | //
19 | // You should have received a copy of the GNU General Public License
20 | // along with the IVPN command line interface. If not, see .
21 | //
22 |
23 | package protocol
24 |
25 | import (
26 | "bufio"
27 | "fmt"
28 | "time"
29 |
30 | "github.com/ivpn/desktop-app-daemon/logger"
31 | "github.com/ivpn/desktop-app-daemon/protocol/types"
32 | )
33 |
34 | func (c *Client) ensureConnected() error {
35 | if c._conn != nil {
36 | return nil
37 | }
38 |
39 | // ensure we are connected
40 | if err := c.Connect(); err != nil {
41 | return err
42 | }
43 | return nil
44 | }
45 |
46 | func (c *Client) sendRecv(request interface{}, response interface{}) error {
47 | return c.sendRecvTimeOut(request, response, c._defaultTimeout)
48 | }
49 |
50 | func (c *Client) sendRecvTimeOut(request interface{}, response interface{}, timeout time.Duration) error {
51 | var receiver *receiverChannel
52 |
53 | // thread-safe receiver registration
54 | func() {
55 | c._receiversLocker.Lock()
56 | defer c._receiversLocker.Unlock()
57 |
58 | c._requestIdx++
59 | if c._requestIdx == 0 {
60 | c._requestIdx++
61 | }
62 |
63 | receiver = createReceiver(c._requestIdx, response)
64 |
65 | c._receivers[receiver] = struct{}{}
66 | }()
67 |
68 | // do not forget to remove receiver
69 | defer func() {
70 | c._receiversLocker.Lock()
71 | defer c._receiversLocker.Unlock()
72 |
73 | delete(c._receivers, receiver)
74 | }()
75 |
76 | // send request
77 | if err := c.send(request, receiver._waitingIdx); err != nil {
78 | return err
79 | }
80 |
81 | // waiting for response
82 | if err := receiver.Wait(timeout); err != nil {
83 | return fmt.Errorf("failed to receive response: %w", err)
84 | }
85 |
86 | return nil
87 | }
88 |
89 | func (c *Client) sendRecvAny(request interface{}, waitingObjects ...interface{}) (data []byte, cmdBase types.CommandBase, err error) {
90 | var receiver *receiverChannel
91 |
92 | var reqIdx int
93 | // thread-safe receiver registration
94 | func() {
95 | c._receiversLocker.Lock()
96 | defer c._receiversLocker.Unlock()
97 |
98 | c._requestIdx++
99 | reqIdx = c._requestIdx
100 | receiver = createReceiver(0, waitingObjects...)
101 |
102 | c._receivers[receiver] = struct{}{}
103 | }()
104 |
105 | // do not forget to remove receiver
106 | defer func() {
107 | c._receiversLocker.Lock()
108 | defer c._receiversLocker.Unlock()
109 |
110 | delete(c._receivers, receiver)
111 | }()
112 |
113 | // send request
114 | if err := c.send(request, reqIdx); err != nil {
115 | return nil, types.CommandBase{}, err
116 | }
117 |
118 | // waiting for response
119 | if err := receiver.Wait(c._defaultTimeout); err != nil {
120 | return nil, types.CommandBase{}, fmt.Errorf("failed to receive response: %w", err)
121 | }
122 |
123 | data, cmdBase = receiver.GetReceivedRawData()
124 | return data, cmdBase, nil
125 | }
126 |
127 | func (c *Client) send(cmd interface{}, requestIdx int) error {
128 | cmdName := types.GetTypeName(cmd)
129 |
130 | logger.Info("--> ", cmdName)
131 | if err := types.Send(c._conn, cmd, requestIdx); err != nil {
132 | return fmt.Errorf("failed to send command '%s': %w", cmdName, err)
133 | }
134 | return nil
135 | }
136 |
137 | func (c *Client) receiverRoutine() {
138 |
139 | defer func() {
140 | logger.Info("Receiver stopped")
141 | c._conn.Close()
142 | }()
143 |
144 | logger.Info("Receiver started")
145 |
146 | reader := bufio.NewReader(c._conn)
147 |
148 | // run loop forever
149 | for {
150 | // will listen for message to process ending in newline (\n)
151 | message, err := reader.ReadString('\n')
152 | if err != nil {
153 | logger.Error("Error receiving data from daemon: ", err)
154 | break
155 | }
156 |
157 | messageData := []byte(message)
158 |
159 | cmd, err := types.GetCommandBase(messageData)
160 | if err != nil {
161 | logger.Error("Failed to parse response:", err)
162 | return
163 | }
164 |
165 | logger.Info("<-- ", cmd.Command)
166 |
167 | isProcessed := false
168 | // thread-safe iteration trough receivers
169 | func() {
170 | c._receiversLocker.Lock()
171 | defer c._receiversLocker.Unlock()
172 |
173 | for receiver := range c._receivers {
174 | if receiver.IsExpectedResponse(cmd.Idx, cmd.Command) {
175 | isProcessed = true
176 | receiver.PushResponse(messageData)
177 | break
178 | }
179 | }
180 | }()
181 |
182 | if isProcessed == false {
183 | logger.Info(fmt.Sprintf("Response '%s:%d' not processed", cmd.Command, cmd.Idx))
184 | }
185 | }
186 | }
187 |
--------------------------------------------------------------------------------
/protocol/receiver_channel.go:
--------------------------------------------------------------------------------
1 | //
2 | // IVPN command line interface (CLI)
3 | // https://github.com/ivpn/desktop-app-cli
4 | //
5 | // Created by Stelnykovych Alexandr.
6 | // Copyright (c) 2020 Privatus Limited.
7 | //
8 | // This file is part of the IVPN command line interface.
9 | //
10 | // The IVPN command line interface is free software: you can redistribute it and/or
11 | // modify it under the terms of the GNU General Public License as published by the Free
12 | // Software Foundation, either version 3 of the License, or (at your option) any later version.
13 | //
14 | // The IVPN command line interface is distributed in the hope that it will be useful,
15 | // but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16 | // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
17 | // details.
18 | //
19 | // You should have received a copy of the GNU General Public License
20 | // along with the IVPN command line interface. If not, see .
21 | //
22 |
23 | package protocol
24 |
25 | import (
26 | "encoding/json"
27 | "fmt"
28 | "time"
29 |
30 | "github.com/ivpn/desktop-app-daemon/logger"
31 | "github.com/ivpn/desktop-app-daemon/protocol/types"
32 | )
33 |
34 | func createReceiver(waitingIdx int, waitingObjectsList ...interface{}) *receiverChannel {
35 | waitingObjects := make(map[string]interface{})
36 |
37 | for _, wo := range waitingObjectsList {
38 | if wo == nil {
39 | continue
40 | }
41 | waitingType := types.GetTypeName(wo)
42 | waitingObjects[waitingType] = wo
43 | }
44 |
45 | receiver := &receiverChannel{
46 | _waitingIdx: waitingIdx,
47 | _waitingObjects: waitingObjects,
48 | _channel: make(chan []byte, 1)}
49 |
50 | return receiver
51 | }
52 |
53 | type receiverChannel struct {
54 | _waitingIdx int
55 | _waitingObjects map[string]interface{}
56 | _channel chan []byte
57 | _receivedData []byte
58 | _receivedCmdBase types.CommandBase
59 | }
60 |
61 | func (r *receiverChannel) GetReceivedRawData() (data []byte, cmdBaseObj types.CommandBase) {
62 | return r._receivedData, r._receivedCmdBase
63 | }
64 |
65 | func (r *receiverChannel) IsExpectedResponse(respIdx int, command string) bool {
66 | // response is acceptable when:
67 | // - received expected responseIndex
68 | // - we are not waiting for response index but received one of responses from _waitingObjects
69 | // - when we do not care about responseIndex and response objects
70 |
71 | if r._waitingIdx == 0 && len(r._waitingObjects) == 0 {
72 | return true
73 | }
74 |
75 | if r._waitingIdx != 0 {
76 | if r._waitingIdx == respIdx {
77 | return true
78 | }
79 | } else {
80 | if len(r._waitingObjects) > 0 {
81 | if _, ok := r._waitingObjects[command]; ok {
82 | return true
83 | }
84 | }
85 | }
86 |
87 | return false
88 | }
89 |
90 | func (r *receiverChannel) PushResponse(responseData []byte) {
91 | select {
92 | case r._channel <- responseData:
93 | default:
94 | logger.Error("Receiver channel is full")
95 | }
96 | }
97 |
98 | func (r *receiverChannel) Wait(timeout time.Duration) (err error) {
99 | select {
100 | case r._receivedData = <-r._channel:
101 |
102 | // check type of response
103 | if err := deserialize(r._receivedData, &r._receivedCmdBase); err != nil {
104 | return fmt.Errorf("response deserialisation failed: %w", err)
105 | }
106 |
107 | if len(r._waitingObjects) > 0 {
108 | if wo, ok := r._waitingObjects[r._receivedCmdBase.Command]; ok {
109 | // deserialize response into expected object type
110 | if err := deserialize(r._receivedData, wo); err != nil {
111 | return fmt.Errorf("response deserialisation failed: %w", err)
112 | }
113 | } else {
114 | // check is it Error object
115 | var errObj types.ErrorResp
116 | if r._receivedCmdBase.Command == types.GetTypeName(errObj) {
117 | if err := deserialize(r._receivedData, &errObj); err != nil {
118 | return fmt.Errorf("response deserialisation failed: %w", err)
119 | }
120 | return fmt.Errorf(errObj.ErrorMessage)
121 | }
122 | return fmt.Errorf("received unexpected data (type:%s)", r._receivedCmdBase.Command)
123 | }
124 | }
125 | return nil
126 |
127 | case <-time.After(timeout):
128 | return ResponseTimeout{}
129 | }
130 | }
131 |
132 | func deserialize(messageData []byte, obj interface{}) error {
133 | if err := json.Unmarshal(messageData, obj); err != nil {
134 | return fmt.Errorf("failed to parse command data: %w", err)
135 | }
136 | return nil
137 | }
138 |
--------------------------------------------------------------------------------