├── .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 | --------------------------------------------------------------------------------