initial commit
This commit is contained in:
commit
c4b259dae3
|
@ -0,0 +1 @@
|
|||
.idea/
|
|
@ -0,0 +1,6 @@
|
|||
[submodule "core/vendor/pac-cmd"]
|
||||
path = core/vendor/pac-cmd
|
||||
url = https://github.com/getlantern/pac-cmd
|
||||
[submodule "core/vendor/sysproxy-cmd"]
|
||||
path = core/vendor/sysproxy-cmd
|
||||
url = https://github.com/getlantern/sysproxy-cmd
|
|
@ -0,0 +1,12 @@
|
|||
# 更新日志
|
||||
|
||||
## 0.1.0 (2018-04-03)
|
||||
|
||||
### :rocket: 特性
|
||||
|
||||
- 三大平台支持(Windows、Linux、macOS)
|
||||
- 双端图形化界面
|
||||
- 单机服务多开
|
||||
- 远程服务配置、启动/停止
|
||||
- 实时监控图表(CPU、内存、上下行速度、网络连接数、网络流量)
|
||||
- 日志查看和搜索
|
|
@ -0,0 +1,201 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2018 Micooz
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
|
@ -0,0 +1,148 @@
|
|||
# blinksocks-gui
|
||||
|
||||
为 [blinksocks](https://github.com/blinksocks/blinksocks) 封装的 WEB 图形化界面。
|
||||
|
||||
![](screenshot.png)
|
||||
|
||||
## 在线体验(只读模式)
|
||||
|
||||
https://gui.blinksocks.org/landing?password=preview
|
||||
|
||||
## 特性
|
||||
|
||||
- 三大平台支持(Windows、Linux、macOS)
|
||||
- 双端图形化界面
|
||||
- 单机服务多开
|
||||
- 远程服务配置、启动/停止
|
||||
- 实时监控图表(CPU、内存、上下行速度、网络连接数、网络流量)
|
||||
- 日志查看和搜索
|
||||
|
||||
## 安装
|
||||
|
||||
### 使用 npm 安装或升级(推荐)
|
||||
|
||||
在此之前,请先安装 [Node.js](https://nodejs.org/en/),Node.js 自带 npm 包管理器。
|
||||
|
||||
> Tips: 如果你是在服务端(一般是 Linux)上使用,可以使用官方提供的安装脚本:
|
||||
> https://nodejs.org/en/download/package-manager/#installing-node-js-via-package-manager
|
||||
|
||||
然后执行下面的命令安装 blinksocks-gui:
|
||||
|
||||
```
|
||||
$ npm install -g blinksocks-gui
|
||||
```
|
||||
|
||||
需要升级时重新执行上面的命令即可。
|
||||
|
||||
### 使用预编译版本
|
||||
|
||||
> 使用预编译版本无需安装 Node.js 和其他依赖软件,但升级时必须重新下载、解压和替换整个软件包。
|
||||
|
||||
下载地址:https://github.com/blinksocks/blinksocks-gui/releases
|
||||
|
||||
## 启动
|
||||
|
||||
### 交互式启动
|
||||
|
||||
桌面环境双击直接运行,服务器环境从命令行启动:
|
||||
|
||||
```
|
||||
$ blinksocks-gui
|
||||
```
|
||||
|
||||
根据提示选择启动类型(客户端或者服务端):
|
||||
|
||||
```
|
||||
? Please choose run type › - Use arrow-keys. Return to submit.
|
||||
❯ Client
|
||||
Server
|
||||
```
|
||||
|
||||
选择一个端口号用于远程访问图形界面:
|
||||
|
||||
```
|
||||
✔ Please choose run type › Client
|
||||
? Please choose a port(1 ~ 65535) for web ui: › 3000
|
||||
```
|
||||
|
||||
完成后在**浏览器**中打开提示链接即可:
|
||||
|
||||
```
|
||||
✔ Please choose run type › Client
|
||||
✔ Please choose a port(1 ~ 65535) for web ui: … 3000
|
||||
info: blinksocks gui client is running at 3000.
|
||||
info: You can now open blinksocks-gui in browser:
|
||||
|
||||
http://localhost:3000/
|
||||
|
||||
```
|
||||
|
||||
## 命令行启动
|
||||
|
||||
```
|
||||
$ blinksocks-gui --client --port 3000
|
||||
```
|
||||
|
||||
> Tips: 第一次启动时,程序会自动创建一个 `root` 用户,初始密码为 `root`。在 `/landing` 页面输入初始密码后登录系统。
|
||||
|
||||
## 修改初始登录密码
|
||||
|
||||
转到 `/settings` 页面或点击左侧 `Settings` 菜单进入系统配置面板修改相关配置并保存。
|
||||
|
||||
## 开发指引
|
||||
|
||||
### 拉取仓库并初始化
|
||||
|
||||
```
|
||||
$ git clone https://github.com/blinksocks/blinksocks-gui
|
||||
$ cd blinksocks-gui
|
||||
$ git submodule update --init
|
||||
```
|
||||
|
||||
### 安装依赖
|
||||
|
||||
```
|
||||
$ cd core && npm install
|
||||
$ cd ui && npm install
|
||||
```
|
||||
|
||||
### 启动调试
|
||||
|
||||
启动本地 HTTP/WebSocket 服务:
|
||||
|
||||
```
|
||||
$ cd core && npm run start:client
|
||||
```
|
||||
|
||||
启动前端开发服务器:
|
||||
|
||||
```
|
||||
$ cd ui && npm start
|
||||
```
|
||||
|
||||
根据提示打开链接开始调试。
|
||||
|
||||
### 编译和打包
|
||||
|
||||
只需要编译打包前端代码,完成后会自动替换 `core/public` 里的内容:
|
||||
|
||||
```
|
||||
$ cd ui && npm run build
|
||||
```
|
||||
|
||||
### 发布
|
||||
|
||||
只需发布 `core/` 里的内容到 npm registry 即可:
|
||||
|
||||
```
|
||||
$ cd core
|
||||
$ npm publish
|
||||
```
|
||||
|
||||
## 更新日志
|
||||
|
||||
[CHANGELOG.md](CHANGELOG.md)
|
||||
|
||||
## License
|
||||
|
||||
Apache License 2.0
|
|
@ -0,0 +1,26 @@
|
|||
# See https://help.github.com/ignore-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# pkg
|
||||
pkg/blinksocks-*.gz
|
||||
pkg/sha256sum.txt
|
||||
|
||||
# runtime
|
||||
/runtime
|
||||
|
||||
*.log*
|
|
@ -0,0 +1 @@
|
|||
module.exports = require('../src/main');
|
|
@ -0,0 +1,108 @@
|
|||
#!/usr/bin/env node
|
||||
const os = require('os');
|
||||
const prompts = require('prompts');
|
||||
const chalk = require('chalk');
|
||||
const bootstrap = require('./bootstrap');
|
||||
const version = require('../package.json').version;
|
||||
|
||||
const RUN_TYPE_CLIENT = 0;
|
||||
const RUN_TYPE_SERVER = 1;
|
||||
|
||||
const examples = [
|
||||
['Start web ui interactively', '$ blinksocks-gui'],
|
||||
['Start web ui at 3000 as client', '$ blinksocks-gui --client --port 3000'],
|
||||
];
|
||||
|
||||
const usage = `
|
||||
${chalk.bold.underline(`blinksocks-gui v${version}`)}
|
||||
|
||||
Usage: blinksocks-gui [options] ...
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help output usage information
|
||||
-v, --version output blinksocks-gui version
|
||||
-c, --client start web ui as client
|
||||
-s, --server start web ui as server
|
||||
-p, --port web ui listening port
|
||||
|
||||
Examples:
|
||||
|
||||
${examples.map(([description, example]) => ` ${chalk.gray('-')} ${description}${os.EOL} ${chalk.blue(example)}`).join(os.EOL)}
|
||||
|
||||
About & Help: ${chalk.underline('https://github.com/blinksocks/blinksocks-gui')}
|
||||
`;
|
||||
|
||||
const argv = process.argv;
|
||||
const options = argv.slice(2);
|
||||
|
||||
function hasOption(opt) {
|
||||
return options.indexOf(opt) !== -1;
|
||||
}
|
||||
|
||||
function getOptionValue(opt) {
|
||||
const index = options.indexOf(opt);
|
||||
if (index !== -1) {
|
||||
return options[index + 1];
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
if (argv.length < 2) {
|
||||
return console.log(usage);
|
||||
}
|
||||
|
||||
// parse options
|
||||
|
||||
if (hasOption('-h') || hasOption('--help')) {
|
||||
return console.log(usage);
|
||||
}
|
||||
|
||||
if (hasOption('-v') || hasOption('--version')) {
|
||||
return console.log(version);
|
||||
}
|
||||
|
||||
let runType, port;
|
||||
|
||||
// ask for runType when necessary
|
||||
if (hasOption('-c') || hasOption('--client')) {
|
||||
runType = RUN_TYPE_CLIENT;
|
||||
}
|
||||
if (hasOption('-s') || hasOption('--server')) {
|
||||
runType = RUN_TYPE_SERVER;
|
||||
}
|
||||
|
||||
if (typeof runType === 'undefined') {
|
||||
const answer = await prompts({
|
||||
type: 'select',
|
||||
name: 'value',
|
||||
message: 'Please choose run type',
|
||||
choices: [
|
||||
{ title: 'Client', value: RUN_TYPE_CLIENT },
|
||||
{ title: 'Server', value: RUN_TYPE_SERVER },
|
||||
],
|
||||
initial: 0,
|
||||
});
|
||||
runType = answer.value;
|
||||
}
|
||||
|
||||
// ask for port when necessary
|
||||
port = getOptionValue('-p') || getOptionValue('--port');
|
||||
|
||||
if (!port || port === '0') {
|
||||
const answer = await prompts({
|
||||
type: 'number',
|
||||
name: 'value',
|
||||
message: 'Please choose a port(1 ~ 65535) for web ui:',
|
||||
initial: 3000,
|
||||
style: 'default',
|
||||
min: 1,
|
||||
max: 65535,
|
||||
});
|
||||
port = answer.value;
|
||||
}
|
||||
|
||||
bootstrap({ runType, port });
|
||||
}
|
||||
|
||||
main();
|
|
@ -0,0 +1,71 @@
|
|||
{
|
||||
"name": "blinksocks-gui",
|
||||
"version": "0.1.0",
|
||||
"description": "A web based GUI wrapper for blinksocks",
|
||||
"author": "Micooz",
|
||||
"files": [
|
||||
"bin",
|
||||
"src",
|
||||
"public"
|
||||
],
|
||||
"bin": {
|
||||
"blinksocks-gui": "bin/start.js"
|
||||
},
|
||||
"scripts": {
|
||||
"start:client": "cross-env NODE_ENV=development nodemon bin/start.js --client --port 3000",
|
||||
"start:server": "cross-env NODE_ENV=development nodemon bin/start.js --server --port 3000",
|
||||
"debug:client": "cross-env NODE_ENV=development node --inspect --inspect-port=9400 bin/start.js --client --port 3000",
|
||||
"debug:server": "cross-env NODE_ENV=development node --inspect --inspect-port=9401 bin/start.js --server --port 3000",
|
||||
"prepkg": "rimraf pkg/blinksocks-gui-* pkg/sha256sum.txt",
|
||||
"pkg": "pkg --out-path pkg/ --targets node8.9.0-linux-x64,node8.9.0-macos-x64,node8.9.0-win-x64 .",
|
||||
"postpkg": "node pkg/postpkg.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"blinksocks": "^3.1.0",
|
||||
"chalk": "^2.3.2",
|
||||
"date-fns": "^1.29.0",
|
||||
"fs-extra": "^5.0.0",
|
||||
"jssha": "^2.3.1",
|
||||
"koa": "^2.5.0",
|
||||
"koa-bodyparser": "^4.2.0",
|
||||
"koa-favicon": "^2.0.1",
|
||||
"koa-router": "^7.4.0",
|
||||
"koa-static-cache": "^5.1.2",
|
||||
"lodash": "^4.17.5",
|
||||
"lodash-id": "^0.14.0",
|
||||
"lowdb": "^1.0.0",
|
||||
"pidusage": "^2.0.6",
|
||||
"prompts": "^0.1.8",
|
||||
"socket.io": "^2.1.0",
|
||||
"sudo-prompt": "^8.2.0",
|
||||
"winston": "^2.4.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"cross-env": "^5.1.4",
|
||||
"nodemon": "^1.17.3",
|
||||
"pkg": "^4.3.1",
|
||||
"rimraf": "^2.6.2"
|
||||
},
|
||||
"nodemonConfig": {
|
||||
"ignore": [
|
||||
"test/*",
|
||||
"docs/*",
|
||||
"vendor/*",
|
||||
"public/*",
|
||||
"runtime/*"
|
||||
]
|
||||
},
|
||||
"pkg": {
|
||||
"assets": [
|
||||
"public/*",
|
||||
"vendor/pac-cmd/binaries/**/*",
|
||||
"vendor/sysproxy-cmd/binaries/**/*"
|
||||
],
|
||||
"scripts": [
|
||||
"src/utils/_fork.js",
|
||||
"src/lives/*",
|
||||
"src/methods/*"
|
||||
]
|
||||
},
|
||||
"license": "MIT"
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
const crypto = require('crypto');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const utils = require('util');
|
||||
const zlib = require('zlib');
|
||||
|
||||
const { version } = require('../package.json');
|
||||
|
||||
const readdir = utils.promisify(fs.readdir);
|
||||
const remove = utils.promisify(fs.unlink);
|
||||
const appendFile = utils.promisify(fs.appendFile);
|
||||
const readFile = utils.promisify(fs.readFile);
|
||||
|
||||
async function sha256sum(file) {
|
||||
const sha256 = crypto.createHash('sha256');
|
||||
const input = await readFile(file);
|
||||
return sha256.update(input).digest('hex');
|
||||
}
|
||||
|
||||
(async function main() {
|
||||
try {
|
||||
let files = await readdir(path.resolve(__dirname));
|
||||
files = files.filter((f) => f.startsWith('blinksocks-gui-'));
|
||||
for (const file of files) {
|
||||
const name = path.basename(file, '.exe');
|
||||
const ext = path.extname(file);
|
||||
const newName = `${name}-x64-v${version}${ext}.gz`;
|
||||
|
||||
const input = path.join(__dirname, file);
|
||||
const output = path.join(__dirname, newName);
|
||||
const hashFile = path.join(__dirname, 'sha256sum.txt');
|
||||
|
||||
// compress into .gz
|
||||
const stream = fs.createReadStream(input).pipe(zlib.createGzip()).pipe(fs.createWriteStream(output));
|
||||
|
||||
stream.on('finish', async () => {
|
||||
// calc sha256sum
|
||||
await appendFile(hashFile, `${path.basename(output)} ${await sha256sum(output)}\n`);
|
||||
// remove original
|
||||
await remove(input);
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err.message);
|
||||
}
|
||||
})();
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"main.css": "static/css/main.6ff87aea.css",
|
||||
"main.js": "static/js/main.d43ae99d.js",
|
||||
"static/media/icons-16.eot": "static/media/icons-16.2c8962a6.eot",
|
||||
"static/media/icons-16.ttf": "static/media/icons-16.717d9aac.ttf",
|
||||
"static/media/icons-16.woff": "static/media/icons-16.d4e66df1.woff",
|
||||
"static/media/icons-20.eot": "static/media/icons-20.8eb14aee.eot",
|
||||
"static/media/icons-20.ttf": "static/media/icons-20.f9eec517.ttf",
|
||||
"static/media/icons-20.woff": "static/media/icons-20.5d6d0525.woff"
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 66 KiB |
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 833 KiB |
|
@ -0,0 +1 @@
|
|||
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"><meta name="theme-color" content="#000000"><link rel="manifest" href="/manifest.json"><link rel="shortcut icon" href="/favicon.ico"><title>blinksocks-gui</title><link href="/static/css/main.6ff87aea.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div><script type="text/javascript" src="/static/js/main.d43ae99d.js"></script></body></html>
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"short_name": "blinksocks gui",
|
||||
"name": "friendly gui management tools for blinksocks",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
}
|
||||
],
|
||||
"start_url": "./index.html",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
"use strict";var precacheConfig=[["/index.html","1836fa0b8441b5341481371e418ce0ef"],["/static/css/main.6ff87aea.css","6ff87aea65bc52b9faaf9ffd641e2cb0"],["/static/js/main.d43ae99d.js","fb1c108bf08f1b01237f028c44e0db3c"],["/static/media/icons-16.2c8962a6.eot","2c8962a6b93ca1f31585c107526f724b"],["/static/media/icons-16.717d9aac.ttf","717d9aacb221362a32e46507340c673e"],["/static/media/icons-16.d4e66df1.woff","d4e66df1333255a372f320437481b2e0"],["/static/media/icons-20.5d6d0525.woff","5d6d0525f61413e1c5b34cf379f234d4"],["/static/media/icons-20.8eb14aee.eot","8eb14aee4b53af31571001acc9161483"],["/static/media/icons-20.f9eec517.ttf","f9eec51722361fb5ef0faec14405391f"]],cacheName="sw-precache-v3-sw-precache-webpack-plugin-"+(self.registration?self.registration.scope:""),ignoreUrlParametersMatching=[/^utm_/],addDirectoryIndex=function(e,t){var n=new URL(e);return"/"===n.pathname.slice(-1)&&(n.pathname+=t),n.toString()},cleanResponse=function(t){return t.redirected?("body"in t?Promise.resolve(t.body):t.blob()).then(function(e){return new Response(e,{headers:t.headers,status:t.status,statusText:t.statusText})}):Promise.resolve(t)},createCacheKey=function(e,t,n,r){var a=new URL(e);return r&&a.pathname.match(r)||(a.search+=(a.search?"&":"")+encodeURIComponent(t)+"="+encodeURIComponent(n)),a.toString()},isPathWhitelisted=function(e,t){if(0===e.length)return!0;var n=new URL(t).pathname;return e.some(function(e){return n.match(e)})},stripIgnoredUrlParameters=function(e,n){var t=new URL(e);return t.hash="",t.search=t.search.slice(1).split("&").map(function(e){return e.split("=")}).filter(function(t){return n.every(function(e){return!e.test(t[0])})}).map(function(e){return e.join("=")}).join("&"),t.toString()},hashParamName="_sw-precache",urlsToCacheKeys=new Map(precacheConfig.map(function(e){var t=e[0],n=e[1],r=new URL(t,self.location),a=createCacheKey(r,hashParamName,n,/\.\w{8}\./);return[r.toString(),a]}));function setOfCachedUrls(e){return e.keys().then(function(e){return e.map(function(e){return e.url})}).then(function(e){return new Set(e)})}self.addEventListener("install",function(e){e.waitUntil(caches.open(cacheName).then(function(r){return setOfCachedUrls(r).then(function(n){return Promise.all(Array.from(urlsToCacheKeys.values()).map(function(t){if(!n.has(t)){var e=new Request(t,{credentials:"same-origin"});return fetch(e).then(function(e){if(!e.ok)throw new Error("Request for "+t+" returned a response with status "+e.status);return cleanResponse(e).then(function(e){return r.put(t,e)})})}}))})}).then(function(){return self.skipWaiting()}))}),self.addEventListener("activate",function(e){var n=new Set(urlsToCacheKeys.values());e.waitUntil(caches.open(cacheName).then(function(t){return t.keys().then(function(e){return Promise.all(e.map(function(e){if(!n.has(e.url))return t.delete(e)}))})}).then(function(){return self.clients.claim()}))}),self.addEventListener("fetch",function(t){if("GET"===t.request.method){var e,n=stripIgnoredUrlParameters(t.request.url,ignoreUrlParametersMatching),r="index.html";(e=urlsToCacheKeys.has(n))||(n=addDirectoryIndex(n,r),e=urlsToCacheKeys.has(n));0,e&&t.respondWith(caches.open(cacheName).then(function(e){return e.match(urlsToCacheKeys.get(n)).then(function(e){if(e)return e;throw Error("The cached response that was expected is missing.")})}).catch(function(e){return console.warn('Couldn\'t serve response for "%s" from cache: %O',t.request.url,e),fetch(t.request)}))}});
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,20 @@
|
|||
const path = require('path');
|
||||
const cwd = process.cwd();
|
||||
|
||||
exports.DATABASE_PATH = path.join(cwd, 'runtime/db.json');
|
||||
exports.RUNTIME_HELPERS_PAC_PATH = path.join(cwd, 'runtime/helpers/pac');
|
||||
exports.RUNTIME_HELPERS_SYSPROXY_PATH = path.join(cwd, 'runtime/helpers/sysproxy');
|
||||
|
||||
exports.HASH_SALT = 'blinksocks';
|
||||
exports.DESENSITIZE_PLACEHOLDER = '********';
|
||||
|
||||
exports.RUN_TYPE_CLIENT = 0;
|
||||
exports.RUN_TYPE_SERVER = 1;
|
||||
|
||||
exports.SERVER_PUSH_REGISTER_SUCCESS = 0;
|
||||
exports.SERVER_PUSH_REGISTER_ERROR = 1;
|
||||
exports.SERVER_PUSH_DISPOSE_TIMEOUT = 6e4; // 1min
|
||||
|
||||
exports.SERVICE_STATUS_INIT = -1;
|
||||
exports.SERVICE_STATUS_RUNNING = 0;
|
||||
exports.SERVICE_STATUS_STOPPED = 1;
|
|
@ -0,0 +1,28 @@
|
|||
const path = require('path');
|
||||
const { import_dir } = require('../utils');
|
||||
|
||||
const methods = import_dir(path.join(__dirname, '..', 'methods'));
|
||||
|
||||
async function dispatch(method, args, extra) {
|
||||
const func = methods[method];
|
||||
if (typeof func === 'function') {
|
||||
// check methods
|
||||
if (this.getDisallowedMethods().includes(method)) {
|
||||
throw Error(`you don't have privileges to call: "${method}"`);
|
||||
}
|
||||
const result = await func.call(this, args || {}, extra || {});
|
||||
if (typeof result === 'undefined') {
|
||||
return null;
|
||||
} else if (result instanceof Promise) {
|
||||
return await result;
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
} else {
|
||||
throw Error(`method "${method}" is not implemented or not registered`);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
dispatch,
|
||||
};
|
|
@ -0,0 +1,158 @@
|
|||
const path = require('path');
|
||||
const http = require('http');
|
||||
const Koa = require('koa');
|
||||
const KoaRouter = require('koa-router');
|
||||
const staticCache = require('koa-static-cache');
|
||||
const favicon = require('koa-favicon');
|
||||
const bodyParser = require('koa-bodyparser');
|
||||
const _ = require('lodash');
|
||||
|
||||
const {
|
||||
RUN_TYPE_CLIENT,
|
||||
RUN_TYPE_SERVER,
|
||||
HASH_SALT,
|
||||
} = require('../constants');
|
||||
|
||||
const Router = require('./router');
|
||||
const { hash, logger, db, import_dir } = require('../utils');
|
||||
|
||||
const ALL_METHODS = Object.assign(
|
||||
{},
|
||||
import_dir(path.resolve(__dirname, '../methods')),
|
||||
import_dir(path.resolve(__dirname, '../lives')),
|
||||
);
|
||||
|
||||
function onConnection(socket, { runType }) {
|
||||
const { handshake } = socket;
|
||||
const { user } = handshake;
|
||||
logger.verbose(`[${handshake.address}] connected`);
|
||||
|
||||
function extendDB(db) {
|
||||
db.getConfigs = () => {
|
||||
const key = {
|
||||
[RUN_TYPE_CLIENT]: 'client_configs',
|
||||
[RUN_TYPE_SERVER]: 'server_configs',
|
||||
}[runType];
|
||||
return db.get(key);
|
||||
};
|
||||
return db;
|
||||
}
|
||||
|
||||
const thisArg = {
|
||||
ctx: {
|
||||
runType,
|
||||
// push_handlers is used for _xxx_server_push().
|
||||
push_handlers: {},
|
||||
},
|
||||
user: user || null,
|
||||
db: extendDB(db),
|
||||
getConfigurableMethods() {
|
||||
const methods = _.transform(ALL_METHODS, (result, _, key) => result.push(key), []);
|
||||
return methods.filter((name) => name[0] !== '_');
|
||||
},
|
||||
getDisallowedMethods() {
|
||||
return user['disallowed_methods'] || [];
|
||||
},
|
||||
push(event, data) {
|
||||
logger.info(`[${handshake.address}] [PUSH] ${JSON.stringify(data)}`);
|
||||
socket.emit(event, data);
|
||||
},
|
||||
invoke(method, args, extra) {
|
||||
return Router.dispatch.call(thisArg, method, args, extra);
|
||||
},
|
||||
};
|
||||
|
||||
// handle client requests
|
||||
socket.on('request', async function (req, send) {
|
||||
const reqStr = JSON.stringify(req);
|
||||
logger.info(`[${handshake.address}] request => ${reqStr}`);
|
||||
const { method, args } = req;
|
||||
try {
|
||||
const result = await Router.dispatch.call(thisArg, method, args);
|
||||
const response = { code: 0 };
|
||||
if (result !== null) {
|
||||
response.data = result;
|
||||
}
|
||||
logger.info(`[${handshake.address}] response => ${JSON.stringify(response)}`);
|
||||
send(response);
|
||||
} catch (err) {
|
||||
logger.error(`[${handshake.address}] cannot process the request: ${reqStr}`, err);
|
||||
send({ code: -1, message: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('disconnect', async function () {
|
||||
logger.verbose(`[${handshake.address}] disconnected`);
|
||||
try {
|
||||
const { push_handlers } = thisArg.ctx;
|
||||
for (const key of Object.keys(push_handlers)) {
|
||||
await push_handlers[key].dispose();
|
||||
}
|
||||
} catch (err) {
|
||||
// ignore any errors
|
||||
// console.log(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function createWrappedUsers() {
|
||||
return db.get('users').value()
|
||||
.map((user) => Object.assign({
|
||||
token: hash('SHA-256', user.password + HASH_SALT),
|
||||
}, user));
|
||||
}
|
||||
|
||||
module.exports = async function startServer(args) {
|
||||
const { runType, port } = args;
|
||||
|
||||
// start koa server
|
||||
const app = new Koa();
|
||||
const router = new KoaRouter();
|
||||
const server = http.createServer(app.callback());
|
||||
const io = require('socket.io')(server);
|
||||
|
||||
// ws authentication middleware
|
||||
io.use((socket, next) => {
|
||||
const { query: { token } } = socket.handshake;
|
||||
const user = createWrappedUsers().find((user) => user.token === token);
|
||||
if (user) {
|
||||
// NOTE: put user to socket.handshake so that
|
||||
// we can access it again in onConnection().
|
||||
socket.handshake.user = user;
|
||||
return next();
|
||||
}
|
||||
return next(new Error('authentication error'));
|
||||
});
|
||||
|
||||
// handle ws connections
|
||||
io.on('connection', (socket) => onConnection(socket, args));
|
||||
|
||||
// standalone http interface
|
||||
router.post('/verify', async (ctx) => {
|
||||
const { token } = ctx.request.body;
|
||||
if (!createWrappedUsers().find((user) => user.token === token)) {
|
||||
return ctx.throw(403, 'authentication error');
|
||||
}
|
||||
ctx.status = 200;
|
||||
});
|
||||
|
||||
app.use(favicon(path.join(__dirname, '../../public/favicon.ico')));
|
||||
app.use(staticCache(path.join(__dirname, '../../public'), {
|
||||
alias: {
|
||||
'/': '/index.html',
|
||||
'/landing': '/index.html',
|
||||
},
|
||||
}));
|
||||
app.use(bodyParser());
|
||||
app.use(router.routes());
|
||||
app.use(router.allowedMethods());
|
||||
|
||||
const _port = port || 3000;
|
||||
server.listen(_port, () => {
|
||||
logger.info(`blinksocks gui ${runType === RUN_TYPE_SERVER ? 'server' : 'client'} is running at ${_port}.`);
|
||||
logger.info('You can now open blinksocks-gui in browser:');
|
||||
console.log('');
|
||||
console.log(` http://localhost:${_port}/`);
|
||||
console.log('');
|
||||
});
|
||||
};
|
|
@ -0,0 +1,72 @@
|
|||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const utils = require('util');
|
||||
const readline = require('readline');
|
||||
const { tailFile: tail } = require('winston/lib/winston/common');
|
||||
|
||||
const readdir = utils.promisify(fs.readdir);
|
||||
const TAIL_FROM = 100;
|
||||
|
||||
async function getTotalLines(file) {
|
||||
let totalLines = 0;
|
||||
const reader = readline.createInterface({
|
||||
input: fs.createReadStream(file),
|
||||
});
|
||||
return new Promise((resolve) => {
|
||||
reader.on('line', () => totalLines += 1);
|
||||
reader.on('close', () => resolve(totalLines));
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = async function live_log({ id }) {
|
||||
const { log_path } = await this.invoke('get_config', { id });
|
||||
|
||||
const logFilePath = path.resolve(process.cwd(), log_path || 'blinksocks');
|
||||
const logFileName = path.basename(logFilePath);
|
||||
const logFileDir = path.dirname(logFilePath);
|
||||
|
||||
// find the most recently created log file
|
||||
const files = await readdir(logFileDir);
|
||||
const logFiles = files
|
||||
.filter(name => name.startsWith(logFileName))
|
||||
.sort()
|
||||
.map(name => path.join(logFileDir, name));
|
||||
|
||||
const logFile = logFiles[0] || '';
|
||||
|
||||
// count total lines
|
||||
let totalLines = 0;
|
||||
if (logFile) {
|
||||
totalLines = await getTotalLines(logFile);
|
||||
}
|
||||
|
||||
let firstTime = true;
|
||||
let counter = 0;
|
||||
let lines = [];
|
||||
|
||||
const start = totalLines > TAIL_FROM ? totalLines - TAIL_FROM - 1 : -1;
|
||||
const destroy = tail({ file: logFile, start: start }, (err, line) => {
|
||||
if (err) {
|
||||
return;
|
||||
}
|
||||
// instead of push many times at the first time,
|
||||
// we can make a cache here and do an one-time push.
|
||||
if (firstTime) {
|
||||
const end = start === -1 ? totalLines : TAIL_FROM;
|
||||
if (counter < end - 1) {
|
||||
lines.push(line);
|
||||
counter++;
|
||||
} else {
|
||||
firstTime = false;
|
||||
lines.push(line);
|
||||
this.push(lines);
|
||||
lines = null;
|
||||
}
|
||||
} else {
|
||||
this.push(line);
|
||||
}
|
||||
});
|
||||
return async function unregister() {
|
||||
destroy();
|
||||
};
|
||||
};
|
|
@ -0,0 +1,7 @@
|
|||
const { ServiceManager } = require('../utils');
|
||||
|
||||
module.exports = async function live_services() {
|
||||
this.pushInterval(async () => ({
|
||||
services: await ServiceManager.getServices()
|
||||
}), 5e3);
|
||||
};
|
|
@ -0,0 +1,12 @@
|
|||
const os = require('os');
|
||||
const pidusage = require('pidusage');
|
||||
|
||||
module.exports = async function live_usage() {
|
||||
this.pushInterval(async () => {
|
||||
const stats = await pidusage(process.pid);
|
||||
return {
|
||||
cpuUsage: stats.cpu / os.cpus().length,
|
||||
memoryUsage: stats.memory,
|
||||
};
|
||||
}, 5e3);
|
||||
};
|
|
@ -0,0 +1,110 @@
|
|||
const os = require('os');
|
||||
const fs = require('fs');
|
||||
const utils = require('util');
|
||||
const path = require('path');
|
||||
const fsExtra = require('fs-extra');
|
||||
const bsInit = require('blinksocks/bin/init');
|
||||
|
||||
const runServer = require('./core/server');
|
||||
|
||||
const {
|
||||
RUN_TYPE_CLIENT,
|
||||
RUN_TYPE_SERVER,
|
||||
RUNTIME_HELPERS_PAC_PATH,
|
||||
RUNTIME_HELPERS_SYSPROXY_PATH,
|
||||
} = require('./constants');
|
||||
|
||||
const { db } = require('./utils');
|
||||
|
||||
const chmod = utils.promisify(fs.chmod);
|
||||
|
||||
function getSysArch() {
|
||||
const arch = os.arch();
|
||||
switch (arch) {
|
||||
case'x32':
|
||||
return '386';
|
||||
case 'x64':
|
||||
return 'amd64';
|
||||
default:
|
||||
throw Error('unsupported architecture: ' + arch);
|
||||
}
|
||||
}
|
||||
|
||||
async function copy(source, target) {
|
||||
if (process.pkg) {
|
||||
// use stream pipe to reduce memory usage
|
||||
// when load a large file into memory.
|
||||
fs.createReadStream(source).pipe(fs.createWriteStream(target));
|
||||
} else {
|
||||
await fsExtra.copy(source, target);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractHelpers() {
|
||||
// copy system-related helper tools to runtime/helpers
|
||||
const platform = os.platform();
|
||||
const arch = getSysArch();
|
||||
|
||||
const pac_cmd_binaries_path = path.resolve(__dirname, '../vendor/pac-cmd/binaries');
|
||||
const sysproxy_binaries_path = path.resolve(__dirname, '../vendor/sysproxy-cmd/binaries');
|
||||
|
||||
switch (platform) {
|
||||
case 'darwin':
|
||||
await copy(path.join(pac_cmd_binaries_path, 'darwin/pac'), RUNTIME_HELPERS_PAC_PATH);
|
||||
await copy(path.join(sysproxy_binaries_path, 'darwin/sysproxy'), RUNTIME_HELPERS_SYSPROXY_PATH);
|
||||
break;
|
||||
case 'linux':
|
||||
await copy(path.join(pac_cmd_binaries_path, `linux_${arch}/pac`), RUNTIME_HELPERS_PAC_PATH);
|
||||
await copy(path.join(sysproxy_binaries_path, `linux_${arch}/sysproxy`), RUNTIME_HELPERS_SYSPROXY_PATH);
|
||||
break;
|
||||
case 'win32':
|
||||
await copy(path.join(pac_cmd_binaries_path, `windows/pac_${arch}`), RUNTIME_HELPERS_PAC_PATH);
|
||||
await copy(path.join(sysproxy_binaries_path, `windows/sysproxy_${arch}`), RUNTIME_HELPERS_SYSPROXY_PATH);
|
||||
break;
|
||||
default:
|
||||
throw Error('unsupported platform: ' + platform);
|
||||
}
|
||||
|
||||
// grant execute permission
|
||||
await chmod(RUNTIME_HELPERS_PAC_PATH, 0o774);
|
||||
await chmod(RUNTIME_HELPERS_SYSPROXY_PATH, 0o774);
|
||||
}
|
||||
|
||||
module.exports = async function main(args) {
|
||||
const { runType } = args;
|
||||
try {
|
||||
// create runtime directory
|
||||
await fsExtra.mkdirp('runtime');
|
||||
|
||||
// keep at least one config in database
|
||||
const { clientJson, serverJson } = bsInit({ isMinimal: false, isDryRun: true });
|
||||
if (runType === RUN_TYPE_CLIENT) {
|
||||
const configs = db.get('client_configs');
|
||||
if (configs.size().value() < 1) {
|
||||
clientJson.remarks = 'Default';
|
||||
configs.insert(clientJson).write();
|
||||
}
|
||||
// await extractHelpers();
|
||||
}
|
||||
if (runType === RUN_TYPE_SERVER) {
|
||||
const configs = db.get('server_configs');
|
||||
if (configs.size().value() < 1) {
|
||||
serverJson.remarks = 'Default';
|
||||
configs.insert(serverJson).write();
|
||||
}
|
||||
}
|
||||
|
||||
// add a default user if no users set
|
||||
const users = db.get('users');
|
||||
if (users.value().length < 1) {
|
||||
users.insert({ 'name': 'root', 'password': 'root', 'disallowed_methods': [] }).write();
|
||||
}
|
||||
|
||||
// start server
|
||||
await runServer(args);
|
||||
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
|
@ -0,0 +1,8 @@
|
|||
module.exports = async function keepalive_server_push({ method }) {
|
||||
const handler = this.ctx.push_handlers[method];
|
||||
if (handler) {
|
||||
handler.keepalive();
|
||||
} else {
|
||||
throw Error(`method "${method}" is not found or was unregistered`);
|
||||
}
|
||||
};
|
|
@ -0,0 +1,85 @@
|
|||
const path = require('path');
|
||||
const { import_dir } = require('../utils');
|
||||
|
||||
const {
|
||||
SERVER_PUSH_REGISTER_SUCCESS,
|
||||
SERVER_PUSH_REGISTER_ERROR,
|
||||
SERVER_PUSH_DISPOSE_TIMEOUT,
|
||||
} = require('../constants');
|
||||
|
||||
const lives = import_dir(path.join(__dirname, '..', 'lives'));
|
||||
|
||||
module.exports = async function register_server_push({ method, args }) {
|
||||
const { push_handlers } = this.ctx;
|
||||
if (push_handlers[method]) {
|
||||
return { code: SERVER_PUSH_REGISTER_ERROR, message: `method "${method}" is already registered` };
|
||||
}
|
||||
|
||||
const func = lives[method];
|
||||
if (typeof func !== 'function') {
|
||||
throw Error(`live method "${method}" is not implemented or not registered`);
|
||||
}
|
||||
|
||||
// keepalive timer
|
||||
let timer = null;
|
||||
|
||||
// interval timers
|
||||
let interval_timers = [];
|
||||
|
||||
// prepare a new thisArgs for live methods
|
||||
const push = this.push;
|
||||
const thisArgs = Object.assign({}, this, {
|
||||
push(data) {
|
||||
// keepalive();
|
||||
push(method, data);
|
||||
},
|
||||
pushInterval(getData, interval) {
|
||||
if (typeof getData !== 'function') {
|
||||
throw Error('"getData" must be a function');
|
||||
}
|
||||
const tick = async () => {
|
||||
try {
|
||||
this.push(await getData());
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
};
|
||||
tick();
|
||||
const tm = setInterval(tick, interval);
|
||||
interval_timers.push(tm);
|
||||
},
|
||||
});
|
||||
const unregister = await func.call(thisArgs, args, {});
|
||||
|
||||
// set a timeout here in case the client doesn't call _unregister_server_push()
|
||||
timer = setTimeout(dispose, SERVER_PUSH_DISPOSE_TIMEOUT);
|
||||
|
||||
// remember to remove this handler from ctx.push_handlers after unregister()
|
||||
async function dispose() {
|
||||
clearTimeout(timer);
|
||||
interval_timers.forEach(clearTimeout);
|
||||
if (typeof unregister === 'function') {
|
||||
await unregister();
|
||||
}
|
||||
delete push_handlers[method];
|
||||
}
|
||||
|
||||
// reset timeout timer
|
||||
function keepalive() {
|
||||
if (timer) {
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(dispose, SERVER_PUSH_DISPOSE_TIMEOUT);
|
||||
}
|
||||
}
|
||||
|
||||
// put it to ctx so that it can be accessed from
|
||||
// _keepalive_server_push() and _unregister_server_push()
|
||||
push_handlers[method] = {
|
||||
// this function should be called in _keepalive_server_push()
|
||||
keepalive,
|
||||
// this function should be called in _unregister_server_push()
|
||||
dispose,
|
||||
};
|
||||
|
||||
return { code: SERVER_PUSH_REGISTER_SUCCESS };
|
||||
};
|
|
@ -0,0 +1,6 @@
|
|||
module.exports = async function unregister_server_push({ method }) {
|
||||
const handler = this.ctx.push_handlers[method];
|
||||
if (handler) {
|
||||
await handler.dispose();
|
||||
}
|
||||
};
|
|
@ -0,0 +1,14 @@
|
|||
const bsInit = require('blinksocks/bin/init');
|
||||
const { RUN_TYPE_CLIENT, RUN_TYPE_SERVER } = require('../constants');
|
||||
|
||||
module.exports = async function add_setting({ remarks }) {
|
||||
const { runType } = this.ctx;
|
||||
const { clientJson, serverJson } = bsInit({ isMinimal: false, isDryRun: true });
|
||||
const configs = this.db.getConfigs();
|
||||
if (runType === RUN_TYPE_CLIENT) {
|
||||
return configs.insert(Object.assign({}, clientJson, { remarks })).write().id;
|
||||
}
|
||||
if (runType === RUN_TYPE_SERVER) {
|
||||
return configs.insert(Object.assign({}, serverJson, { remarks })).write().id;
|
||||
}
|
||||
};
|
|
@ -0,0 +1,23 @@
|
|||
module.exports = async function add_user({ user }) {
|
||||
if (typeof user !== 'object') {
|
||||
throw Error('invalid parameter');
|
||||
}
|
||||
|
||||
const { name, password } = user;
|
||||
if (typeof name !== 'string' || name.length < 1) {
|
||||
throw Error('username is invalid');
|
||||
}
|
||||
if (typeof password !== 'string' || password.length < 1) {
|
||||
throw Error('password is invalid');
|
||||
}
|
||||
|
||||
if (this.db.get('users').find({ name }).value()) {
|
||||
throw Error('user is already exists');
|
||||
}
|
||||
|
||||
this.db.get('users').insert({
|
||||
name: name,
|
||||
password: password,
|
||||
disallowed_methods: [],
|
||||
}).write();
|
||||
};
|
|
@ -0,0 +1,6 @@
|
|||
module.exports = async function copy_setting({ id }) {
|
||||
const configs = this.db.getConfigs();
|
||||
const config = configs.find({ id }).cloneDeep().value();
|
||||
delete config.id;
|
||||
return configs.insert(config).write();
|
||||
};
|
|
@ -0,0 +1,3 @@
|
|||
module.exports = async function delete_setting({ id }) {
|
||||
return this.db.getConfigs().remove({ id }).write();
|
||||
};
|
|
@ -0,0 +1,3 @@
|
|||
module.exports = async function delete_user({ id }) {
|
||||
this.db.get('users').remove({ id }).write();
|
||||
};
|
|
@ -0,0 +1,18 @@
|
|||
const { RUN_TYPE_CLIENT, RUN_TYPE_SERVER, DESENSITIZE_PLACEHOLDER } = require('../constants');
|
||||
|
||||
module.exports = async function get_config({ id }, { desensitize = true }) {
|
||||
const { runType } = this.ctx;
|
||||
const config = this.db.getConfigs().find({ id }).cloneDeep().value();
|
||||
if (!config) {
|
||||
throw Error('config is not found');
|
||||
}
|
||||
if (desensitize) {
|
||||
if (runType === RUN_TYPE_CLIENT) {
|
||||
config.server.key = DESENSITIZE_PLACEHOLDER;
|
||||
}
|
||||
if (runType === RUN_TYPE_SERVER) {
|
||||
config.key = DESENSITIZE_PLACEHOLDER;
|
||||
}
|
||||
}
|
||||
return config;
|
||||
};
|
|
@ -0,0 +1,27 @@
|
|||
const os = require('os');
|
||||
|
||||
/**
|
||||
* return os information and Node.js versions
|
||||
*/
|
||||
module.exports = async function get_env() {
|
||||
const osParams = [
|
||||
['cpu', os.cpus()[0].model],
|
||||
['cores', os.cpus().length],
|
||||
['memory', os.totalmem()],
|
||||
['type', os.type()],
|
||||
['platform', os.platform()],
|
||||
['arch', os.arch()],
|
||||
['release', os.release()]
|
||||
];
|
||||
const nodeVersions = [];
|
||||
for (const [key, value] of Object.entries(process.versions)) {
|
||||
nodeVersions.push([key, value]);
|
||||
}
|
||||
return {
|
||||
version: require('../../package').version,
|
||||
blinksocksVersion: require('blinksocks/package').version,
|
||||
runType: this.ctx.runType,
|
||||
os: osParams,
|
||||
node: nodeVersions,
|
||||
};
|
||||
};
|
|
@ -0,0 +1,159 @@
|
|||
module.exports = async function get_preset_defs() {
|
||||
return [
|
||||
{
|
||||
"name": "ss-base",
|
||||
"params": [],
|
||||
"isAddressing": true
|
||||
},
|
||||
{
|
||||
"name": "ss-stream-cipher",
|
||||
"params": [{
|
||||
"key": "method",
|
||||
"type": "enum",
|
||||
"values": [
|
||||
"aes-128-ctr",
|
||||
"aes-192-ctr",
|
||||
"aes-256-ctr",
|
||||
"aes-128-cfb",
|
||||
"aes-192-cfb",
|
||||
"aes-256-cfb",
|
||||
"camellia-128-cfb",
|
||||
"camellia-192-cfb",
|
||||
"camellia-256-cfb",
|
||||
"rc4-md5",
|
||||
"rc4-md5-6",
|
||||
"none"
|
||||
],
|
||||
"defaultValue": "aes-128-ctr",
|
||||
"description": "encryption/decryption algorithm",
|
||||
"optional": true
|
||||
}]
|
||||
},
|
||||
{
|
||||
"name": "ss-aead-cipher",
|
||||
"params": [{
|
||||
"key": "method",
|
||||
"type": "enum",
|
||||
"values": [
|
||||
"aes-128-gcm",
|
||||
"aes-192-gcm",
|
||||
"aes-256-gcm",
|
||||
"chacha20-poly1305",
|
||||
"chacha20-ietf-poly1305",
|
||||
"xchacha20-ietf-poly1305"
|
||||
],
|
||||
"defaultValue": "aes-128-gcm",
|
||||
"description": "encryption/decryption algorithm",
|
||||
"optional": true
|
||||
}]
|
||||
},
|
||||
{
|
||||
"name": "ssr-auth-aes128-md5",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"name": "ssr-auth-aes128-sha1",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"name": "ssr-auth-chain-a",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"name": "ssr-auth-chain-b",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"name": "v2ray-vmess",
|
||||
"params": [{
|
||||
"key": "id",
|
||||
"type": "string",
|
||||
"defaultValue": "",
|
||||
"description": "uuid",
|
||||
"optional": false
|
||||
}, {
|
||||
"key": "security",
|
||||
"type": "enum",
|
||||
"values": [
|
||||
"aes-128-gcm",
|
||||
"chacha20-poly1305",
|
||||
"none"
|
||||
],
|
||||
"defaultValue": "aes-128-gcm",
|
||||
"description": "encryption/decryption algorithm",
|
||||
"optional": true
|
||||
}]
|
||||
},
|
||||
{
|
||||
"name": "obfs-random-padding",
|
||||
"params": []
|
||||
},
|
||||
// {
|
||||
// "name": "obfs-http",
|
||||
// "params": [
|
||||
// {
|
||||
// "key": "file",
|
||||
// "type": "string",
|
||||
// "defaultValue": ""
|
||||
// }
|
||||
// ]
|
||||
// },
|
||||
{
|
||||
"name": "obfs-tls1.2-ticket",
|
||||
"params": [{
|
||||
"key": "sni",
|
||||
"type": "array",
|
||||
"defaultValue": [],
|
||||
"description": "server name indication",
|
||||
"optional": false
|
||||
}]
|
||||
},
|
||||
{
|
||||
"name": "base-auth",
|
||||
"params": [{
|
||||
"key": "method",
|
||||
"type": "enum",
|
||||
"values": [
|
||||
"md5",
|
||||
"sha1",
|
||||
"sha256"
|
||||
],
|
||||
"defaultValue": "sha1",
|
||||
"description": "hash algorithm",
|
||||
"optional": true
|
||||
}],
|
||||
"isAddressing": true
|
||||
},
|
||||
{
|
||||
"name": "aead-random-cipher",
|
||||
"params": [{
|
||||
"key": "method",
|
||||
"type": "enum",
|
||||
"values": [
|
||||
"aes-128-gcm",
|
||||
"aes-192-gcm",
|
||||
"aes-256-gcm"
|
||||
],
|
||||
"defaultValue": "aes-128-gcm",
|
||||
"description": "encryption/decryption algorithm",
|
||||
"optional": true
|
||||
}, {
|
||||
"key": "info",
|
||||
"type": "string",
|
||||
"defaultValue": "bs-subkey",
|
||||
"description": "",
|
||||
"optional": true
|
||||
}, {
|
||||
"key": "factor",
|
||||
"type": "number",
|
||||
"defaultValue": 2,
|
||||
"description": "",
|
||||
"optional": true
|
||||
}]
|
||||
},
|
||||
// {
|
||||
// "name": "auto-conf",
|
||||
// "params": []
|
||||
// },
|
||||
];
|
||||
};
|
|
@ -0,0 +1,3 @@
|
|||
module.exports = async function get_service({ id }) {
|
||||
return this.db.getConfigs().find({ id }).value();
|
||||
};
|
|
@ -0,0 +1,18 @@
|
|||
const { ServiceManager } = require('../utils');
|
||||
|
||||
module.exports = async function get_service_metrics({ id }) {
|
||||
const [cpu_metrics, memory_metrics, speed_metrics, connections_metrics, traffic_metrics] = await Promise.all([
|
||||
ServiceManager.getMetrics(id, 'cpu'),
|
||||
ServiceManager.getMetrics(id, 'memory'),
|
||||
ServiceManager.getMetrics(id, 'speed'),
|
||||
ServiceManager.getMetrics(id, 'connections'),
|
||||
ServiceManager.getMetrics(id, 'traffic'),
|
||||
]);
|
||||
return {
|
||||
cpu_metrics,
|
||||
memory_metrics,
|
||||
speed_metrics,
|
||||
connections_metrics,
|
||||
traffic_metrics,
|
||||
};
|
||||
};
|
|
@ -0,0 +1,19 @@
|
|||
const url = require('url');
|
||||
|
||||
module.exports = async function get_services() {
|
||||
return this.db
|
||||
.getConfigs()
|
||||
.map(({ id, service, log_path, remarks }) => {
|
||||
const { protocol, hostname, port } = url.parse(service);
|
||||
return {
|
||||
id: id,
|
||||
status: '-', // TODO: make a query
|
||||
protocol: protocol ? protocol.slice(0, -1) : '-',
|
||||
address: `${hostname}:${port}`,
|
||||
remarks: remarks || '',
|
||||
log_path: log_path || '-',
|
||||
};
|
||||
})
|
||||
.sortBy('updatedAt')
|
||||
.value();
|
||||
};
|
|
@ -0,0 +1,14 @@
|
|||
const child_process = require('child_process');
|
||||
const { RUNTIME_HELPERS_PAC_PATH } = require('../constants');
|
||||
|
||||
module.exports = async function get_system_pac() {
|
||||
return new Promise((resolve, reject) => {
|
||||
child_process.exec(RUNTIME_HELPERS_PAC_PATH + ' show', { encoding: 'utf-8' }, (err, result) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(result);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
|
@ -0,0 +1,26 @@
|
|||
const child_process = require('child_process');
|
||||
const sudo = require('sudo-prompt');
|
||||
const { RUNTIME_HELPERS_SYSPROXY_PATH } = require('../constants');
|
||||
|
||||
module.exports = async function get_system_proxy() {
|
||||
const command = RUNTIME_HELPERS_SYSPROXY_PATH + ' show';
|
||||
return new Promise((resolve, reject) => {
|
||||
if (process.platform === 'darwin') {
|
||||
sudo.exec(command, { name: 'blinksocksGUI' }, (err, stdout) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(stdout);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
child_process.exec(command, { encoding: 'utf-8' }, (err, result) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(result);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
|
@ -0,0 +1,28 @@
|
|||
const _ = require('lodash');
|
||||
const { DESENSITIZE_PLACEHOLDER } = require('../constants');
|
||||
|
||||
function merge(method_mapping, disallowed_methods) {
|
||||
for (const name of disallowed_methods) {
|
||||
method_mapping[name] = false;
|
||||
}
|
||||
return _.transform(method_mapping, (result, value, key) => result.push({
|
||||
name: key,
|
||||
active: !!value,
|
||||
}), []);
|
||||
}
|
||||
|
||||
module.exports = async function get_users() {
|
||||
return this.db
|
||||
.get('users')
|
||||
.map(({ id, name, password, disallowed_methods }) => {
|
||||
const method_mapping = _.transform(this.getConfigurableMethods(), (result, name) => result[name] = true, {});
|
||||
return {
|
||||
id,
|
||||
name,
|
||||
password: DESENSITIZE_PLACEHOLDER,
|
||||
methods: merge(method_mapping, disallowed_methods || [])
|
||||
.sort((a, b) => a.name.localeCompare(b.name)),
|
||||
};
|
||||
})
|
||||
.value();
|
||||
};
|
|
@ -0,0 +1,7 @@
|
|||
const { ServiceManager } = require('../utils');
|
||||
|
||||
module.exports = async function restart_service({ id }) {
|
||||
await this.invoke('stop_service', { id });
|
||||
await this.invoke('start_service', { id });
|
||||
return ServiceManager.getServiceInfo(id);
|
||||
};
|
|
@ -0,0 +1,23 @@
|
|||
const { Config } = require('blinksocks');
|
||||
const { RUN_TYPE_CLIENT, RUN_TYPE_SERVER, DESENSITIZE_PLACEHOLDER } = require('../constants');
|
||||
|
||||
module.exports = async function save_setting({ id, config }) {
|
||||
const { runType } = this.ctx;
|
||||
const configs = this.db.getConfigs();
|
||||
const prevConfig = configs.find({ id }).value();
|
||||
switch (runType) {
|
||||
case RUN_TYPE_CLIENT:
|
||||
Config.testOnClient(config);
|
||||
if (config.server.key === DESENSITIZE_PLACEHOLDER) {
|
||||
config.server.key = prevConfig.server.key;
|
||||
}
|
||||
break;
|
||||
case RUN_TYPE_SERVER:
|
||||
Config.testOnServer(config);
|
||||
if (config.key === DESENSITIZE_PLACEHOLDER) {
|
||||
config.key = prevConfig.key;
|
||||
}
|
||||
break;
|
||||
}
|
||||
configs.find({ id }).assign(config).write();
|
||||
};
|
|
@ -0,0 +1,42 @@
|
|||
const { DESENSITIZE_PLACEHOLDER } = require('../constants');
|
||||
|
||||
module.exports = async function save_user({ user }) {
|
||||
if (!user || typeof user !== 'object') {
|
||||
throw Error('invalid parameter');
|
||||
}
|
||||
|
||||
// strict check
|
||||
const { id, name, password, methods } = user;
|
||||
if (typeof name !== 'string' || name.length < 1) {
|
||||
throw Error('name is invalid');
|
||||
}
|
||||
if (typeof password !== 'string' || password.length < 1) {
|
||||
throw Error('password is invalid');
|
||||
}
|
||||
if (!Array.isArray(methods)) {
|
||||
throw Error('methods is invalid');
|
||||
}
|
||||
|
||||
// all exist method names
|
||||
const method_names = this.getConfigurableMethods();
|
||||
for (const method of methods) {
|
||||
const { name, active } = method;
|
||||
if (!method_names.includes(name)) {
|
||||
throw Error(`method: "${name}" is not allowed here`);
|
||||
}
|
||||
if (typeof active !== 'boolean') {
|
||||
throw Error('active is invalid');
|
||||
}
|
||||
}
|
||||
|
||||
const _user = this.db.get('users').find({ id }).value();
|
||||
if (!_user) {
|
||||
throw Error(`user "${name}" is not exist`);
|
||||
}
|
||||
|
||||
this.db.get('users').find({ id }).assign({
|
||||
name: name,
|
||||
password: password === DESENSITIZE_PLACEHOLDER ? _user.password : password,
|
||||
disallowed_methods: methods.filter(({ active }) => !active).map(({ name }) => name),
|
||||
}).write();
|
||||
};
|
|
@ -0,0 +1,3 @@
|
|||
module.exports = async function set_system_pac({ sysPac }) {
|
||||
|
||||
};
|
|
@ -0,0 +1,3 @@
|
|||
module.exports = async function set_system_proxy({ sysProxy }) {
|
||||
|
||||
};
|
|
@ -0,0 +1,9 @@
|
|||
const { Config } = require('blinksocks');
|
||||
const { ServiceManager } = require('../utils');
|
||||
|
||||
module.exports = async function start_service({ id }) {
|
||||
const config = await this.invoke('get_config', { id }, { desensitize: false });
|
||||
Config.test(config);
|
||||
await ServiceManager.start(id, config);
|
||||
return ServiceManager.getServiceInfo(id);
|
||||
};
|
|
@ -0,0 +1,6 @@
|
|||
const { ServiceManager } = require('../utils');
|
||||
|
||||
module.exports = async function stop_service({ id }) {
|
||||
await ServiceManager.stop(id);
|
||||
return ServiceManager.getServiceInfo(id);
|
||||
};
|
|
@ -0,0 +1,3 @@
|
|||
module.exports = async function update_remarks({ id, remarks }) {
|
||||
this.db.getConfigs().find({ id }).assign({ remarks }).write();
|
||||
};
|
|
@ -0,0 +1,190 @@
|
|||
const pidusage = require('pidusage');
|
||||
const dateFns = require('date-fns');
|
||||
|
||||
const { Hub } = require('blinksocks');
|
||||
|
||||
let hub = null;
|
||||
|
||||
async function getUsage() {
|
||||
const stats = await pidusage(process.pid);
|
||||
return {
|
||||
cpuUsage: stats.cpu,
|
||||
memoryUsage: stats.memory,
|
||||
};
|
||||
}
|
||||
|
||||
const QUEUE_SIZE = 300; // 5min
|
||||
|
||||
// metrics collector
|
||||
const Monitor = {
|
||||
|
||||
_timer: null,
|
||||
|
||||
_cpu_metrics: [
|
||||
// timestamp, cpu_percentage
|
||||
],
|
||||
|
||||
_memory_metrics: [
|
||||
// timestamp, memory_usage
|
||||
],
|
||||
|
||||
_upload_speed_metrics: [
|
||||
// timestamp, memory_usage
|
||||
],
|
||||
|
||||
_download_speed_metrics: [
|
||||
// timestamp, memory_usage
|
||||
],
|
||||
|
||||
_connections_metrics: [
|
||||
// timestamp, connections
|
||||
],
|
||||
|
||||
_upload_traffic_metrics: [
|
||||
// timestamp, total_bytes
|
||||
],
|
||||
|
||||
_download_traffic_metrics: [
|
||||
// timestamp, total_bytes
|
||||
],
|
||||
|
||||
getCPUMetrics() {
|
||||
return this._cpu_metrics;
|
||||
},
|
||||
|
||||
getMemoryMetrics() {
|
||||
return this._memory_metrics;
|
||||
},
|
||||
|
||||
getUploadSpeedMetrics() {
|
||||
return this._upload_speed_metrics;
|
||||
},
|
||||
|
||||
getDownloadSpeedMetrics() {
|
||||
return this._download_speed_metrics;
|
||||
},
|
||||
|
||||
getConnectionsMetrics() {
|
||||
return this._connections_metrics;
|
||||
},
|
||||
|
||||
getUploadTrafficMetrics() {
|
||||
return this._upload_traffic_metrics;
|
||||
},
|
||||
|
||||
getDownloadTrafficMetrics() {
|
||||
return this._download_traffic_metrics;
|
||||
},
|
||||
|
||||
start() {
|
||||
this._timer = setInterval(this._sample.bind(this), 1e3);
|
||||
},
|
||||
|
||||
stop() {
|
||||
clearInterval(this._timer);
|
||||
},
|
||||
|
||||
async _sample() {
|
||||
const { _cpu_metrics, _memory_metrics } = this;
|
||||
const { _connections_metrics, _upload_speed_metrics, _download_speed_metrics } = this;
|
||||
const { _upload_traffic_metrics, _download_traffic_metrics } = this;
|
||||
try {
|
||||
const { cpuUsage, memoryUsage } = await getUsage();
|
||||
const dateStr = dateFns.format(Date.now(), 'HH:mm:ss');
|
||||
|
||||
_cpu_metrics.push([dateStr, cpuUsage > 1 ? 1 : cpuUsage]);
|
||||
_memory_metrics.push([dateStr, memoryUsage]);
|
||||
|
||||
if (hub) {
|
||||
const performance = hub.getPerformance();
|
||||
const connections = await hub.getConnections();
|
||||
_upload_speed_metrics.push([dateStr, performance.getUploadSpeed()]);
|
||||
_download_speed_metrics.push([dateStr, performance.getDownloadSpeed()]);
|
||||
_connections_metrics.push([dateStr, connections]);
|
||||
_upload_traffic_metrics.push([dateStr, hub.getTotalWritten()]);
|
||||
_download_traffic_metrics.push([dateStr, hub.getTotalRead()]);
|
||||
}
|
||||
|
||||
const metricsCollection = [
|
||||
_cpu_metrics,
|
||||
_memory_metrics,
|
||||
_upload_speed_metrics,
|
||||
_download_speed_metrics,
|
||||
_connections_metrics,
|
||||
_upload_traffic_metrics,
|
||||
_download_traffic_metrics,
|
||||
];
|
||||
|
||||
metricsCollection.forEach((metrics) => {
|
||||
if (metrics.length > QUEUE_SIZE) {
|
||||
metrics.shift();
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// process methods mapping
|
||||
const methods = {
|
||||
|
||||
// start hub
|
||||
'start': async function start(config) {
|
||||
if (!hub) {
|
||||
hub = new Hub(config);
|
||||
Monitor.start();
|
||||
return hub.run();
|
||||
}
|
||||
},
|
||||
// stop hub
|
||||
'stop': async function stop() {
|
||||
if (hub) {
|
||||
Monitor.stop();
|
||||
return hub.terminate();
|
||||
}
|
||||
},
|
||||
// get status from hub
|
||||
'getStatus': async function getStatus() {
|
||||
if (hub) {
|
||||
const performance = hub.getPerformance();
|
||||
return {
|
||||
connections: await hub.getConnections(),
|
||||
// total_download_bytes: instance.getTotalRead(),
|
||||
// total_upload_bytes: instance.getTotalWritten(),
|
||||
download_speed: performance.getDownloadSpeed(),
|
||||
upload_speed: performance.getUploadSpeed(),
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
// get current process cpu metrics
|
||||
'getCPUMetrics': () => Monitor.getCPUMetrics(),
|
||||
// get current process memory metrics
|
||||
'getMemoryMetrics': () => Monitor.getMemoryMetrics(),
|
||||
// get current process upload speed and download speed metrics
|
||||
'getSpeedMetrics': () => [Monitor.getUploadSpeedMetrics(), Monitor.getDownloadSpeedMetrics()],
|
||||
// get current process connections number
|
||||
'getConnectionsMetrics': () => Monitor.getConnectionsMetrics(),
|
||||
// get current process upload traffic and download traffic
|
||||
'getTrafficMetrics': () => [Monitor.getUploadTrafficMetrics(), Monitor.getDownloadTrafficMetrics()],
|
||||
|
||||
};
|
||||
|
||||
process.on('message', async (action) => {
|
||||
if (typeof action !== 'object') {
|
||||
return;
|
||||
}
|
||||
const { type, payload } = action;
|
||||
const func = methods[type];
|
||||
if (typeof func !== 'function') {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const result = await func(payload);
|
||||
process.send({ type: type + '/done', payload: result });
|
||||
} catch (err) {
|
||||
process.send({ type: type + '/error', payload: err.message });
|
||||
}
|
||||
});
|
|
@ -0,0 +1,26 @@
|
|||
const path = require('path');
|
||||
const fsExtra = require('fs-extra');
|
||||
const lodashId = require('lodash-id');
|
||||
const lowdb = require('lowdb');
|
||||
const FileSync = require('lowdb/adapters/FileSync');
|
||||
|
||||
const { DATABASE_PATH } = require('../constants');
|
||||
|
||||
const DATABASE_SCHEMA = {
|
||||
"client_configs": [],
|
||||
"server_configs": [],
|
||||
"users": [],
|
||||
};
|
||||
|
||||
fsExtra.mkdirpSync(path.dirname(DATABASE_PATH));
|
||||
|
||||
const db = lowdb(new FileSync(DATABASE_PATH, {
|
||||
defaultValue: DATABASE_SCHEMA,
|
||||
serialize: (data) => JSON.stringify(data, null, 2),
|
||||
deserialize: JSON.parse,
|
||||
}));
|
||||
|
||||
db.defaults(DATABASE_SCHEMA).write();
|
||||
db._.mixin(lodashId);
|
||||
|
||||
module.exports = db;
|
|
@ -0,0 +1,7 @@
|
|||
const jsSHA = require('jssha');
|
||||
|
||||
module.exports = function hash(algorithm, message) {
|
||||
const shaObj = new jsSHA(algorithm, 'TEXT');
|
||||
shaObj.update(message);
|
||||
return shaObj.getHash('HEX');
|
||||
};
|
|
@ -0,0 +1,19 @@
|
|||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const cache = {};
|
||||
|
||||
module.exports = function import_dir(dir) {
|
||||
if (cache[dir]) {
|
||||
return cache[dir];
|
||||
}
|
||||
const files = fs.readdirSync(dir);
|
||||
const modules = {};
|
||||
for (const file of files) {
|
||||
const name = path.basename(file, '.js');
|
||||
if (name && name[0] !== '.') {
|
||||
modules[name] = require(path.resolve(dir, name));
|
||||
}
|
||||
}
|
||||
return cache[dir] = modules;
|
||||
};
|
|
@ -0,0 +1,7 @@
|
|||
module.exports = {
|
||||
db: require('./db'),
|
||||
hash: require('./hash'),
|
||||
import_dir: require('./import_dir'),
|
||||
logger: require('./logger'),
|
||||
ServiceManager: require('./service_manager'),
|
||||
};
|
|
@ -0,0 +1,19 @@
|
|||
// const os = require('os');
|
||||
const winston = require('winston');
|
||||
|
||||
module.exports = new (winston.Logger)({
|
||||
level: 'silly',
|
||||
transports: [
|
||||
new (winston.transports.Console)({
|
||||
colorize: true,
|
||||
prettyPrint: true
|
||||
}),
|
||||
// new (require('winston-daily-rotate-file'))({
|
||||
// json: false,
|
||||
// eol: os.EOL,
|
||||
// filename: __LOG_PATH__,
|
||||
// level: __LOG_LEVEL__,
|
||||
// maxDays: __LOG_MAX_DAYS__
|
||||
// }),
|
||||
]
|
||||
});
|
|
@ -0,0 +1,156 @@
|
|||
const path = require('path');
|
||||
const child_process = require('child_process');
|
||||
|
||||
const {
|
||||
SERVICE_STATUS_INIT,
|
||||
SERVICE_STATUS_RUNNING,
|
||||
SERVICE_STATUS_STOPPED,
|
||||
} = require('../constants');
|
||||
|
||||
const FORK_SCRIPT = path.resolve(__dirname, '_fork.js');
|
||||
|
||||
const subprocesses = new Map(
|
||||
// <id>: <ChildProcess>,
|
||||
);
|
||||
|
||||
// spawn a new sub process
|
||||
function fork() {
|
||||
const subprocess = child_process.fork(FORK_SCRIPT, { silent: true });
|
||||
const messageQueue = [];
|
||||
|
||||
subprocess.on('message', (message) => {
|
||||
if (typeof message !== 'object') {
|
||||
// drop non-object message
|
||||
return;
|
||||
}
|
||||
messageQueue.push(message);
|
||||
});
|
||||
|
||||
async function send(action) {
|
||||
return new Promise((resolve, reject) => {
|
||||
// send message to sub process immediately
|
||||
subprocess.send(action);
|
||||
|
||||
function scanQueue() {
|
||||
for (let i = 0; i < messageQueue.length; i++) {
|
||||
const message = messageQueue[i];
|
||||
// find related message from sub process
|
||||
if (message.type.indexOf(action.type) === 0) {
|
||||
const { type, payload } = message;
|
||||
if (type === action.type + '/error') {
|
||||
reject(Error(payload));
|
||||
return i;
|
||||
}
|
||||
if (type === action.type + '/done') {
|
||||
resolve(payload);
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// consume messageQueue
|
||||
setImmediate(function consume() {
|
||||
const index = scanQueue();
|
||||
// if not found, continue to consume,
|
||||
if (index < 0) {
|
||||
setImmediate(consume);
|
||||
}
|
||||
// if message found, remove it from queue.
|
||||
else {
|
||||
messageQueue.splice(index, 1);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
// return original <ChildProcess>
|
||||
getProcess() {
|
||||
return subprocess;
|
||||
},
|
||||
// call any methods of forked process
|
||||
async invoke(method, args) {
|
||||
return send({ type: method, payload: args });
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
||||
async start(id, config) {
|
||||
let sub = subprocesses.get(id);
|
||||
if (!sub) {
|
||||
sub = fork();
|
||||
subprocesses.set(id, sub);
|
||||
}
|
||||
try {
|
||||
await sub.invoke('start', config);
|
||||
} catch (err) {
|
||||
subprocesses.delete(id);
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
|
||||
async stop(id) {
|
||||
const sub = subprocesses.get(id);
|
||||
if (sub) {
|
||||
await sub.invoke('stop');
|
||||
sub.getProcess().kill();
|
||||
subprocesses.set(id, null);
|
||||
} else {
|
||||
throw Error(`service(${id}) is not found`);
|
||||
}
|
||||
},
|
||||
|
||||
async getServices() {
|
||||
const services = {};
|
||||
for (const [id] of subprocesses) {
|
||||
services[id] = await this.getServiceInfo(id);
|
||||
}
|
||||
return services;
|
||||
},
|
||||
|
||||
async getServiceInfo(id) {
|
||||
const sub = subprocesses.get(id);
|
||||
if (sub) {
|
||||
return {
|
||||
status: SERVICE_STATUS_RUNNING,
|
||||
...(await sub.invoke('getStatus')),
|
||||
};
|
||||
} else {
|
||||
let status;
|
||||
if (sub === undefined) {
|
||||
status = SERVICE_STATUS_INIT;
|
||||
}
|
||||
if (sub === null) {
|
||||
status = SERVICE_STATUS_STOPPED;
|
||||
}
|
||||
return { status };
|
||||
}
|
||||
},
|
||||
|
||||
async getMetrics(id, type) {
|
||||
const sub = subprocesses.get(id);
|
||||
if (sub) {
|
||||
switch (type) {
|
||||
case 'cpu':
|
||||
return await sub.invoke('getCPUMetrics');
|
||||
case 'memory':
|
||||
return await sub.invoke('getMemoryMetrics');
|
||||
case 'speed':
|
||||
return await sub.invoke('getSpeedMetrics');
|
||||
case 'connections':
|
||||
return await sub.invoke('getConnectionsMetrics');
|
||||
case 'traffic':
|
||||
return await sub.invoke('getTrafficMetrics');
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
|
||||
};
|
|
@ -0,0 +1,3 @@
|
|||
*.o
|
||||
*~
|
||||
*.gch
|
|
@ -0,0 +1,202 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2015 Brave New Software Project, Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
# This Makefile is GNU make compatible. You can get GNU Make from
|
||||
# http://gnuwin32.sourceforge.net/packages/make.htm
|
||||
|
||||
CCFLAGS = -Wall -c
|
||||
|
||||
ifeq ($(OS),Windows_NT)
|
||||
os = windows
|
||||
CCFLAGS += -D WIN32
|
||||
# 32 bit `make` utility over 64 bit OS
|
||||
ifeq ($(PROCESSOR_ARCHITEW6432),AMD64)
|
||||
CCFLAGS += -D AMD64
|
||||
BIN = binaries/windows/pac_amd64
|
||||
else
|
||||
ifeq ($(PROCESSOR_ARCHITECTURE),AMD64)
|
||||
CCFLAGS += -D AMD64
|
||||
BIN = binaries/windows/pac_amd64
|
||||
endif
|
||||
ifeq ($(PROCESSOR_ARCHITECTURE),x86)
|
||||
CCFLAGS += -D IA32
|
||||
BIN = binaries/windows/pac_386
|
||||
endif
|
||||
endif
|
||||
LDFLAGS += -l rasapi32 -l wininet -Wl,--subsystem,windows
|
||||
else
|
||||
UNAME_S := $(shell uname -s)
|
||||
ifeq ($(UNAME_S),Linux)
|
||||
os = linux
|
||||
CCFLAGS += -D LINUX $(shell pkg-config --cflags gio-2.0)
|
||||
LDFLAGS += $(shell pkg-config --libs gio-2.0)
|
||||
UNAME_P := $(shell uname -p)
|
||||
ifeq ($(UNAME_P),x86_64)
|
||||
CCFLAGS += -D AMD64
|
||||
BIN = binaries/linux_amd64/pac
|
||||
endif
|
||||
ifneq ($(filter %86,$(UNAME_P)),)
|
||||
CCFLAGS += -D IA32
|
||||
BIN = binaries/linux_386/pac
|
||||
endif
|
||||
ifneq ($(filter arm%,$(UNAME_P)),)
|
||||
CCFLAGS += -D ARM
|
||||
BIN = binaries/linux_arm/pac
|
||||
endif
|
||||
endif
|
||||
ifeq ($(UNAME_S),Darwin)
|
||||
os = darwin
|
||||
CCFLAGS += -D DARWIN -D AMD64 -x objective-c
|
||||
LDFLAGS += -framework Cocoa -framework SystemConfiguration -framework Security
|
||||
BIN = binaries/darwin/pac
|
||||
endif
|
||||
endif
|
||||
|
||||
CC=gcc
|
||||
|
||||
all: $(BIN)
|
||||
main.o: main.c common.h
|
||||
$(CC) $(CCFLAGS) $^
|
||||
$(os).o: $(os).c common.h
|
||||
$(CC) $(CCFLAGS) $^
|
||||
$(BIN): $(os).o main.o
|
||||
$(CC) -o $@ $^ $(LDFLAGS)
|
||||
|
||||
clean:
|
||||
rm *.o
|
|
@ -0,0 +1,42 @@
|
|||
# pac-cmd
|
||||
|
||||
A command line tool to change proxy auto-config settings of operation system.
|
||||
|
||||
Binaries included in repo. Simply `make` to build it again.
|
||||
|
||||
Note - you will need to run make separately on each platform.
|
||||
|
||||
# Usage
|
||||
|
||||
```sh
|
||||
pac show
|
||||
pac on <pac-url>
|
||||
pac off [old-pac-url-prefix]
|
||||
```
|
||||
|
||||
`pac off` with `old-pac-url` will turn off pac setting only if the existing pac url is prefixed with `old-pac-url-prefix`.
|
||||
|
||||
#Notes
|
||||
|
||||
* **Mac**
|
||||
|
||||
Setting pac is an privileged action on Mac OS. `sudo` or elevate it as below.
|
||||
|
||||
There's an additional option to chown itself to root:wheel and add setuid bit.
|
||||
|
||||
```sh
|
||||
pac setuid
|
||||
```
|
||||
|
||||
* **Windows**
|
||||
|
||||
Install [MinGW-W64](http://sourceforge.net/projects/mingw-w64) to build pac, as it has up to date SDK headers we require.
|
||||
|
||||
To avoid bringing up console window, it doesn't show anything directly to console. Piping the result to other utilities should work.
|
||||
```
|
||||
pac show | cat
|
||||
```
|
||||
|
||||
* **Linux**
|
||||
|
||||
`sudo apt-get install libgtk2.0-dev`
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,19 @@
|
|||
#include <errno.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef DARWIN
|
||||
int setUid();
|
||||
int elevate(char *path, char *prompt, char *iconPath);
|
||||
#endif
|
||||
|
||||
int show();
|
||||
int togglePac(bool turnOn, const char* pacUrl);
|
||||
|
||||
enum RET_ERRORS {
|
||||
RET_NO_ERROR = 0,
|
||||
INVALID_FORMAT = 1,
|
||||
NO_PERMISSION = 2,
|
||||
SYSCALL_FAILED = 3,
|
||||
NO_MEMORY = 4,
|
||||
PAC_URL_CONVERSION_ERROR = 5,
|
||||
};
|
|
@ -0,0 +1,169 @@
|
|||
#import <Foundation/NSArray.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <SystemConfiguration/SCPreferences.h>
|
||||
#import <SystemConfiguration/SCNetworkConfiguration.h>
|
||||
|
||||
#include <sys/syslimits.h>
|
||||
#include <sys/stat.h>
|
||||
#include <mach-o/dyld.h>
|
||||
#include "common.h"
|
||||
|
||||
/* === implement details === */
|
||||
|
||||
typedef Boolean (*visitor) (SCNetworkProtocolRef proxyProtocolRef, NSDictionary* oldPreferences, bool turnOn, const char* pacUrl);
|
||||
|
||||
Boolean showAction(SCNetworkProtocolRef proxyProtocolRef/*unused*/, NSDictionary* oldPreferences, bool turnOn/*unused*/, const char* pacUrl/*unused*/)
|
||||
{
|
||||
NSNumber* on = [oldPreferences valueForKey:(NSString*)kSCPropNetProxiesProxyAutoConfigEnable];
|
||||
NSString* nsOldPacUrl = [oldPreferences valueForKey:(NSString*)kSCPropNetProxiesProxyAutoConfigURLString];
|
||||
if ([on intValue] == 1) {
|
||||
printf("%s\n", [nsOldPacUrl UTF8String]);
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
Boolean toggleAction(SCNetworkProtocolRef proxyProtocolRef, NSDictionary* oldPreferences, bool turnOn, const char* pacUrl)
|
||||
{
|
||||
NSString* nsPacUrl = [[NSString alloc] initWithCString: pacUrl encoding:NSUTF8StringEncoding];
|
||||
NSString* nsOldPacUrl;
|
||||
NSMutableDictionary *newPreferences = [NSMutableDictionary dictionaryWithDictionary: oldPreferences];
|
||||
Boolean success;
|
||||
|
||||
if(turnOn == true) {
|
||||
[newPreferences setValue:[NSNumber numberWithInt:1] forKey:(NSString*)kSCPropNetProxiesProxyAutoConfigEnable];
|
||||
[newPreferences setValue:nsPacUrl forKey:(NSString*)kSCPropNetProxiesProxyAutoConfigURLString];
|
||||
} else {
|
||||
nsOldPacUrl = [oldPreferences valueForKey:(NSString*)kSCPropNetProxiesProxyAutoConfigURLString];
|
||||
// we turn pac off only if the option is set and pac url has the provided
|
||||
// prefix.
|
||||
if (nsPacUrl.length == 0 || [nsOldPacUrl hasPrefix:nsPacUrl]) {
|
||||
[newPreferences setValue:[NSNumber numberWithInt:0] forKey:(NSString*)kSCPropNetProxiesProxyAutoConfigEnable];
|
||||
[newPreferences setValue:@"" forKey:(NSString*)kSCPropNetProxiesProxyAutoConfigURLString];
|
||||
}
|
||||
}
|
||||
|
||||
success = SCNetworkProtocolSetConfiguration(proxyProtocolRef, (__bridge CFDictionaryRef)newPreferences);
|
||||
if(!success) {
|
||||
NSLog(@"Failed to set Protocol Configuration");
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
int visit(visitor v, bool persist, bool turnOn, const char* pacUrl)
|
||||
{
|
||||
int ret = RET_NO_ERROR;
|
||||
Boolean success;
|
||||
|
||||
SCNetworkSetRef networkSetRef;
|
||||
CFArrayRef networkServicesArrayRef;
|
||||
SCNetworkServiceRef networkServiceRef;
|
||||
SCNetworkProtocolRef proxyProtocolRef;
|
||||
NSDictionary *oldPreferences;
|
||||
|
||||
// Get System Preferences Lock
|
||||
SCPreferencesRef prefsRef = SCPreferencesCreate(NULL, CFSTR("org.getlantern.lantern"), NULL);
|
||||
|
||||
if(prefsRef==NULL) {
|
||||
NSLog(@"Fail to obtain Preferences Ref");
|
||||
ret = NO_PERMISSION;
|
||||
goto freePrefsRef;
|
||||
}
|
||||
|
||||
success = SCPreferencesLock(prefsRef, true);
|
||||
if (!success) {
|
||||
NSLog(@"Fail to obtain PreferencesLock");
|
||||
ret = NO_PERMISSION;
|
||||
goto freePrefsRef;
|
||||
}
|
||||
|
||||
// Get available network services
|
||||
networkSetRef = SCNetworkSetCopyCurrent(prefsRef);
|
||||
if(networkSetRef == NULL) {
|
||||
NSLog(@"Fail to get available network services");
|
||||
ret = SYSCALL_FAILED;
|
||||
goto freeNetworkSetRef;
|
||||
}
|
||||
|
||||
//Look up interface entry
|
||||
networkServicesArrayRef = SCNetworkSetCopyServices(networkSetRef);
|
||||
networkServiceRef = NULL;
|
||||
for (long i = 0; i < CFArrayGetCount(networkServicesArrayRef); i++) {
|
||||
networkServiceRef = CFArrayGetValueAtIndex(networkServicesArrayRef, i);
|
||||
|
||||
// Get proxy protocol
|
||||
proxyProtocolRef = SCNetworkServiceCopyProtocol(networkServiceRef, kSCNetworkProtocolTypeProxies);
|
||||
if(proxyProtocolRef == NULL) {
|
||||
NSLog(@"Couldn't acquire copy of proxyProtocol");
|
||||
ret = SYSCALL_FAILED;
|
||||
goto freeProxyProtocolRef;
|
||||
}
|
||||
|
||||
oldPreferences = (__bridge NSDictionary*)SCNetworkProtocolGetConfiguration(proxyProtocolRef);
|
||||
if (!v(proxyProtocolRef, oldPreferences, turnOn, pacUrl)) {
|
||||
ret = SYSCALL_FAILED;
|
||||
}
|
||||
|
||||
freeProxyProtocolRef:
|
||||
CFRelease(proxyProtocolRef);
|
||||
}
|
||||
|
||||
if (persist) {
|
||||
success = SCPreferencesCommitChanges(prefsRef);
|
||||
if(!success) {
|
||||
NSLog(@"Failed to Commit Changes");
|
||||
ret = SYSCALL_FAILED;
|
||||
goto freeNetworkServicesArrayRef;
|
||||
}
|
||||
|
||||
success = SCPreferencesApplyChanges(prefsRef);
|
||||
if(!success) {
|
||||
NSLog(@"Failed to Apply Changes");
|
||||
ret = SYSCALL_FAILED;
|
||||
goto freeNetworkServicesArrayRef;
|
||||
}
|
||||
}
|
||||
|
||||
//Free Resources
|
||||
freeNetworkServicesArrayRef:
|
||||
CFRelease(networkServicesArrayRef);
|
||||
freeNetworkSetRef:
|
||||
CFRelease(networkSetRef);
|
||||
freePrefsRef:
|
||||
SCPreferencesUnlock(prefsRef);
|
||||
CFRelease(prefsRef);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* === public functions === */
|
||||
int setUid()
|
||||
{
|
||||
char exeFullPath [PATH_MAX];
|
||||
uint32_t size = PATH_MAX;
|
||||
if (_NSGetExecutablePath(exeFullPath, &size) != 0)
|
||||
{
|
||||
printf("Path longer than %d, should not occur!!!!!", size);
|
||||
return SYSCALL_FAILED;
|
||||
}
|
||||
if (chown(exeFullPath, 0, 0) != 0) // root:wheel
|
||||
{
|
||||
puts("Error chown");
|
||||
return NO_PERMISSION;
|
||||
}
|
||||
if (chmod(exeFullPath, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH | S_ISUID) != 0)
|
||||
{
|
||||
puts("Error chmod");
|
||||
return NO_PERMISSION;
|
||||
}
|
||||
return RET_NO_ERROR;
|
||||
}
|
||||
|
||||
int show()
|
||||
{
|
||||
return visit(&showAction, false, false /*unused*/, "" /*unused*/);
|
||||
}
|
||||
|
||||
int togglePac(bool turnOn, const char* pacUrl)
|
||||
{
|
||||
return visit(&toggleAction, true, turnOn, pacUrl);
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
#include <gio/gio.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "common.h"
|
||||
|
||||
void init() {
|
||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||
// deprecated since version 2.36, must leave here or prior glib will crash
|
||||
g_type_init();
|
||||
#pragma GCC diagnostic warning "-Wdeprecated-declarations"
|
||||
}
|
||||
|
||||
int show()
|
||||
{
|
||||
init();
|
||||
GSettings* setting = g_settings_new("org.gnome.system.proxy");
|
||||
char* old_mode = g_settings_get_string(setting, "mode");
|
||||
char* old_pac_url = g_settings_get_string(setting, "autoconfig-url");
|
||||
if (strcmp(old_mode, "auto") == 0) {
|
||||
printf("%s\n", old_pac_url);
|
||||
}
|
||||
return RET_NO_ERROR;
|
||||
}
|
||||
|
||||
int togglePac(bool turnOn, const char* pacUrl)
|
||||
{
|
||||
int ret = RET_NO_ERROR;
|
||||
init();
|
||||
GSettings* setting = g_settings_new("org.gnome.system.proxy");
|
||||
if (turnOn == true) {
|
||||
gboolean success = g_settings_set_string(setting, "mode", "auto");
|
||||
if (!success) {
|
||||
fprintf(stderr, "error setting mode to auto\n");
|
||||
ret = SYSCALL_FAILED;
|
||||
goto cleanup;
|
||||
}
|
||||
success = g_settings_set_string(setting, "autoconfig-url", pacUrl);
|
||||
if (!success) {
|
||||
fprintf(stderr, "error setting autoconfig-url to %s\n", pacUrl);
|
||||
ret = SYSCALL_FAILED;
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (strlen(pacUrl) != 0) {
|
||||
char* old_mode = g_settings_get_string(setting, "mode");
|
||||
char* old_pac_url = g_settings_get_string(setting, "autoconfig-url");
|
||||
// we turn pac off only if the option is set and pac url has the provided
|
||||
// prefix.
|
||||
if (strcmp(old_mode, "auto") != 0
|
||||
|| strncmp(old_pac_url, pacUrl, strlen(pacUrl)) != 0 ) {
|
||||
fprintf(stderr, "current pac url setting is not %s, skipping\n", pacUrl);
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
g_settings_reset(setting, "autoconfig-url");
|
||||
gboolean success = g_settings_set_string(setting, "mode", "none");
|
||||
if (!success) {
|
||||
fprintf(stderr, "error setting mode to none\n");
|
||||
ret = SYSCALL_FAILED;
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
cleanup:
|
||||
g_settings_sync();
|
||||
g_object_unref(setting);
|
||||
|
||||
return ret;
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "common.h"
|
||||
|
||||
void usage(const char* binName)
|
||||
{
|
||||
printf("Usage: %s [show | on <pac url> | off [prefix-of-old-pac-url]]\n", binName);
|
||||
exit(INVALID_FORMAT);
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
if (argc < 2) {
|
||||
usage(argv[0]);
|
||||
}
|
||||
|
||||
#ifdef DARWIN
|
||||
if (strcmp(argv[1], "setuid") == 0) {
|
||||
return setUid();
|
||||
}
|
||||
#endif
|
||||
|
||||
if (strcmp(argv[1], "show") == 0) {
|
||||
return show();
|
||||
} else if (strcmp(argv[1], "on") == 0) {
|
||||
if (argc < 3) {
|
||||
usage(argv[0]);
|
||||
}
|
||||
return togglePac(true, argv[2]);
|
||||
} else if (strcmp(argv[1], "off") == 0) {
|
||||
return togglePac(false, argc < 3 ? "" : argv[2]);
|
||||
} else {
|
||||
usage(argv[0]);
|
||||
}
|
||||
// code never reaches here, just avoids compiler from complaining.
|
||||
return RET_NO_ERROR;
|
||||
}
|
|
@ -0,0 +1,182 @@
|
|||
#include <stdlib.h>
|
||||
#include <windows.h>
|
||||
#include <Wininet.h>
|
||||
#include <ras.h>
|
||||
#include <tchar.h>
|
||||
#include <stdio.h>
|
||||
#include "common.h"
|
||||
|
||||
void reportWindowsError(const char* action) {
|
||||
LPTSTR pErrMsg = NULL;
|
||||
DWORD errCode = GetLastError();
|
||||
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|
|
||||
FORMAT_MESSAGE_FROM_SYSTEM|
|
||||
FORMAT_MESSAGE_ARGUMENT_ARRAY,
|
||||
NULL,
|
||||
errCode,
|
||||
LANG_NEUTRAL,
|
||||
pErrMsg,
|
||||
0,
|
||||
NULL);
|
||||
fprintf(stderr, "Error %s: %lu %s\n", action, errCode, pErrMsg);
|
||||
}
|
||||
|
||||
// Stolen from https://github.com/getlantern/winproxy
|
||||
// Figure out which Dial-Up or VPN connection is active; in a normal LAN connection, this should
|
||||
// return NULL. NOTE: For some reason this method fails when compiled in Debug mode but works
|
||||
// every time in Release mode.
|
||||
LPTSTR findActiveConnection() {
|
||||
DWORD dwCb = sizeof(RASCONN);
|
||||
DWORD dwErr = ERROR_SUCCESS;
|
||||
DWORD dwRetries = 5;
|
||||
DWORD dwConnections = 0;
|
||||
RASCONN* lpRasConn = NULL;
|
||||
RASCONNSTATUS rasconnstatus;
|
||||
rasconnstatus.dwSize = sizeof(RASCONNSTATUS);
|
||||
|
||||
//
|
||||
// Loop through in case the information from RAS changes between calls.
|
||||
//
|
||||
while (dwRetries--) {
|
||||
// If the memory is allocated, free it.
|
||||
if (NULL != lpRasConn) {
|
||||
HeapFree(GetProcessHeap(), 0, lpRasConn);
|
||||
lpRasConn = NULL;
|
||||
}
|
||||
|
||||
// Allocate the size needed for the RAS structure.
|
||||
lpRasConn = (RASCONN*)HeapAlloc(GetProcessHeap(), 0, dwCb);
|
||||
if (NULL == lpRasConn) {
|
||||
dwErr = ERROR_NOT_ENOUGH_MEMORY;
|
||||
break;
|
||||
}
|
||||
|
||||
// Set the structure size for version checking purposes.
|
||||
lpRasConn->dwSize = sizeof(RASCONN);
|
||||
|
||||
// Call the RAS API then exit the loop if we are successful or an unknown
|
||||
// error occurs.
|
||||
dwErr = RasEnumConnections(lpRasConn, &dwCb, &dwConnections);
|
||||
if (ERROR_INSUFFICIENT_BUFFER != dwErr) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
//
|
||||
// In the success case, print the names of the connections.
|
||||
//
|
||||
if (ERROR_SUCCESS == dwErr) {
|
||||
DWORD i;
|
||||
for (i = 0; i < dwConnections; i++) {
|
||||
RasGetConnectStatus(lpRasConn[i].hrasconn, &rasconnstatus);
|
||||
if (rasconnstatus.rasconnstate == RASCS_Connected){
|
||||
return lpRasConn[i].szEntryName;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return NULL; // Couldn't find an active dial-up/VPN connection; return NULL
|
||||
}
|
||||
|
||||
int initialize(INTERNET_PER_CONN_OPTION_LIST* options) {
|
||||
DWORD dwBufferSize = sizeof(INTERNET_PER_CONN_OPTION_LIST);
|
||||
options->dwSize = dwBufferSize;
|
||||
options->pszConnection = findActiveConnection();
|
||||
|
||||
options->dwOptionCount = 2;
|
||||
options->dwOptionError = 0;
|
||||
options->pOptions = (INTERNET_PER_CONN_OPTION*)calloc(2, sizeof(INTERNET_PER_CONN_OPTION));
|
||||
if(!options->pOptions) {
|
||||
return NO_MEMORY;
|
||||
}
|
||||
options->pOptions[0].dwOption = INTERNET_PER_CONN_FLAGS;
|
||||
options->pOptions[1].dwOption = INTERNET_PER_CONN_AUTOCONFIG_URL;
|
||||
return RET_NO_ERROR;
|
||||
}
|
||||
|
||||
int query(INTERNET_PER_CONN_OPTION_LIST* options) {
|
||||
DWORD dwBufferSize = sizeof(INTERNET_PER_CONN_OPTION_LIST);
|
||||
if(!InternetQueryOption(NULL, INTERNET_OPTION_PER_CONNECTION_OPTION, options, &dwBufferSize)) {
|
||||
reportWindowsError("Querying options");
|
||||
return SYSCALL_FAILED;
|
||||
}
|
||||
return RET_NO_ERROR;
|
||||
}
|
||||
|
||||
int show()
|
||||
{
|
||||
INTERNET_PER_CONN_OPTION_LIST options;
|
||||
int ret = initialize(&options);
|
||||
if (ret != RET_NO_ERROR) {
|
||||
return ret;
|
||||
}
|
||||
ret = query(&options);
|
||||
if (ret != RET_NO_ERROR) {
|
||||
return ret;
|
||||
}
|
||||
if ((options.pOptions[0].Value.dwValue & PROXY_TYPE_AUTO_PROXY_URL) > 0) {
|
||||
if (options.pOptions[1].Value.pszValue != NULL) {
|
||||
printf("%s\n", options.pOptions[1].Value.pszValue);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int togglePac(bool turnOn, const char* pacUrl)
|
||||
{
|
||||
INTERNET_PER_CONN_OPTION_LIST options;
|
||||
int ret = initialize(&options);
|
||||
if (ret != RET_NO_ERROR) {
|
||||
return ret;
|
||||
}
|
||||
if (turnOn) {
|
||||
options.pOptions[0].Value.dwValue = PROXY_TYPE_AUTO_PROXY_URL;
|
||||
options.pOptions[1].Value.pszValue = (char*)pacUrl;
|
||||
}
|
||||
else {
|
||||
if (strlen(pacUrl) == 0) {
|
||||
goto turnOff;
|
||||
}
|
||||
ret = query(&options);
|
||||
if (ret != RET_NO_ERROR) {
|
||||
goto cleanup;
|
||||
}
|
||||
// we turn pac off only if the option is set and pac url has the provided
|
||||
// prefix.
|
||||
if ((options.pOptions[0].Value.dwValue & PROXY_TYPE_AUTO_PROXY_URL) == 0
|
||||
|| options.pOptions[1].Value.pszValue == NULL
|
||||
|| strncmp(pacUrl, options.pOptions[1].Value.pszValue, strlen(pacUrl)) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
// fall through
|
||||
turnOff:
|
||||
options.pOptions[0].Value.dwValue = PROXY_TYPE_DIRECT;
|
||||
options.pOptions[1].Value.pszValue = "";
|
||||
}
|
||||
|
||||
DWORD dwBufferSize = sizeof(INTERNET_PER_CONN_OPTION_LIST);
|
||||
BOOL result = InternetSetOption(NULL,
|
||||
INTERNET_OPTION_PER_CONNECTION_OPTION,
|
||||
&options,
|
||||
dwBufferSize);
|
||||
if (!result) {
|
||||
reportWindowsError("setting options");
|
||||
ret = SYSCALL_FAILED;
|
||||
goto cleanup;
|
||||
}
|
||||
result = InternetSetOption(NULL, INTERNET_OPTION_SETTINGS_CHANGED, NULL, 0);
|
||||
if (!result) {
|
||||
reportWindowsError("propagating changes");
|
||||
ret = SYSCALL_FAILED;
|
||||
goto cleanup;
|
||||
}
|
||||
result = InternetSetOption(NULL, INTERNET_OPTION_REFRESH , NULL, 0);
|
||||
if (!result) {
|
||||
reportWindowsError("refreshing");
|
||||
ret = SYSCALL_FAILED;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
cleanup:
|
||||
free(options.pOptions);
|
||||
return ret;
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
*.o
|
||||
*~
|
||||
*.gch
|
|
@ -0,0 +1,202 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2015 Brave New Software Project, Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
# This Makefile is GNU make compatible. You can get GNU Make from
|
||||
# http://gnuwin32.sourceforge.net/packages/make.htm
|
||||
|
||||
CCFLAGS = -Wall -c
|
||||
|
||||
ifeq ($(OS),Windows_NT)
|
||||
os = windows
|
||||
CCFLAGS += -D WIN32
|
||||
# 32 bit `make` utility over 64 bit OS
|
||||
ifeq ($(PROCESSOR_ARCHITEW6432),AMD64)
|
||||
CCFLAGS += -D AMD64
|
||||
BIN = binaries/windows/sysproxy_amd64
|
||||
else
|
||||
ifeq ($(PROCESSOR_ARCHITECTURE),AMD64)
|
||||
CCFLAGS += -D AMD64
|
||||
BIN = binaries/windows/sysproxy_amd64
|
||||
endif
|
||||
ifeq ($(PROCESSOR_ARCHITECTURE),x86)
|
||||
CCFLAGS += -D IA32
|
||||
BIN = binaries/windows/sysproxy_386
|
||||
endif
|
||||
endif
|
||||
LDFLAGS += -l rasapi32 -l wininet -Wl,--subsystem,windows
|
||||
else
|
||||
UNAME_S := $(shell uname -s)
|
||||
ifeq ($(UNAME_S),Linux)
|
||||
os = linux
|
||||
CCFLAGS += -D LINUX $(shell pkg-config --cflags gio-2.0)
|
||||
LDFLAGS += $(shell pkg-config --libs gio-2.0)
|
||||
UNAME_P := $(shell uname -p)
|
||||
ifeq ($(UNAME_P),x86_64)
|
||||
CCFLAGS += -D AMD64
|
||||
BIN = binaries/linux_amd64/sysproxy
|
||||
endif
|
||||
ifneq ($(filter %86,$(UNAME_P)),)
|
||||
CCFLAGS += -D IA32
|
||||
BIN = binaries/linux_386/sysproxy
|
||||
endif
|
||||
ifneq ($(filter arm%,$(UNAME_P)),)
|
||||
CCFLAGS += -D ARM
|
||||
BIN = binaries/linux_arm/sysproxy
|
||||
endif
|
||||
endif
|
||||
ifeq ($(UNAME_S),Darwin)
|
||||
os = darwin
|
||||
CCFLAGS += -D DARWIN -D AMD64 -x objective-c
|
||||
LDFLAGS += -framework Cocoa -framework SystemConfiguration -framework Security
|
||||
BIN = binaries/darwin/sysproxy
|
||||
endif
|
||||
endif
|
||||
|
||||
CC=gcc
|
||||
|
||||
all: $(BIN)
|
||||
main.o: main.c common.h
|
||||
$(CC) $(CCFLAGS) $^
|
||||
$(os).o: $(os).c common.h
|
||||
$(CC) $(CCFLAGS) $^
|
||||
$(BIN): $(os).o main.o
|
||||
$(CC) -o $@ $^ $(LDFLAGS)
|
||||
|
||||
clean:
|
||||
rm *.o
|
|
@ -0,0 +1,53 @@
|
|||
# sysproxy-cmd
|
||||
|
||||
A command line tool to change HTTP(s) proxy settings of the operating system.
|
||||
|
||||
Binaries included in repo. Simply `make` to build it again.
|
||||
|
||||
Note - you will need to run make separately on each platform.
|
||||
|
||||
# Usage
|
||||
|
||||
```sh
|
||||
sysproxy show
|
||||
sysproxy on <proxy host> <proxy port>
|
||||
sysproxy off <proxy host> <proxy port>
|
||||
sysproxy wait-and-cleanup <proxy host> <proxy port>
|
||||
```
|
||||
|
||||
`sysproxy off` and `sysproxy wait-and-cleanup` turns off proxy setting only if the
|
||||
existing host and port equal <proxy host> <proxy port>.
|
||||
|
||||
`sysproxy wait-and-cleanup` differs from `sysproxy off` in that it waits for input
|
||||
from stdin (or close) before turning off proxy setting. Any signal or Windows
|
||||
system shutdown message triggers the cleanup too.
|
||||
|
||||
|
||||
# Notes
|
||||
|
||||
* **Mac**
|
||||
|
||||
Setting the system proxy is a privileged action on Mac OS. `sudo` or elevate it
|
||||
as below.
|
||||
|
||||
There's an additional option to chown itself to root:wheel and add setuid bit.
|
||||
|
||||
```sh
|
||||
sysproxy setuid
|
||||
```
|
||||
|
||||
* **Windows**
|
||||
|
||||
Install [MinGW-W64](http://sourceforge.net/projects/mingw-w64) to build sysproxy
|
||||
as it has up to date SDK headers we require. The make command is `mingw32-make`.
|
||||
|
||||
To avoid bringing up console window, it doesn't show anything directly to
|
||||
console. Piping the result to other utilities should work.
|
||||
|
||||
```
|
||||
sysproxy show | cat
|
||||
```
|
||||
|
||||
* **Linux**
|
||||
|
||||
`sudo apt-get install libgtk2.0-dev`
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,25 @@
|
|||
#include <errno.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef DARWIN
|
||||
int setUid();
|
||||
int elevate(char *path, char *prompt, char *iconPath);
|
||||
#endif
|
||||
|
||||
const char* proxyHost;
|
||||
const char* proxyPort;
|
||||
|
||||
#ifdef _WIN32
|
||||
void setupSystemShutdownHandler();
|
||||
#endif
|
||||
|
||||
int show();
|
||||
int toggleProxy(bool turnOn);
|
||||
|
||||
enum RET_ERRORS {
|
||||
RET_NO_ERROR = 0,
|
||||
INVALID_FORMAT = 1,
|
||||
NO_PERMISSION = 2,
|
||||
SYSCALL_FAILED = 3,
|
||||
NO_MEMORY = 4
|
||||
};
|
|
@ -0,0 +1,183 @@
|
|||
#import <Foundation/NSArray.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <SystemConfiguration/SCPreferences.h>
|
||||
#import <SystemConfiguration/SCNetworkConfiguration.h>
|
||||
|
||||
#include <sys/syslimits.h>
|
||||
#include <sys/stat.h>
|
||||
#include <mach-o/dyld.h>
|
||||
#include "common.h"
|
||||
|
||||
/* === implement details === */
|
||||
|
||||
typedef Boolean (*visitor) (SCNetworkProtocolRef proxyProtocolRef, NSDictionary* oldPreferences, bool turnOn);
|
||||
|
||||
Boolean showAction(SCNetworkProtocolRef proxyProtocolRef /*unused*/, NSDictionary* oldPreferences, bool turnOn /*unused*/)
|
||||
{
|
||||
NSNumber* on = [oldPreferences valueForKey:(NSString*)kSCPropNetProxiesHTTPEnable];
|
||||
NSString* nsOldProxyHost = [oldPreferences valueForKey:(NSString*)kSCPropNetProxiesHTTPProxy];
|
||||
NSNumber* nsOldProxyPort = [oldPreferences valueForKey:(NSString*)kSCPropNetProxiesHTTPPort];
|
||||
if ([on intValue] == 1) {
|
||||
printf("%s:%d\n", [nsOldProxyHost UTF8String], [nsOldProxyPort intValue]);
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
Boolean toggleAction(SCNetworkProtocolRef proxyProtocolRef, NSDictionary* oldPreferences, bool turnOn)
|
||||
{
|
||||
NSString* nsProxyHost = [[NSString alloc] initWithCString: proxyHost encoding:NSUTF8StringEncoding];
|
||||
NSNumber* nsProxyPort = [[NSNumber alloc] initWithLong: [[[NSString alloc] initWithCString: proxyPort encoding:NSUTF8StringEncoding] integerValue]];
|
||||
NSString* nsOldProxyHost;
|
||||
NSNumber* nsOldProxyPort;
|
||||
NSMutableDictionary *newPreferences = [NSMutableDictionary dictionaryWithDictionary: oldPreferences];
|
||||
Boolean success;
|
||||
|
||||
if (turnOn == true) {
|
||||
[newPreferences setValue: nsProxyHost forKey:(NSString*)kSCPropNetProxiesHTTPProxy];
|
||||
[newPreferences setValue: nsProxyHost forKey:(NSString*)kSCPropNetProxiesHTTPSProxy];
|
||||
[newPreferences setValue: nsProxyPort forKey:(NSString*)kSCPropNetProxiesHTTPPort];
|
||||
[newPreferences setValue: nsProxyPort forKey:(NSString*)kSCPropNetProxiesHTTPSPort];
|
||||
[newPreferences setValue:[NSNumber numberWithInt:1] forKey:(NSString*)kSCPropNetProxiesHTTPEnable];
|
||||
[newPreferences setValue:[NSNumber numberWithInt:1] forKey:(NSString*)kSCPropNetProxiesHTTPSEnable];
|
||||
} else {
|
||||
nsOldProxyHost = [newPreferences valueForKey:(NSString*)kSCPropNetProxiesHTTPProxy];
|
||||
nsOldProxyPort = [newPreferences valueForKey:(NSString*)kSCPropNetProxiesHTTPPort];
|
||||
if ([nsProxyHost isEqualToString:nsOldProxyHost] && [nsProxyPort intValue] == [nsOldProxyPort intValue]) {
|
||||
[newPreferences setValue:[NSNumber numberWithInt:0] forKey:(NSString*)kSCPropNetProxiesHTTPEnable];
|
||||
[newPreferences setValue: @"" forKey:(NSString*)kSCPropNetProxiesHTTPProxy];
|
||||
[newPreferences setValue: @"" forKey:(NSString*)kSCPropNetProxiesHTTPPort];
|
||||
}
|
||||
nsOldProxyHost = [newPreferences valueForKey:(NSString*)kSCPropNetProxiesHTTPSProxy];
|
||||
nsOldProxyPort = [newPreferences valueForKey:(NSString*)kSCPropNetProxiesHTTPSPort];
|
||||
if ([nsProxyHost isEqualToString:nsOldProxyHost] && [nsProxyPort intValue] == [nsOldProxyPort intValue]) {
|
||||
[newPreferences setValue:[NSNumber numberWithInt:0] forKey:(NSString*)kSCPropNetProxiesHTTPSEnable];
|
||||
[newPreferences setValue: @"" forKey:(NSString*)kSCPropNetProxiesHTTPSProxy];
|
||||
[newPreferences setValue: @"" forKey:(NSString*)kSCPropNetProxiesHTTPSPort];
|
||||
}
|
||||
}
|
||||
|
||||
success = SCNetworkProtocolSetConfiguration(proxyProtocolRef, (__bridge CFDictionaryRef)newPreferences);
|
||||
if(!success) {
|
||||
NSLog(@"Failed to set Protocol Configuration");
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
int visit(visitor v, bool persist, bool turnOn)
|
||||
{
|
||||
int ret = RET_NO_ERROR;
|
||||
Boolean success;
|
||||
|
||||
SCNetworkSetRef networkSetRef;
|
||||
CFArrayRef networkServicesArrayRef;
|
||||
SCNetworkServiceRef networkServiceRef;
|
||||
SCNetworkProtocolRef proxyProtocolRef;
|
||||
NSDictionary *oldPreferences;
|
||||
|
||||
// Get System Preferences Lock
|
||||
SCPreferencesRef prefsRef = SCPreferencesCreate(NULL, CFSTR("org.getlantern.lantern"), NULL);
|
||||
|
||||
if(prefsRef==NULL) {
|
||||
NSLog(@"Fail to obtain Preferences Ref");
|
||||
ret = NO_PERMISSION;
|
||||
goto freePrefsRef;
|
||||
}
|
||||
|
||||
success = SCPreferencesLock(prefsRef, true);
|
||||
if (!success) {
|
||||
NSLog(@"Fail to obtain PreferencesLock");
|
||||
ret = NO_PERMISSION;
|
||||
goto freePrefsRef;
|
||||
}
|
||||
|
||||
// Get available network services
|
||||
networkSetRef = SCNetworkSetCopyCurrent(prefsRef);
|
||||
if(networkSetRef == NULL) {
|
||||
NSLog(@"Fail to get available network services");
|
||||
ret = SYSCALL_FAILED;
|
||||
goto freeNetworkSetRef;
|
||||
}
|
||||
|
||||
//Look up interface entry
|
||||
networkServicesArrayRef = SCNetworkSetCopyServices(networkSetRef);
|
||||
networkServiceRef = NULL;
|
||||
for (long i = 0; i < CFArrayGetCount(networkServicesArrayRef); i++) {
|
||||
networkServiceRef = CFArrayGetValueAtIndex(networkServicesArrayRef, i);
|
||||
|
||||
// Get proxy protocol
|
||||
proxyProtocolRef = SCNetworkServiceCopyProtocol(networkServiceRef, kSCNetworkProtocolTypeProxies);
|
||||
if(proxyProtocolRef == NULL) {
|
||||
NSLog(@"Couldn't acquire copy of proxyProtocol");
|
||||
ret = SYSCALL_FAILED;
|
||||
goto freeProxyProtocolRef;
|
||||
}
|
||||
|
||||
oldPreferences = (__bridge NSDictionary*)SCNetworkProtocolGetConfiguration(proxyProtocolRef);
|
||||
if (!v(proxyProtocolRef, oldPreferences, turnOn)) {
|
||||
ret = SYSCALL_FAILED;
|
||||
}
|
||||
|
||||
freeProxyProtocolRef:
|
||||
CFRelease(proxyProtocolRef);
|
||||
}
|
||||
|
||||
if (persist) {
|
||||
success = SCPreferencesCommitChanges(prefsRef);
|
||||
if(!success) {
|
||||
NSLog(@"Failed to Commit Changes");
|
||||
ret = SYSCALL_FAILED;
|
||||
goto freeNetworkServicesArrayRef;
|
||||
}
|
||||
|
||||
success = SCPreferencesApplyChanges(prefsRef);
|
||||
if(!success) {
|
||||
NSLog(@"Failed to Apply Changes");
|
||||
ret = SYSCALL_FAILED;
|
||||
goto freeNetworkServicesArrayRef;
|
||||
}
|
||||
}
|
||||
|
||||
//Free Resources
|
||||
freeNetworkServicesArrayRef:
|
||||
CFRelease(networkServicesArrayRef);
|
||||
freeNetworkSetRef:
|
||||
CFRelease(networkSetRef);
|
||||
freePrefsRef:
|
||||
SCPreferencesUnlock(prefsRef);
|
||||
CFRelease(prefsRef);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* === public functions === */
|
||||
int setUid()
|
||||
{
|
||||
char exeFullPath [PATH_MAX];
|
||||
uint32_t size = PATH_MAX;
|
||||
if (_NSGetExecutablePath(exeFullPath, &size) != 0)
|
||||
{
|
||||
printf("Path longer than %d, should not occur!!!!!", size);
|
||||
return SYSCALL_FAILED;
|
||||
}
|
||||
if (chown(exeFullPath, 0, 0) != 0) // root:wheel
|
||||
{
|
||||
puts("Error chown");
|
||||
return NO_PERMISSION;
|
||||
}
|
||||
if (chmod(exeFullPath, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH | S_ISUID) != 0)
|
||||
{
|
||||
puts("Error chmod");
|
||||
return NO_PERMISSION;
|
||||
}
|
||||
return RET_NO_ERROR;
|
||||
}
|
||||
|
||||
int show()
|
||||
{
|
||||
return visit(&showAction, false, false /*unused*/);
|
||||
}
|
||||
|
||||
int toggleProxy(bool turnOn)
|
||||
{
|
||||
return visit(&toggleAction, true, turnOn);
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
#include <gio/gio.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "common.h"
|
||||
|
||||
void init() {
|
||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||
// deprecated since version 2.36, must leave here or prior glib will crash
|
||||
g_type_init();
|
||||
#pragma GCC diagnostic warning "-Wdeprecated-declarations"
|
||||
}
|
||||
|
||||
int show()
|
||||
{
|
||||
init();
|
||||
GSettings* setting = g_settings_new("org.gnome.system.proxy");
|
||||
GSettings* httpSetting = g_settings_new("org.gnome.system.proxy.http");
|
||||
char* oldMode = g_settings_get_string(setting, "mode");
|
||||
gboolean oldEnabled = g_settings_get_boolean(httpSetting, "enabled");
|
||||
char* oldHost = g_settings_get_string(httpSetting, "host");
|
||||
gint oldPort = g_settings_get_int(httpSetting, "port");
|
||||
if (oldEnabled && strcmp(oldMode, "manual") == 0) {
|
||||
printf("%s:%d\n", oldHost, oldPort);
|
||||
}
|
||||
return RET_NO_ERROR;
|
||||
}
|
||||
|
||||
int toggleProxy(bool turnOn)
|
||||
{
|
||||
long port = strtol(proxyPort, NULL, 10);
|
||||
if (port == 0) {
|
||||
fprintf(stderr, "unable to parse port '%s'\n", proxyPort);
|
||||
return INVALID_FORMAT;
|
||||
}
|
||||
|
||||
int ret = RET_NO_ERROR;
|
||||
init();
|
||||
GSettings* setting = g_settings_new("org.gnome.system.proxy");
|
||||
GSettings* httpSetting = g_settings_new("org.gnome.system.proxy.http");
|
||||
GSettings* httpsSetting = g_settings_new("org.gnome.system.proxy.https");
|
||||
if (turnOn == true) {
|
||||
gboolean success = g_settings_set_string(httpSetting, "host", proxyHost);
|
||||
if (!success) {
|
||||
fprintf(stderr, "error setting http host to %s\n", proxyHost);
|
||||
ret = SYSCALL_FAILED;
|
||||
goto cleanup;
|
||||
}
|
||||
success = g_settings_set_int(httpSetting, "port", port);
|
||||
if (!success) {
|
||||
fprintf(stderr, "error setting http port %s\n", proxyPort);
|
||||
ret = SYSCALL_FAILED;
|
||||
goto cleanup;
|
||||
}
|
||||
success = g_settings_set_string(httpsSetting, "host", proxyHost);
|
||||
if (!success) {
|
||||
fprintf(stderr, "error setting https host to %s\n", proxyHost);
|
||||
ret = SYSCALL_FAILED;
|
||||
goto cleanup;
|
||||
}
|
||||
success = g_settings_set_int(httpsSetting, "port", port);
|
||||
if (!success) {
|
||||
fprintf(stderr, "error setting https port %s\n", proxyPort);
|
||||
ret = SYSCALL_FAILED;
|
||||
goto cleanup;
|
||||
}
|
||||
success = g_settings_set_boolean(httpSetting, "enabled", TRUE);
|
||||
if (!success) {
|
||||
fprintf(stderr, "error enabling http %s\n", proxyPort);
|
||||
ret = SYSCALL_FAILED;
|
||||
goto cleanup;
|
||||
}
|
||||
success = g_settings_set_string(setting, "mode", "manual");
|
||||
if (!success) {
|
||||
fprintf(stderr, "error setting mode to manual\n");
|
||||
ret = SYSCALL_FAILED;
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (strlen(proxyHost) != 0) {
|
||||
// clear proxy setting only if it's equal to the original setting
|
||||
char* oldMode = g_settings_get_string(setting, "mode");
|
||||
char* oldHTTPHost = g_settings_get_string(httpSetting, "host");
|
||||
long oldHTTPPort = g_settings_get_int(httpSetting, "port");
|
||||
char* oldHTTPSHost = g_settings_get_string(httpsSetting, "host");
|
||||
long oldHTTPSPort = g_settings_get_int(httpsSetting, "port");
|
||||
if (strcmp(oldMode, "manual") != 0 ||
|
||||
strcmp(oldHTTPHost, proxyHost) != 0 ||
|
||||
oldHTTPPort != port ||
|
||||
strcmp(oldHTTPSHost, proxyHost) != 0 ||
|
||||
oldHTTPSPort != port) {
|
||||
fprintf(stderr, "current http or https setting is not %s:%s, skipping\n", proxyHost, proxyPort);
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
g_settings_reset(httpSetting, "host");
|
||||
g_settings_reset(httpSetting, "port");
|
||||
g_settings_reset(httpsSetting, "host");
|
||||
g_settings_reset(httpsSetting, "port");
|
||||
g_settings_reset(httpSetting, "enabled");
|
||||
g_settings_reset(setting, "mode");
|
||||
}
|
||||
|
||||
cleanup:
|
||||
g_settings_sync();
|
||||
g_object_unref(setting);
|
||||
|
||||
return ret;
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <signal.h>
|
||||
#include "common.h"
|
||||
|
||||
void usage(const char* binName)
|
||||
{
|
||||
printf("Usage: %s [show | on | off | wait-and-cleanup <proxy host> <proxy port>]\n", binName);
|
||||
exit(INVALID_FORMAT);
|
||||
}
|
||||
|
||||
void turnOffProxyOnSignal(int signal)
|
||||
{
|
||||
toggleProxy(false);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
void setupSignals()
|
||||
{
|
||||
// Register signal handlers to make sure we turn proxy off no matter what
|
||||
signal(SIGABRT, turnOffProxyOnSignal);
|
||||
signal(SIGFPE, turnOffProxyOnSignal);
|
||||
signal(SIGILL, turnOffProxyOnSignal);
|
||||
signal(SIGINT, turnOffProxyOnSignal);
|
||||
signal(SIGSEGV, turnOffProxyOnSignal);
|
||||
signal(SIGTERM, turnOffProxyOnSignal);
|
||||
signal(SIGSEGV, turnOffProxyOnSignal);
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
if (argc < 2) {
|
||||
usage(argv[0]);
|
||||
}
|
||||
|
||||
#ifdef DARWIN
|
||||
if (strcmp(argv[1], "setuid") == 0) {
|
||||
return setUid();
|
||||
}
|
||||
#endif
|
||||
|
||||
if (strcmp(argv[1], "show") == 0) {
|
||||
return show();
|
||||
} else {
|
||||
if (argc < 4) {
|
||||
usage(argv[0]);
|
||||
}
|
||||
proxyHost = argv[2];
|
||||
proxyPort = argv[3];
|
||||
if (strcmp(argv[1], "on") == 0) {
|
||||
return toggleProxy(true);
|
||||
} else if (strcmp(argv[1], "off") == 0) {
|
||||
return toggleProxy(false);
|
||||
} else if (strcmp(argv[1], "wait-and-cleanup") == 0) {
|
||||
setupSignals();
|
||||
#ifdef _WIN32
|
||||
setupSystemShutdownHandler();
|
||||
#endif
|
||||
// wait for input from stdin (or close), then toggle off
|
||||
getchar();
|
||||
return toggleProxy(false);
|
||||
} else {
|
||||
usage(argv[0]);
|
||||
}
|
||||
}
|
||||
// code never reaches here, just avoids compiler from complaining.
|
||||
return RET_NO_ERROR;
|
||||
}
|
|
@ -0,0 +1,278 @@
|
|||
#include <stdlib.h>
|
||||
#include <windows.h>
|
||||
#include <Wininet.h>
|
||||
#include <ras.h>
|
||||
#include <tchar.h>
|
||||
#include <stdio.h>
|
||||
#include "common.h"
|
||||
|
||||
void reportWindowsError(const char* action, const char* connName) {
|
||||
LPTSTR pErrMsg = NULL;
|
||||
DWORD errCode = GetLastError();
|
||||
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|
|
||||
FORMAT_MESSAGE_IGNORE_INSERTS |
|
||||
FORMAT_MESSAGE_FROM_HMODULE|
|
||||
FORMAT_MESSAGE_FROM_SYSTEM|
|
||||
FORMAT_MESSAGE_ARGUMENT_ARRAY,
|
||||
GetModuleHandle(_T("wininet.dll")),
|
||||
errCode,
|
||||
LANG_NEUTRAL,
|
||||
pErrMsg,
|
||||
0,
|
||||
NULL);
|
||||
if (NULL != connName) {
|
||||
fprintf(stderr, "Error %s for connection '%s': %lu %s\n",
|
||||
action, connName, errCode, pErrMsg);
|
||||
} else {
|
||||
fprintf(stderr, "Error %s: %lu %s\n", action, errCode, pErrMsg);
|
||||
}
|
||||
}
|
||||
|
||||
// Stolen from https://github.com/getlantern/winproxy Figure out which Dial-Up
|
||||
// or VPN connection is active; in a normal LAN connection, this should return
|
||||
// NULL. NOTE: For some reason this method fails when compiled in Debug mode
|
||||
// but works every time in Release mode.
|
||||
// TODO: we may want to find all active connections instead of the first one.
|
||||
LPTSTR findActiveConnection() {
|
||||
DWORD dwCb = sizeof(RASCONN);
|
||||
DWORD dwErr = ERROR_SUCCESS;
|
||||
DWORD dwRetries = 5;
|
||||
DWORD dwConnections = 0;
|
||||
RASCONN* lpRasConn = NULL;
|
||||
RASCONNSTATUS rasconnstatus;
|
||||
rasconnstatus.dwSize = sizeof(RASCONNSTATUS);
|
||||
|
||||
// Loop through in case the information from RAS changes between calls.
|
||||
while (dwRetries--) {
|
||||
// If the memory is allocated, free it.
|
||||
if (NULL != lpRasConn) {
|
||||
HeapFree(GetProcessHeap(), 0, lpRasConn);
|
||||
lpRasConn = NULL;
|
||||
}
|
||||
|
||||
// Allocate the size needed for the RAS structure.
|
||||
lpRasConn = (RASCONN*)HeapAlloc(GetProcessHeap(), 0, dwCb);
|
||||
if (NULL == lpRasConn) {
|
||||
dwErr = ERROR_NOT_ENOUGH_MEMORY;
|
||||
break;
|
||||
}
|
||||
|
||||
// Set the structure size for version checking purposes.
|
||||
lpRasConn->dwSize = sizeof(RASCONN);
|
||||
|
||||
// Call the RAS API then exit the loop if we are successful or an unknown
|
||||
// error occurs.
|
||||
dwErr = RasEnumConnections(lpRasConn, &dwCb, &dwConnections);
|
||||
if (ERROR_INSUFFICIENT_BUFFER != dwErr) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// In the success case, return the first active connection.
|
||||
if (ERROR_SUCCESS == dwErr) {
|
||||
DWORD i;
|
||||
for (i = 0; i < dwConnections; i++) {
|
||||
RasGetConnectStatus(lpRasConn[i].hrasconn, &rasconnstatus);
|
||||
if (rasconnstatus.rasconnstate == RASCS_Connected){
|
||||
return lpRasConn[i].szEntryName;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return NULL; // Couldn't find an active dial-up/VPN connection; return NULL
|
||||
}
|
||||
|
||||
int initialize(INTERNET_PER_CONN_OPTION_LIST* options) {
|
||||
DWORD dwBufferSize = sizeof(INTERNET_PER_CONN_OPTION_LIST);
|
||||
options->dwSize = dwBufferSize;
|
||||
// NULL for LAN, connection name otherwise.
|
||||
options->pszConnection = findActiveConnection();
|
||||
|
||||
options->dwOptionCount = 3;
|
||||
options->dwOptionError = 0;
|
||||
options->pOptions = (INTERNET_PER_CONN_OPTION*)calloc(3, sizeof(INTERNET_PER_CONN_OPTION));
|
||||
if(NULL == options->pOptions) {
|
||||
return NO_MEMORY;
|
||||
}
|
||||
options->pOptions[0].dwOption = INTERNET_PER_CONN_FLAGS;
|
||||
options->pOptions[1].dwOption = INTERNET_PER_CONN_PROXY_SERVER;
|
||||
options->pOptions[2].dwOption = INTERNET_PER_CONN_PROXY_BYPASS;
|
||||
return RET_NO_ERROR;
|
||||
}
|
||||
|
||||
int query(INTERNET_PER_CONN_OPTION_LIST* options) {
|
||||
DWORD dwBufferSize = sizeof(INTERNET_PER_CONN_OPTION_LIST);
|
||||
if(!InternetQueryOption(NULL, INTERNET_OPTION_PER_CONNECTION_OPTION, options, &dwBufferSize)) {
|
||||
reportWindowsError("querying options", options->pszConnection ? options->pszConnection : "LAN");
|
||||
return SYSCALL_FAILED;
|
||||
}
|
||||
return RET_NO_ERROR;
|
||||
}
|
||||
|
||||
int show()
|
||||
{
|
||||
INTERNET_PER_CONN_OPTION_LIST options;
|
||||
int ret = initialize(&options);
|
||||
if (ret != RET_NO_ERROR) {
|
||||
return ret;
|
||||
}
|
||||
ret = query(&options);
|
||||
if (ret != RET_NO_ERROR) {
|
||||
return ret;
|
||||
}
|
||||
if ((options.pOptions[0].Value.dwValue & PROXY_TYPE_PROXY) > 0) {
|
||||
if (options.pOptions[1].Value.pszValue != NULL) {
|
||||
printf("%s\n", options.pOptions[1].Value.pszValue);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int doToggleProxy(bool turnOn)
|
||||
{
|
||||
INTERNET_PER_CONN_OPTION_LIST options;
|
||||
int ret = initialize(&options);
|
||||
if (ret != RET_NO_ERROR) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
char *proxy = malloc(256);
|
||||
snprintf(proxy, 256, "%s:%s", proxyHost, proxyPort);
|
||||
|
||||
if (turnOn) {
|
||||
options.pOptions[0].Value.dwValue = PROXY_TYPE_DIRECT | PROXY_TYPE_PROXY;
|
||||
options.pOptions[1].Value.pszValue = proxy;
|
||||
options.pOptions[2].Value.pszValue = TEXT("<local>");
|
||||
}
|
||||
else {
|
||||
if (strlen(proxyHost) == 0) {
|
||||
goto turnOff;
|
||||
}
|
||||
ret = query(&options);
|
||||
if (ret != RET_NO_ERROR) {
|
||||
goto cleanup;
|
||||
}
|
||||
// we turn proxy off only if the option is set and proxy address has the
|
||||
// provided prefix.
|
||||
if ((options.pOptions[0].Value.dwValue & PROXY_TYPE_PROXY) == 0
|
||||
|| options.pOptions[1].Value.pszValue == NULL
|
||||
|| strncmp(proxy, options.pOptions[1].Value.pszValue, strlen(proxy)) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
// fall through
|
||||
turnOff:
|
||||
options.pOptions[0].Value.dwValue = PROXY_TYPE_DIRECT;
|
||||
options.pOptions[1].Value.pszValue = "";
|
||||
options.pOptions[2].Value.pszValue = "";
|
||||
}
|
||||
|
||||
DWORD dwBufferSize = sizeof(INTERNET_PER_CONN_OPTION_LIST);
|
||||
BOOL result = InternetSetOption(NULL,
|
||||
INTERNET_OPTION_PER_CONNECTION_OPTION,
|
||||
&options,
|
||||
dwBufferSize);
|
||||
if (!result) {
|
||||
reportWindowsError("setting options", options.pszConnection ? options.pszConnection : "LAN");
|
||||
ret = SYSCALL_FAILED;
|
||||
goto cleanup;
|
||||
}
|
||||
result = InternetSetOption(NULL, INTERNET_OPTION_SETTINGS_CHANGED, NULL, 0);
|
||||
if (!result) {
|
||||
reportWindowsError("propagating changes", NULL);
|
||||
ret = SYSCALL_FAILED;
|
||||
goto cleanup;
|
||||
}
|
||||
result = InternetSetOption(NULL, INTERNET_OPTION_REFRESH , NULL, 0);
|
||||
if (!result) {
|
||||
reportWindowsError("refreshing", NULL);
|
||||
ret = SYSCALL_FAILED;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
cleanup:
|
||||
free(options.pOptions);
|
||||
free(proxy);
|
||||
return ret;
|
||||
}
|
||||
|
||||
HWND hWnd = NULL;
|
||||
HANDLE hInvisibleThread = NULL;
|
||||
|
||||
int toggleProxy(bool turnOn)
|
||||
{
|
||||
if (hInvisibleThread == NULL) {
|
||||
return doToggleProxy(turnOn);
|
||||
}
|
||||
|
||||
// in this case, we're running in wait-and-cleanup mode, close the Window and
|
||||
// let that trigger the toggling on the event loop thread.
|
||||
PostMessage(hWnd, WM_CLOSE, 0, 0);
|
||||
WaitForSingleObject(hInvisibleThread, INFINITE);
|
||||
DWORD exitCode = 0;
|
||||
GetExitCodeThread(hInvisibleThread, &exitCode);
|
||||
return exitCode;
|
||||
}
|
||||
|
||||
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
|
||||
switch (message) {
|
||||
case WM_DESTROY:
|
||||
PostQuitMessage(doToggleProxy(false));
|
||||
break;
|
||||
case WM_ENDSESSION:
|
||||
doToggleProxy(false);
|
||||
break;
|
||||
default:
|
||||
return DefWindowProc(hWnd, message, wParam, lParam);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// courtesy of https://social.msdn.microsoft.com/Forums/windowsdesktop/en-US/abf09824-4e4c-4f2c-ae1e-5981f06c9c6e/windows-7-console-application-has-no-way-of-trapping-logoffshutdown-event?forum=windowscompatibility
|
||||
void createInvisibleWindow()
|
||||
{
|
||||
WNDCLASS wc={0};
|
||||
wc.lpfnWndProc=(WNDPROC)WndProc;
|
||||
wc.hInstance=GetModuleHandle(NULL);
|
||||
wc.hIcon=LoadIcon(GetModuleHandle(NULL), "SysproxyWindow");
|
||||
wc.lpszClassName="SysproxyWindow";
|
||||
RegisterClass(&wc);
|
||||
|
||||
hWnd=CreateWindowEx(0,"SysproxyWindow","SysproxyWindow",WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,(HWND) NULL, (HMENU) NULL, GetModuleHandle(NULL), (LPVOID) NULL);
|
||||
if(!hWnd)
|
||||
printf("FAILED to create window!!! %Iu\n",GetLastError());
|
||||
}
|
||||
|
||||
DWORD WINAPI runInvisibleWindowThread(LPVOID lpParam)
|
||||
{
|
||||
MSG msg;
|
||||
createInvisibleWindow();
|
||||
while (GetMessage(&msg,(HWND) NULL , 0 , 0))
|
||||
{
|
||||
TranslateMessage(&msg);
|
||||
DispatchMessage(&msg);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void setupSystemShutdownHandler()
|
||||
{
|
||||
// Create an invisible window so that we can respond to system shutdown and
|
||||
// make sure that we finish setting the system proxy to off.
|
||||
DWORD tid;
|
||||
hInvisibleThread=CreateThread(NULL, 0, runInvisibleWindowThread, NULL, 0, &tid);
|
||||
if (hInvisibleThread == NULL)
|
||||
{
|
||||
printf("FAILED to create thread for invisible window!!! %Iu\n",GetLastError());
|
||||
}
|
||||
|
||||
// Make this process shut down very early so that in system logoff case, we
|
||||
// successfully disable system proxy before parent process tries to do so and
|
||||
// fails, and also do it early enough that required system services have not
|
||||
// yet shut down.
|
||||
//
|
||||
// See https://stackoverflow.com/questions/8760509/graceful-application-shutdown-when-windows-shuts-down
|
||||
BOOL fOkay = SetProcessShutdownParameters(0x3FF, SHUTDOWN_NORETRY);
|
||||
if (!fOkay) {
|
||||
printf("Failed to prioritize sysproxy-cmd for shutdown\n");
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,4 @@
|
|||
BROWSER=none
|
||||
HOST=localhost
|
||||
PORT=3030
|
||||
CI=false
|
|
@ -0,0 +1,2 @@
|
|||
GENERATE_SOURCEMAP=false
|
||||
GOOGLE_ANALYTICS_TRACKING_ID=UA-72182315-3
|
|
@ -0,0 +1,23 @@
|
|||
# See https://help.github.com/ignore-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
*.css
|
|
@ -0,0 +1,8 @@
|
|||
const rewireReactHotLoader = require('react-app-rewire-hot-loader');
|
||||
const rewireMobX = require('react-app-rewire-mobx');
|
||||
|
||||
module.exports = function override(config, env) {
|
||||
config = rewireReactHotLoader(config, env);
|
||||
config = rewireMobX(config, env);
|
||||
return config;
|
||||
};
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue