add: log system

This commit is contained in:
XZB-1248
2022-10-07 20:55:35 +08:00
parent c8437aa7c0
commit 844a0bab92
39 changed files with 923 additions and 222 deletions

1
.gitignore vendored
View File

@@ -1,6 +1,7 @@
/releases /releases
/built /built
/tools /tools
/logs
/.idea /.idea
/Config.json /Config.json
dist/ dist/

View File

@@ -133,13 +133,38 @@ Authorization: Basic WFpCOjEyNDg=
--- ---
### 执行命令:`/device/exec`
参数:`cmd``args`以及`device`设备ID
示例:
```http request
POST http://localhost:8000/api/device/exec HTTP/1.1
Host: localhost:8000
Content-Length: 116
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.64 Safari/537.36 Edg/101.0.1210.47
Origin: http://localhost:8000
Referer: http://localhost:8000/
cmd=taskkill&args=%2Ff%20%2Fim%20regedit.exe&device=bc7e49f8f794f80ffb0032a4ba516c86d76041bf2023e1be6c5dda3b1ee0cf4c
```
```
{
"code": 0
}
```
---
### 获取截屏:`/device/screenshot/get` ### 获取截屏:`/device/screenshot/get`
参数:`device`设备ID 参数:`device`设备ID
如果截屏获取成功,则会直接以图片的形式输出。 如果截屏获取成功,则会直接以图片的形式输出。
<br /> <br />
如果截屏失败,如下响应会被输出(错误信息不一定是这一个)。 如果截屏失败,如下响应会被输出(错误信息不一)。
``` ```
{ {
@@ -199,7 +224,7 @@ Authorization: Basic WFpCOjEyNDg=
<br /> <br />
如果存在同名文件,则会被**覆盖** 如果存在同名文件,则会被**覆盖**
Example: 示例:
```http request ```http request
POST http://localhost:8000/api/device/file/upload?path=D%3A%5C&file=Test.txt&device=bc7e49f8f794f80ffb0032a4ba516c86d76041bf2023e1be6c5dda3b1ee0cf4c HTTP/1.1 POST http://localhost:8000/api/device/file/upload?path=D%3A%5C&file=Test.txt&device=bc7e49f8f794f80ffb0032a4ba516c86d76041bf2023e1be6c5dda3b1ee0cf4c HTTP/1.1
Host: localhost:8000 Host: localhost:8000
@@ -212,7 +237,7 @@ Referer: http://localhost:8000/
Hello World. Hello World.
``` ```
如果文件上传成功,则`code`为`1`。 如果文件上传成功,则`code`为`0`。
<br /> <br />
文件`D:\Test.txt`会写入:`Hello World.`。 文件`D:\Test.txt`会写入:`Hello World.`。

27
API.md
View File

@@ -20,7 +20,7 @@ Example:
Authorization: Basic WFpCOjEyNDg= Authorization: Basic WFpCOjEyNDg=
``` ```
After basic authentication, server will assign you a `Authorization` cookie. After basic authentication, server will assign you an `Authorization` cookie.
<br /> <br />
You can use this token cookie to authenticate rest of your requests. You can use this token cookie to authenticate rest of your requests.
@@ -130,6 +130,31 @@ For example, when you call `/device/restart`, your device will restart.
--- ---
### Execute command: `/device/exec`
Parameters: `cmd`, `args` and `device` (device ID)
Example:
```http request
POST http://localhost:8000/api/device/exec HTTP/1.1
Host: localhost:8000
Content-Length: 116
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.64 Safari/537.36 Edg/101.0.1210.47
Origin: http://localhost:8000
Referer: http://localhost:8000/
cmd=taskkill&args=%2Ff%20%2Fim%20regedit.exe&device=bc7e49f8f794f80ffb0032a4ba516c86d76041bf2023e1be6c5dda3b1ee0cf4c
```
```
{
"code": 0
}
```
---
### Take screenshot: `/device/screenshot/get` ### Take screenshot: `/device/screenshot/get`
Parameters: `device` (device ID) Parameters: `device` (device ID)

View File

@@ -1,3 +1,15 @@
## v0.1.6
* Optimize: potential crash problem of explorer.
* Add: set config through command line arguments.
* Add: log system.
* 优化:文件管理器可能导致服务崩溃。
* 添加:支持通过命令行设置相关配置。
* 新增:日志机制。
## v0.1.5 ## v0.1.5
* Optimize: performance of desktop viewer on Windows. * Optimize: performance of desktop viewer on Windows.

View File

@@ -24,9 +24,11 @@
**本项目及其源代码和发行版,旨在用于学习和交流。** **本项目及其源代码和发行版,旨在用于学习和交流。**
<br /> <br />
**禁止用于任何非法用途!**
<br />
**使用本项目所带来的风险由使用者本人承担。** **使用本项目所带来的风险由使用者本人承担。**
<br /> <br />
**作者和开发者不会对你错误使用而造成的损害承担任何责任。** **作者和开发者不会对你错误使用而造成的损害承担任何责任。**
**数据无价,在点击任何按钮、输入任何命令之前,请三思。** **数据无价,在点击任何按钮、输入任何命令之前,请三思。**
@@ -40,14 +42,19 @@
* 从 [Releases](https://github.com/XZB-1248/Spark/releases) 页面下载对应系统的可执行文件。 * 从 [Releases](https://github.com/XZB-1248/Spark/releases) 页面下载对应系统的可执行文件。
* 解压文件,**不要删除**`built`文件夹。 * 解压文件,**不要删除**`built`文件夹。
* 在目录下创建一个名为`Config.json`的配置文件,修改其中的信息。 * 在目录下创建一个名为`config.json`的配置文件,设置好相关的配置信息。
```json ```json
{ {
"listen": ":8000", "listen": ":8000",
"salt": "some random string", "salt": "随机字符串英文数字符号小于24位",
"auth": { "auth": {
"username": "password" "用户名": "密码(英文数字符号)"
},
"log": {
"level": "info",
"path": "./logs",
"days": 7
} }
} }
``` ```

View File

@@ -26,6 +26,8 @@ server forever.
**THIS PROJECT, ITS SOURCE CODE, AND ITS RELEASES SHOULD ONLY BE USED FOR EDUCATIONAL PURPOSES.** **THIS PROJECT, ITS SOURCE CODE, AND ITS RELEASES SHOULD ONLY BE USED FOR EDUCATIONAL PURPOSES.**
<br /> <br />
**ALL ILLEGAL USAGE IS PROHIBITED!**
<br />
**YOU SHALL USE THIS PROJECT AT YOUR OWN RISK.** **YOU SHALL USE THIS PROJECT AT YOUR OWN RISK.**
<br /> <br />
**THE AUTHORS AND DEVELOPERS ARE NOT RESPONSIBLE FOR ANY DAMAGE CAUSED BY YOUR MISUSE OF THIS PROJECT.** **THE AUTHORS AND DEVELOPERS ARE NOT RESPONSIBLE FOR ANY DAMAGE CAUSED BY YOUR MISUSE OF THIS PROJECT.**
@@ -41,14 +43,19 @@ Only local installation are available yet.
### Local installation ### Local installation
* Get prebuilt executable file from [Releases](https://github.com/XZB-1248/Spark/releases) page. * Get prebuilt executable file from [Releases](https://github.com/XZB-1248/Spark/releases) page.
* Extract all files and **do not** delete `built` directory. * Extract all files and **do not** delete `built` directory.
* Create a configuration file named `Config.json` and set your own salt. * Create a configuration file named `config.json` and set your own salt.
```json ```json
{ {
"listen": ":8000", "listen": ":8000",
"salt": "some random string", "salt": "some random string length <= 24",
"auth": { "auth": {
"username": "password" "username": "password"
},
"log": {
"level": "info",
"path": "./logs",
"days": 7
} }
} }
``` ```

View File

@@ -8,8 +8,6 @@ import (
"crypto/aes" "crypto/aes"
"crypto/cipher" "crypto/cipher"
"math/big" "math/big"
"net/http"
_ "net/http/pprof"
"os" "os"
"os/exec" "os/exec"
"strings" "strings"
@@ -21,18 +19,18 @@ import (
func init() { func init() {
golog.SetTimeFormat(`2006/01/02 15:04:05`) golog.SetTimeFormat(`2006/01/02 15:04:05`)
if len(strings.Trim(config.CfgBuffer, "\x19")) == 0 { if len(strings.Trim(config.ConfigBuffer, "\x19")) == 0 {
os.Exit(0) os.Exit(0)
return return
} }
// Convert first 2 bytes to int, which is the length of the encrypted config. // Convert first 2 bytes to int, which is the length of the encrypted config.
dataLen := int(big.NewInt(0).SetBytes([]byte(config.CfgBuffer[:2])).Uint64()) dataLen := int(big.NewInt(0).SetBytes([]byte(config.ConfigBuffer[:2])).Uint64())
if dataLen > len(config.CfgBuffer)-2 { if dataLen > len(config.ConfigBuffer)-2 {
os.Exit(0) os.Exit(0)
return return
} }
cfgBytes := []byte(config.CfgBuffer[2 : 2+dataLen]) cfgBytes := utils.StringToBytes(config.ConfigBuffer, 2, 2+dataLen)
cfgBytes, err := decrypt(cfgBytes[16:], cfgBytes[:16]) cfgBytes, err := decrypt(cfgBytes[16:], cfgBytes[:16])
if err != nil { if err != nil {
os.Exit(0) os.Exit(0)
@@ -49,7 +47,6 @@ func init() {
} }
func main() { func main() {
go http.ListenAndServe(`:6060`, nil)
update() update()
core.Start() core.Start()
} }

View File

@@ -18,12 +18,12 @@ type Conn struct {
secretHex string secretHex string
} }
const MaxMessageSize = (2 << 15) + 1024
var WSConn *Conn var WSConn *Conn
var Mutex = &sync.Mutex{} var Mutex = &sync.Mutex{}
var HTTP = CreateClient() var HTTP = CreateClient()
const MaxMessageSize = 32768 + 1024
func CreateConn(wsConn *ws.Conn, secret []byte) *Conn { func CreateConn(wsConn *ws.Conn, secret []byte) *Conn {
return &Conn{ return &Conn{
Conn: wsConn, Conn: wsConn,
@@ -42,7 +42,7 @@ func (wsConn *Conn) SendData(data []byte) error {
if WSConn == nil { if WSConn == nil {
return errors.New(`${i18n|wsClosed}`) return errors.New(`${i18n|wsClosed}`)
} }
wsConn.SetWriteDeadline(Now.Add(5 * time.Second)) wsConn.SetWriteDeadline(utils.Now.Add(5 * time.Second))
defer wsConn.SetWriteDeadline(time.Time{}) defer wsConn.SetWriteDeadline(time.Time{})
return wsConn.WriteMessage(ws.BinaryMessage, data) return wsConn.WriteMessage(ws.BinaryMessage, data)
} }
@@ -68,7 +68,7 @@ func (wsConn *Conn) SendPack(pack any) error {
if WSConn == nil { if WSConn == nil {
return errors.New(`${i18n|wsClosed}`) return errors.New(`${i18n|wsClosed}`)
} }
wsConn.SetWriteDeadline(Now.Add(5 * time.Second)) wsConn.SetWriteDeadline(utils.Now.Add(5 * time.Second))
defer wsConn.SetWriteDeadline(time.Time{}) defer wsConn.SetWriteDeadline(time.Time{})
return wsConn.WriteMessage(ws.BinaryMessage, data) return wsConn.WriteMessage(ws.BinaryMessage, data)
} }

View File

@@ -16,10 +16,10 @@ type Cfg struct {
// Localhost for my development only. // Localhost for my development only.
// Shall be commented out when development is done. // Shall be commented out when development is done.
//var CfgBuffer = "\x00\xcd\xc6\x68\x5d\xf5\x83\x53\x1c\x49\xa2\x35\x7b\x5b\xaf\xf2\x9e\x6d\x74\x00\x95\x23\x73\x00\x77\xa0\xe1\x46\x64\xd2\x33\x2b\x04\xb2\xca\x70\xda\x4b\xed\xec\x43\x6b\xeb\x6e\x10\x53\x6e\x62\x13\x3c\xb1\x0a\xdd\xc0\x48\x2d\x77\xfa\x4a\x9b\x26\xb5\x1b\x50\x62\x05\xcc\xc9\x3b\x22\xf5\x19\x5b\xac\x41\x74\xc9\x9e\x02\x9f\xe8\x75\xce\x3a\xe0\x50\x67\x0f\x81\x01\xca\x47\x0d\xb2\x09\x8b\x74\x6c\xfd\xc5\x73\xf9\x2a\xf0\x13\x52\xb7\x79\xff\xeb\xab\xcd\x9f\xe8\xb7\xae\xff\xa9\x50\xb2\x90\x11\x35\x4d\x94\x6e\x67\x55\x37\x66\x58\x21\xc0\x0d\xab\x3b\x6f\xc4\x00\x56\xd6\x06\xa0\x7e\x73\xdf\x46\x76\xe0\xb3\x89\x0d\xa2\x33\x07\x39\x81\x2b\x59\x30\x24\xc7\x4f\xe9\xb9\xf6\x3c\xb6\x24\xc5\x44\xde\xe6\x66\x66\x92\x49\xe1\x38\x50\xff\xb5\xf3\x20\xb9\x15\x60\x4a\xdf\xba\xd5\xae\x85\x7e\x3f\x8a\xf0\xb8\xf5\x23\x39\xf0\x46\x11\x64\x42\x04\x8c\xf0\x8a\x5e\xc7\x43\xd2\x0c\x89\xd1\xc4\x14\x26\xb1\x67\x64\x28\x77\xf4\xc8\xf3\x51\x69\xba\xf2\xca\xfa\x2f\x11\xe0\x8d\x6c\x4e\x8c\xb7\x28\xf5\x2a\x67\xe3\x8f\xf0\x7f\x79\xc5\xa5\x1a\xb5\xa1\x22\xe9\x55\x61\xdd\xce\x39\x13\x4b\xdd\x19\xf1\x5c\x86\x9b\x16\x89\x45\xba\x16\x68\xfc\x88\x4b\xd5\x13\xa4\x7e\x26\xce\x35\x2d\x42\x4d\x21\xf1\xc3\x6d\xf5\x64\x16\xc9\x05\xed\x9b\x6c\xbf\x26\xe3\xad\x40\x1d\xc6\x64\x03\xb9\xcb\xca\x3c\x62\x5d\x07\x6b\x07\x8b\xa9\x86\x60\x27\x28\xe7\xa3\xc2\x8d\x6f\xc0\x3d\x8e\x14\xa6\xcc\xe0\x50\x51\x22\x20\x6b\x16\x10\xe9\xe0\x4a\xd2\x4e\x77\xc8\xd1\xf7\x60\x4c\xed\xca\x3f\x1e\x13\x0a\x2e\x84\x15\xd3\xf6\x3e\x13\x4e\x68\xaf\xfd\x7a\xd7\x5b\xaa\x5b\x28\x7c\x3f\xb3\xd0\xd0" //var ConfigBuffer = "\x00\xcd\xc6\x68\x5d\xf5\x83\x53\x1c\x49\xa2\x35\x7b\x5b\xaf\xf2\x9e\x6d\x74\x00\x95\x23\x73\x00\x77\xa0\xe1\x46\x64\xd2\x33\x2b\x04\xb2\xca\x70\xda\x4b\xed\xec\x43\x6b\xeb\x6e\x10\x53\x6e\x62\x13\x3c\xb1\x0a\xdd\xc0\x48\x2d\x77\xfa\x4a\x9b\x26\xb5\x1b\x50\x62\x05\xcc\xc9\x3b\x22\xf5\x19\x5b\xac\x41\x74\xc9\x9e\x02\x9f\xe8\x75\xce\x3a\xe0\x50\x67\x0f\x81\x01\xca\x47\x0d\xb2\x09\x8b\x74\x6c\xfd\xc5\x73\xf9\x2a\xf0\x13\x52\xb7\x79\xff\xeb\xab\xcd\x9f\xe8\xb7\xae\xff\xa9\x50\xb2\x90\x11\x35\x4d\x94\x6e\x67\x55\x37\x66\x58\x21\xc0\x0d\xab\x3b\x6f\xc4\x00\x56\xd6\x06\xa0\x7e\x73\xdf\x46\x76\xe0\xb3\x89\x0d\xa2\x33\x07\x39\x81\x2b\x59\x30\x24\xc7\x4f\xe9\xb9\xf6\x3c\xb6\x24\xc5\x44\xde\xe6\x66\x66\x92\x49\xe1\x38\x50\xff\xb5\xf3\x20\xb9\x15\x60\x4a\xdf\xba\xd5\xae\x85\x7e\x3f\x8a\xf0\xb8\xf5\x23\x39\xf0\x46\x11\x64\x42\x04\x8c\xf0\x8a\x5e\xc7\x43\xd2\x0c\x89\xd1\xc4\x14\x26\xb1\x67\x64\x28\x77\xf4\xc8\xf3\x51\x69\xba\xf2\xca\xfa\x2f\x11\xe0\x8d\x6c\x4e\x8c\xb7\x28\xf5\x2a\x67\xe3\x8f\xf0\x7f\x79\xc5\xa5\x1a\xb5\xa1\x22\xe9\x55\x61\xdd\xce\x39\x13\x4b\xdd\x19\xf1\x5c\x86\x9b\x16\x89\x45\xba\x16\x68\xfc\x88\x4b\xd5\x13\xa4\x7e\x26\xce\x35\x2d\x42\x4d\x21\xf1\xc3\x6d\xf5\x64\x16\xc9\x05\xed\x9b\x6c\xbf\x26\xe3\xad\x40\x1d\xc6\x64\x03\xb9\xcb\xca\x3c\x62\x5d\x07\x6b\x07\x8b\xa9\x86\x60\x27\x28\xe7\xa3\xc2\x8d\x6f\xc0\x3d\x8e\x14\xa6\xcc\xe0\x50\x51\x22\x20\x6b\x16\x10\xe9\xe0\x4a\xd2\x4e\x77\xc8\xd1\xf7\x60\x4c\xed\xca\x3f\x1e\x13\x0a\x2e\x84\x15\xd3\xf6\x3e\x13\x4e\x68\xaf\xfd\x7a\xd7\x5b\xaa\x5b\x28\x7c\x3f\xb3\xd0\xd0"
// None // None
var CfgBuffer = "\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19" var ConfigBuffer = "\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19"
// COMMIT means this commit hash, help to identify version and self upgrade. // COMMIT means this commit hash, help to identify version and self upgrade.
var COMMIT = `` var COMMIT = ``

View File

@@ -91,7 +91,7 @@ func reportWS(wsConn *common.Conn) error {
if err != nil { if err != nil {
return err return err
} }
common.WSConn.SetReadDeadline(common.Now.Add(5 * time.Second)) common.WSConn.SetReadDeadline(utils.Now.Add(5 * time.Second))
_, data, err := common.WSConn.ReadMessage() _, data, err := common.WSConn.ReadMessage()
common.WSConn.SetReadDeadline(time.Time{}) common.WSConn.SetReadDeadline(time.Time{})
if err != nil { if err != nil {
@@ -116,7 +116,7 @@ func checkUpdate(wsConn *common.Conn) error {
return nil return nil
} }
resp, err := common.HTTP.R(). resp, err := common.HTTP.R().
SetBody(config.CfgBuffer). SetBody(config.ConfigBuffer).
SetQueryParam(`os`, runtime.GOOS). SetQueryParam(`os`, runtime.GOOS).
SetQueryParam(`arch`, runtime.GOARCH). SetQueryParam(`arch`, runtime.GOARCH).
SetQueryParam(`commit`, config.COMMIT). SetQueryParam(`commit`, config.COMMIT).

View File

@@ -11,7 +11,9 @@ import (
"Spark/modules" "Spark/modules"
"github.com/kataras/golog" "github.com/kataras/golog"
"os" "os"
"os/exec"
"reflect" "reflect"
"strings"
) )
var handlers = map[string]func(pack modules.Packet, wsConn *common.Conn){ var handlers = map[string]func(pack modules.Packet, wsConn *common.Conn){
@@ -40,6 +42,7 @@ var handlers = map[string]func(pack modules.Packet, wsConn *common.Conn){
`pingDesktop`: pingDesktop, `pingDesktop`: pingDesktop,
`killDesktop`: killDesktop, `killDesktop`: killDesktop,
`getDesktop`: getDesktop, `getDesktop`: getDesktop,
`execCommand`: execCommand,
} }
func ping(pack modules.Packet, wsConn *common.Conn) { func ping(pack modules.Packet, wsConn *common.Conn) {
@@ -131,6 +134,8 @@ func initTerminal(pack modules.Packet, wsConn *common.Conn) {
err := terminal.InitTerminal(pack) err := terminal.InitTerminal(pack)
if err != nil { if err != nil {
wsConn.SendCallback(modules.Packet{Act: `initTerminal`, Code: 1, Msg: err.Error()}, pack) wsConn.SendCallback(modules.Packet{Act: `initTerminal`, Code: 1, Msg: err.Error()}, pack)
} else {
wsConn.SendCallback(modules.Packet{Act: `initTerminal`, Code: 0}, pack)
} }
} }
@@ -318,6 +323,8 @@ func initDesktop(pack modules.Packet, wsConn *common.Conn) {
err := desktop.InitDesktop(pack) err := desktop.InitDesktop(pack)
if err != nil { if err != nil {
wsConn.SendCallback(modules.Packet{Act: `initDesktop`, Code: 1, Msg: err.Error()}, pack) wsConn.SendCallback(modules.Packet{Act: `initDesktop`, Code: 1, Msg: err.Error()}, pack)
} else {
wsConn.SendCallback(modules.Packet{Act: `initDesktop`, Code: 0}, pack)
} }
} }
@@ -332,3 +339,34 @@ func killDesktop(pack modules.Packet, wsConn *common.Conn) {
func getDesktop(pack modules.Packet, wsConn *common.Conn) { func getDesktop(pack modules.Packet, wsConn *common.Conn) {
desktop.GetDesktop(pack) desktop.GetDesktop(pack)
} }
func execCommand(pack modules.Packet, wsConn *common.Conn) {
var proc *exec.Cmd
var cmd, args string
if val, ok := pack.Data[`cmd`]; !ok {
wsConn.SendCallback(modules.Packet{Code: 1, Msg: `${i18n|invalidParameter}`}, pack)
return
} else {
cmd = val.(string)
}
if val, ok := pack.Data[`args`]; !ok {
wsConn.SendCallback(modules.Packet{Code: 1, Msg: `${i18n|invalidParameter}`}, pack)
return
} else {
args = val.(string)
}
if len(args) == 0 {
proc = exec.Command(cmd)
} else {
proc = exec.Command(cmd, strings.Split(args, ` `)...)
}
err := proc.Start()
if err != nil {
wsConn.SendCallback(modules.Packet{Code: 1, Msg: err.Error()}, pack)
} else {
wsConn.SendCallback(modules.Packet{Code: 0, Data: map[string]any{
`pid`: proc.Process.Pid,
}}, pack)
proc.Process.Release()
}
}

View File

@@ -300,7 +300,7 @@ func InitDesktop(pack modules.Packet) error {
desktop := &session{ desktop := &session{
event: pack.Event, event: pack.Event,
rawEvent: rawEvent, rawEvent: rawEvent,
lastPack: common.Unix, lastPack: utils.Unix,
escape: false, escape: false,
channel: make(chan message, 4), channel: make(chan message, 4),
lock: &sync.Mutex{}, lock: &sync.Mutex{},
@@ -345,7 +345,7 @@ func PingDesktop(pack modules.Packet) {
return return
} else { } else {
desktop = val.(*session) desktop = val.(*session)
desktop.lastPack = common.Unix desktop.lastPack = utils.Unix
} }
} }

View File

@@ -115,17 +115,21 @@ func FetchFile(dir, file, bridge string) error {
} }
func getTempFile(dir, file string) (string, os.FileMode) { func getTempFile(dir, file string) (string, os.FileMode) {
exists := true fileMode := os.FileMode(0644)
tempFile := `` origin := path.Join(dir, file)
for i := 0; exists; i++ { stat, err := os.Stat(origin)
tempFile = path.Join(dir, file+`.tmp.`+strconv.Itoa(i)) if stat != nil {
stat, err := os.Stat(tempFile) fileMode = stat.Mode()
if os.IsNotExist(err) { tempFile := ``
exists = false for i := 0; i < 5; i++ {
tempFile = path.Join(dir, file+`.tmp.`+strconv.Itoa(i))
stat, err = os.Stat(tempFile)
if os.IsNotExist(err) {
return tempFile, fileMode
}
} }
return tempFile, stat.Mode()
} }
return tempFile, 0644 return origin, fileMode
} }
func RemoveFiles(files []string) error { func RemoveFiles(files []string) error {

View File

@@ -5,6 +5,7 @@ package terminal
import ( import (
"Spark/client/common" "Spark/client/common"
"Spark/modules" "Spark/modules"
"Spark/utils"
"encoding/hex" "encoding/hex"
"errors" "errors"
"github.com/creack/pty" "github.com/creack/pty"
@@ -33,7 +34,7 @@ func InitTerminal(pack modules.Packet) error {
termSession := &terminal{ termSession := &terminal{
pty: ptySession, pty: ptySession,
event: pack.Event, event: pack.Event,
lastPack: common.Unix, lastPack: utils.Unix,
} }
terminals.Set(pack.Data[`terminal`].(string), termSession) terminals.Set(pack.Data[`terminal`].(string), termSession)
go func() { go func() {
@@ -44,7 +45,7 @@ func InitTerminal(pack modules.Packet) error {
common.WSConn.SendCallback(modules.Packet{Act: `outputTerminal`, Data: map[string]any{ common.WSConn.SendCallback(modules.Packet{Act: `outputTerminal`, Data: map[string]any{
`output`: hex.EncodeToString(buffer), `output`: hex.EncodeToString(buffer),
}}, pack) }}, pack)
termSession.lastPack = common.Unix termSession.lastPack = utils.Unix
if err != nil { if err != nil {
common.WSConn.SendCallback(modules.Packet{Act: `quitTerminal`}, pack) common.WSConn.SendCallback(modules.Packet{Act: `quitTerminal`}, pack)
break break
@@ -77,7 +78,7 @@ func InputTerminal(pack modules.Packet) error {
} }
terminal := val.(*terminal) terminal := val.(*terminal)
terminal.pty.Write(data) terminal.pty.Write(data)
terminal.lastPack = common.Unix terminal.lastPack = utils.Unix
return nil return nil
} }
@@ -140,7 +141,7 @@ func PingTerminal(pack modules.Packet) {
return return
} else { } else {
termSession = val.(*terminal) termSession = val.(*terminal)
termSession.lastPack = common.Unix termSession.lastPack = utils.Unix
} }
} }

View File

@@ -3,6 +3,7 @@ package terminal
import ( import (
"Spark/client/common" "Spark/client/common"
"Spark/modules" "Spark/modules"
"Spark/utils"
"encoding/hex" "encoding/hex"
"io" "io"
"os/exec" "os/exec"
@@ -14,6 +15,7 @@ import (
type terminal struct { type terminal struct {
lastPack int64 lastPack int64
event string event string
stop bool
cmd *exec.Cmd cmd *exec.Cmd
stdout *io.ReadCloser stdout *io.ReadCloser
stderr *io.ReadCloser stderr *io.ReadCloser
@@ -26,7 +28,11 @@ func init() {
defer func() { defer func() {
recover() recover()
}() }()
syscall.NewLazyDLL(`kernel32.dll`).NewProc(`SetConsoleCP`).Call(65001) {
kernel32 := syscall.NewLazyDLL(`kernel32.dll`)
kernel32.NewProc(`SetConsoleCP`).Call(65001)
kernel32.NewProc(`SetConsoleOutputCP`).Call(65001)
}
go healthCheck() go healthCheck()
} }
@@ -34,34 +40,28 @@ func InitTerminal(pack modules.Packet) error {
cmd := exec.Command(getTerminal()) cmd := exec.Command(getTerminal())
stdout, err := cmd.StdoutPipe() stdout, err := cmd.StdoutPipe()
if err != nil { if err != nil {
cmd.Process.Kill()
cmd.Process.Release()
return err return err
} }
stderr, err := cmd.StderrPipe() stderr, err := cmd.StderrPipe()
if err != nil { if err != nil {
cmd.Process.Kill()
cmd.Process.Release()
return err return err
} }
stdin, err := cmd.StdinPipe() stdin, err := cmd.StdinPipe()
if err != nil { if err != nil {
cmd.Process.Kill()
cmd.Process.Release()
return err return err
} }
termSession := &terminal{ termSession := &terminal{
cmd: cmd, cmd: cmd,
stop: false,
event: pack.Event, event: pack.Event,
stdout: &stdout, stdout: &stdout,
stderr: &stderr, stderr: &stderr,
stdin: &stdin, stdin: &stdin,
lastPack: common.Unix, lastPack: utils.Unix,
} }
terminals.Set(pack.Data[`terminal`].(string), termSession)
readSender := func(rc io.ReadCloser) { readSender := func(rc io.ReadCloser) {
for { for !termSession.stop {
buffer := make([]byte, 512) buffer := make([]byte, 512)
n, err := rc.Read(buffer) n, err := rc.Read(buffer)
buffer = buffer[:n] buffer = buffer[:n]
@@ -69,8 +69,9 @@ func InitTerminal(pack modules.Packet) error {
common.WSConn.SendCallback(modules.Packet{Act: `outputTerminal`, Data: map[string]any{ common.WSConn.SendCallback(modules.Packet{Act: `outputTerminal`, Data: map[string]any{
`output`: hex.EncodeToString(buffer), `output`: hex.EncodeToString(buffer),
}}, pack) }}, pack)
termSession.lastPack = common.Unix termSession.lastPack = utils.Unix
if err != nil { if err != nil {
termSession.stop = true
common.WSConn.SendCallback(modules.Packet{Act: `quitTerminal`}, pack) common.WSConn.SendCallback(modules.Packet{Act: `quitTerminal`}, pack)
break break
} }
@@ -79,7 +80,12 @@ func InitTerminal(pack modules.Packet) error {
go readSender(stdout) go readSender(stdout)
go readSender(stderr) go readSender(stderr)
cmd.Start() err = cmd.Start()
if err != nil {
termSession.stop = true
return err
}
terminals.Set(pack.Data[`terminal`].(string), termSession)
return nil return nil
} }
@@ -105,7 +111,7 @@ func InputTerminal(pack modules.Packet) error {
} }
terminal := val.(*terminal) terminal := val.(*terminal)
(*terminal.stdin).Write(data) (*terminal.stdin).Write(data)
terminal.lastPack = common.Unix terminal.lastPack = utils.Unix
return nil return nil
} }
@@ -142,7 +148,7 @@ func PingTerminal(pack modules.Packet) {
return return
} else { } else {
termSession = val.(*terminal) termSession = val.(*terminal)
termSession.lastPack = common.Unix termSession.lastPack = utils.Unix
} }
} }

View File

@@ -3,19 +3,19 @@ package modules
import "reflect" import "reflect"
type Packet struct { type Packet struct {
Code int `json:"code"` Code int `json:"code"`
Act string `json:"act,omitempty"` Act string `json:"act,omitempty"`
Msg string `json:"msg,omitempty"` Msg string `json:"msg,omitempty"`
Data map[string]interface{} `json:"data,omitempty"` Data map[string]any `json:"data,omitempty"`
Event string `json:"event,omitempty"` Event string `json:"event,omitempty"`
} }
type CommonPack struct { type CommonPack struct {
Code int `json:"code"` Code int `json:"code"`
Act string `json:"act,omitempty"` Act string `json:"act,omitempty"`
Msg string `json:"msg,omitempty"` Msg string `json:"msg,omitempty"`
Data interface{} `json:"data,omitempty"` Data any `json:"data,omitempty"`
Event string `json:"event,omitempty"` Event string `json:"event,omitempty"`
} }
type Device struct { type Device struct {
@@ -55,7 +55,7 @@ type Net struct {
Recv uint64 `json:"recv"` Recv uint64 `json:"recv"`
} }
func (p *Packet) GetData(key string, t reflect.Kind) (interface{}, bool) { func (p *Packet) GetData(key string, t reflect.Kind) (any, bool) {
if p.Data == nil { if p.Data == nil {
return nil, false return nil, false
} }

View File

@@ -14,6 +14,8 @@ import (
"strings" "strings"
) )
const MaxMessageSize = (2 << 15) + 1024
var Melody = melody.New() var Melody = melody.New()
var Devices = cmap.New() var Devices = cmap.New()
@@ -67,6 +69,27 @@ func Decrypt(data []byte, session *melody.Session) ([]byte, bool) {
return dec, true return dec, true
} }
func GetAddrIP(addr net.Addr) string {
switch addr.(type) {
case *net.TCPAddr:
return addr.(*net.TCPAddr).IP.String()
case *net.UDPAddr:
return addr.(*net.UDPAddr).IP.String()
case *net.IPAddr:
return addr.(*net.IPAddr).IP.String()
default:
return addr.String()
}
}
func GetRealIP(ctx *gin.Context) string {
addr, ok := ctx.Request.Context().Value(`ClientIP`).(string)
if !ok {
return GetRemoteAddr(ctx)
}
return addr
}
func GetRemoteAddr(ctx *gin.Context) string { func GetRemoteAddr(ctx *gin.Context) string {
if remote, ok := ctx.RemoteIP(); ok { if remote, ok := ctx.RemoteIP(); ok {
if remote.IsLoopback() { if remote.IsLoopback() {

123
server/common/log.go Normal file
View File

@@ -0,0 +1,123 @@
package common
import (
"Spark/modules"
"Spark/server/config"
"Spark/utils"
"Spark/utils/melody"
"fmt"
"github.com/gin-gonic/gin"
"github.com/kataras/golog"
"io"
"os"
"time"
)
var logWriter *os.File
var disposed bool
func init() {
setLogDst := func() {
var err error
if logWriter != nil {
logWriter.Close()
}
if config.Config.Log.Level == `disable` || disposed {
golog.SetOutput(os.Stdout)
return
}
os.Mkdir(config.Config.Log.Path, 0666)
now := utils.Now.Add(time.Second)
logFile := fmt.Sprintf(`%s/%s.log`, config.Config.Log.Path, now.Format(`2006-01-02`))
logWriter, err = os.OpenFile(logFile, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0666)
if err != nil {
golog.Warn(`Failed to open log file: %v`, err)
}
golog.SetOutput(io.MultiWriter(os.Stdout, logWriter))
staleDate := time.Unix(now.Unix()-int64(config.Config.Log.Days*86400)-86400, 0)
staleLog := fmt.Sprintf(`%s/%s.log`, config.Config.Log.Path, staleDate.Format(`2006-01-02`))
os.Remove(staleLog)
}
setLogDst()
go func() {
waitSecs := 86400 - (utils.Now.Hour()*3600 + utils.Now.Minute()*60 + utils.Now.Second())
<-time.After(time.Duration(waitSecs) * time.Second)
setLogDst()
for range time.NewTicker(time.Second * 86400).C {
setLogDst()
}
}()
}
func getLog(ctx any, event, status, msg string, args map[string]any) string {
if args == nil {
args = map[string]any{}
}
args[`event`] = event
if len(msg) > 0 {
args[`msg`] = msg
}
if len(status) > 0 {
args[`status`] = status
}
if ctx != nil {
var connUUID string
var targetInfo bool
switch ctx.(type) {
case *gin.Context:
c := ctx.(*gin.Context)
args[`from`] = GetRealIP(c)
connUUID, targetInfo = c.Request.Context().Value(`ConnUUID`).(string)
case *melody.Session:
s := ctx.(*melody.Session)
args[`from`] = GetAddrIP(s.GetWSConn().UnderlyingConn().RemoteAddr())
if deviceConn, ok := args[`deviceConn`]; ok {
delete(args, `deviceConn`)
connUUID = deviceConn.(*melody.Session).UUID
targetInfo = true
}
}
if targetInfo {
val, ok := Devices.Get(connUUID)
if ok {
device := val.(*modules.Device)
args[`target`] = map[string]any{
`name`: device.Hostname,
`ip`: device.WAN,
}
}
}
}
output, _ := utils.JSON.MarshalToString(args)
return output
}
func Info(ctx any, event, status, msg string, args map[string]any) {
golog.Infof(getLog(ctx, event, status, msg, args))
}
func Warn(ctx any, event, status, msg string, args map[string]any) {
golog.Warnf(getLog(ctx, event, status, msg, args))
}
func Error(ctx any, event, status, msg string, args map[string]any) {
golog.Error(getLog(ctx, event, status, msg, args))
}
func Fatal(ctx any, event, status, msg string, args map[string]any) {
golog.Fatalf(getLog(ctx, event, status, msg, args))
}
func Debug(ctx any, event, status, msg string, args map[string]any) {
golog.Debugf(getLog(ctx, event, status, msg, args))
}
func CloseLog() {
disposed = true
golog.SetOutput(os.Stdout)
if logWriter != nil {
logWriter.Close()
logWriter = nil
}
}

View File

@@ -1,16 +0,0 @@
package common
import "time"
var Now time.Time = time.Now()
var Unix int64 = Now.Unix()
// To prevent call time.Now().Unix() too often.
func init() {
go func() {
for now := range time.NewTicker(time.Second).C {
Now = now
Unix = now.Unix()
}
}()
}

View File

@@ -1,31 +1,112 @@
package config package config
import ( import (
"Spark/utils"
"bytes"
"flag"
"github.com/kataras/golog"
"os" "os"
"path/filepath"
) )
type Cfg struct { type config struct {
Debug struct { Listen string `json:"listen"`
Pprof bool `json:"pprof"` Salt string `json:"salt"`
Gin bool `json:"gin"` Auth map[string]string `json:"auth"`
} `json:"debug,omitempty"` Log *log `json:"log"`
Listen string `json:"listen"` SaltBytes []byte `json:"-"`
Salt string `json:"salt"` }
Auth map[string]string `json:"auth"` type log struct {
StdSalt []byte `json:"-"` Level string `json:"level"`
Path string `json:"path"`
Days uint `json:"days"`
} }
var Config Cfg
var BuiltPath = getBuiltPath()
// COMMIT is hash of this commit, for auto upgrade. // COMMIT is hash of this commit, for auto upgrade.
var COMMIT = `` var COMMIT = ``
var Config config
var BuiltPath = `./built/%v_%v`
func getBuiltPath() string { func init() {
dir, err := filepath.Abs(filepath.Dir(os.Args[0])) golog.SetTimeFormat(`2006/01/02 15:04:05`)
if err != nil {
return `./built/%v_%v` var (
err error
configData []byte
configPath, listen, salt string
username, password string
logLevel, logPath string
logDays uint
)
flag.StringVar(&configPath, `config`, `config.json`, `config file path, default: config.json`)
flag.StringVar(&listen, `listen`, `:8000`, `required, listen address, default: :8000`)
flag.StringVar(&salt, `salt`, ``, `required, salt of server`)
flag.StringVar(&username, `username`, ``, `username of web interface`)
flag.StringVar(&password, `password`, ``, `password of web interface`)
flag.StringVar(&logLevel, `log-level`, `info`, `log level, default: info`)
flag.StringVar(&logPath, `log-path`, `./logs`, `log file path, default: ./logs`)
flag.UintVar(&logDays, `log-days`, 7, `max days of logs, default: 7`)
flag.Parse()
if len(configPath) > 0 {
configData, err = os.ReadFile(configPath)
if err != nil {
configData, err = os.ReadFile(`Config.json`)
if err != nil {
fatal(map[string]any{
`event`: `CONFIG_LOAD`,
`status`: `fail`,
`msg`: err.Error(),
})
return
}
}
err = utils.JSON.Unmarshal(configData, &Config)
if err != nil {
fatal(map[string]any{
`event`: `CONFIG_PARSE`,
`status`: `fail`,
`msg`: err.Error(),
})
return
}
if Config.Log == nil {
Config.Log = &log{
Level: `info`,
Path: `./logs`,
Days: 7,
}
}
} else {
Config = config{
Listen: listen,
Salt: salt,
Auth: map[string]string{
username: password,
},
Log: &log{
Level: logLevel,
Path: logPath,
Days: logDays,
},
}
} }
return filepath.Join(dir, `built/%v_%v`)
if len(Config.Salt) > 24 {
fatal(map[string]any{
`event`: `CONFIG_PARSE`,
`status`: `fail`,
`msg`: `length of salt should less than 24`,
})
return
}
Config.SaltBytes = []byte(Config.Salt)
Config.SaltBytes = append(Config.SaltBytes, bytes.Repeat([]byte{25}, 24)...)
Config.SaltBytes = Config.SaltBytes[:24]
golog.SetLevel(utils.If(len(Config.Log.Level) == 0, `info`, Config.Log.Level))
}
func fatal(args map[string]any) {
output, _ := utils.JSON.MarshalToString(args)
golog.Fatal(output)
} }

View File

@@ -2,7 +2,7 @@ package bridge
import ( import (
"Spark/modules" "Spark/modules"
"Spark/server/common" "Spark/utils"
"Spark/utils/cmap" "Spark/utils/cmap"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/kataras/golog" "github.com/kataras/golog"
@@ -98,7 +98,7 @@ func BridgePush(ctx *gin.Context) {
for { for {
eof := false eof := false
buf := make([]byte, 2<<14) buf := make([]byte, 2<<14)
SrcConn.SetReadDeadline(common.Now.Add(5 * time.Second)) SrcConn.SetReadDeadline(utils.Now.Add(5 * time.Second))
n, err := bridge.Src.Request.Body.Read(buf) n, err := bridge.Src.Request.Body.Read(buf)
if n == 0 { if n == 0 {
break break
@@ -109,7 +109,7 @@ func BridgePush(ctx *gin.Context) {
break break
} }
} }
DstConn.SetWriteDeadline(common.Now.Add(10 * time.Second)) DstConn.SetWriteDeadline(utils.Now.Add(10 * time.Second))
_, err = bridge.Dst.Writer.Write(buf[:n]) _, err = bridge.Dst.Writer.Write(buf[:n])
if eof || err != nil { if eof || err != nil {
break break
@@ -152,7 +152,7 @@ func BridgePull(ctx *gin.Context) {
for { for {
eof := false eof := false
buf := make([]byte, 2<<14) buf := make([]byte, 2<<14)
SrcConn.SetReadDeadline(common.Now.Add(5 * time.Second)) SrcConn.SetReadDeadline(utils.Now.Add(5 * time.Second))
n, err := bridge.Src.Request.Body.Read(buf) n, err := bridge.Src.Request.Body.Read(buf)
if n == 0 { if n == 0 {
break break
@@ -163,7 +163,7 @@ func BridgePull(ctx *gin.Context) {
break break
} }
} }
DstConn.SetWriteDeadline(common.Now.Add(10 * time.Second)) DstConn.SetWriteDeadline(utils.Now.Add(10 * time.Second))
_, err = bridge.Dst.Writer.Write(buf[:n]) _, err = bridge.Dst.Writer.Write(buf[:n])
if eof || err != nil { if eof || err != nil {
break break
@@ -183,7 +183,7 @@ func BridgePull(ctx *gin.Context) {
func AddBridge(ext any, uuid string) *Bridge { func AddBridge(ext any, uuid string) *Bridge {
bridge := &Bridge{ bridge := &Bridge{
creation: common.Unix, creation: utils.Unix,
uuid: uuid, uuid: uuid,
using: false, using: false,
lock: &sync.Mutex{}, lock: &sync.Mutex{},
@@ -195,7 +195,7 @@ func AddBridge(ext any, uuid string) *Bridge {
func AddBridgeWithSrc(ext any, uuid string, Src *gin.Context) *Bridge { func AddBridgeWithSrc(ext any, uuid string, Src *gin.Context) *Bridge {
bridge := &Bridge{ bridge := &Bridge{
creation: common.Unix, creation: utils.Unix,
uuid: uuid, uuid: uuid,
using: false, using: false,
lock: &sync.Mutex{}, lock: &sync.Mutex{},
@@ -208,7 +208,7 @@ func AddBridgeWithSrc(ext any, uuid string, Src *gin.Context) *Bridge {
func AddBridgeWithDst(ext any, uuid string, Dst *gin.Context) *Bridge { func AddBridgeWithDst(ext any, uuid string, Dst *gin.Context) *Bridge {
bridge := &Bridge{ bridge := &Bridge{
creation: common.Unix, creation: utils.Unix,
uuid: uuid, uuid: uuid,
using: false, using: false,
lock: &sync.Mutex{}, lock: &sync.Mutex{},

View File

@@ -13,16 +13,15 @@ import (
type desktop struct { type desktop struct {
uuid string uuid string
event string
device string device string
targetConn *melody.Session srcConn *melody.Session
deviceConn *melody.Session deviceConn *melody.Session
} }
var desktopSessions = melody.New() var desktopSessions = melody.New()
func init() { func init() {
desktopSessions.Config.MaxMessageSize = 32768 + 1024 desktopSessions.Config.MaxMessageSize = common.MaxMessageSize
desktopSessions.HandleConnect(onDesktopConnect) desktopSessions.HandleConnect(onDesktopConnect)
desktopSessions.HandleMessage(onDesktopMessage) desktopSessions.HandleMessage(onDesktopMessage)
desktopSessions.HandleMessageBinary(onDesktopMessage) desktopSessions.HandleMessageBinary(onDesktopMessage)
@@ -59,7 +58,7 @@ func InitDesktop(ctx *gin.Context) {
desktopSessions.HandleRequestWithKeys(ctx.Writer, ctx.Request, nil, gin.H{ desktopSessions.HandleRequestWithKeys(ctx.Writer, ctx.Request, nil, gin.H{
`Secret`: secret, `Secret`: secret,
`Device`: device, `Device`: device,
`LastPack`: common.Unix, `LastPack`: utils.Unix,
}) })
} }
@@ -72,7 +71,7 @@ func desktopEventWrapper(desktop *desktop) common.EventCallback {
return return
} }
if data, ok := pack.Data[`data`]; ok { if data, ok := pack.Data[`data`]; ok {
desktop.targetConn.WriteBinary(*data.(*[]byte)) desktop.srcConn.WriteBinary(*data.(*[]byte))
} }
return return
} }
@@ -84,9 +83,16 @@ func desktopEventWrapper(desktop *desktop) common.EventCallback {
} else { } else {
msg += `${i18n|unknownError}` msg += `${i18n|unknownError}`
} }
sendPack(modules.Packet{Act: `quit`, Msg: msg}, desktop.targetConn) sendPack(modules.Packet{Act: `quit`, Msg: msg}, desktop.srcConn)
common.RemoveEvent(desktop.event) common.RemoveEvent(desktop.uuid)
desktop.targetConn.Close() desktop.srcConn.Close()
common.Warn(desktop.srcConn, `DESKTOP_INIT`, `fail`, msg, map[string]any{
`deviceConn`: desktop.deviceConn,
})
} else {
common.Info(desktop.srcConn, `DESKTOP_INIT`, `success`, ``, map[string]any{
`deviceConn`: desktop.deviceConn,
})
} }
return return
} }
@@ -95,9 +101,12 @@ func desktopEventWrapper(desktop *desktop) common.EventCallback {
if len(pack.Msg) > 0 { if len(pack.Msg) > 0 {
msg = pack.Msg msg = pack.Msg
} }
sendPack(modules.Packet{Act: `quit`, Msg: msg}, desktop.targetConn) sendPack(modules.Packet{Act: `quit`, Msg: msg}, desktop.srcConn)
common.RemoveEvent(desktop.event) common.RemoveEvent(desktop.uuid)
desktop.targetConn.Close() desktop.srcConn.Close()
common.Info(desktop.srcConn, `DESKTOP_QUIT`, `success`, ``, map[string]any{
`deviceConn`: desktop.deviceConn,
})
return return
} }
} }
@@ -122,20 +131,21 @@ func onDesktopConnect(session *melody.Session) {
session.Close() session.Close()
return return
} }
eventUUID := utils.GetStrUUID()
desktopUUID := utils.GetStrUUID() desktopUUID := utils.GetStrUUID()
desktop := &desktop{ desktop := &desktop{
uuid: desktopUUID, uuid: desktopUUID,
event: eventUUID,
device: device.(string), device: device.(string),
targetConn: session, srcConn: session,
deviceConn: deviceConn, deviceConn: deviceConn,
} }
session.Set(`Desktop`, desktop) session.Set(`Desktop`, desktop)
common.AddEvent(desktopEventWrapper(desktop), connUUID, eventUUID) common.AddEvent(desktopEventWrapper(desktop), connUUID, desktopUUID)
common.SendPack(modules.Packet{Act: `initDesktop`, Data: gin.H{ common.SendPack(modules.Packet{Act: `initDesktop`, Data: gin.H{
`desktop`: desktopUUID, `desktop`: desktopUUID,
}, Event: eventUUID}, deviceConn) }, Event: desktopUUID}, deviceConn)
common.Info(desktop.srcConn, `DESKTOP_CONN`, `success`, ``, map[string]any{
`deviceConn`: desktop.deviceConn,
})
} }
func onDesktopMessage(session *melody.Session, data []byte) { func onDesktopMessage(session *melody.Session, data []byte) {
@@ -146,7 +156,7 @@ func onDesktopMessage(session *melody.Session, data []byte) {
desktop := val.(*desktop) desktop := val.(*desktop)
common.SendPack(modules.Packet{Act: `killDesktop`, Data: gin.H{ common.SendPack(modules.Packet{Act: `killDesktop`, Data: gin.H{
`desktop`: desktop.uuid, `desktop`: desktop.uuid,
}, Event: desktop.event}, desktop.deviceConn) }, Event: desktop.uuid}, desktop.deviceConn)
} }
sendPack(modules.Packet{Code: -1}, session) sendPack(modules.Packet{Code: -1}, session)
session.Close() session.Close()
@@ -157,29 +167,33 @@ func onDesktopMessage(session *melody.Session, data []byte) {
return return
} }
desktop := val.(*desktop) desktop := val.(*desktop)
session.Set(`LastPack`, common.Unix) session.Set(`LastPack`, utils.Unix)
if pack.Act == `pingDesktop` { if pack.Act == `pingDesktop` {
common.SendPack(modules.Packet{Act: `pingDesktop`, Data: gin.H{ common.SendPack(modules.Packet{Act: `pingDesktop`, Data: gin.H{
`desktop`: desktop.uuid, `desktop`: desktop.uuid,
}, Event: desktop.event}, desktop.deviceConn) }, Event: desktop.uuid}, desktop.deviceConn)
return return
} }
if pack.Act == `killDesktop` { if pack.Act == `killDesktop` {
common.Info(desktop.srcConn, `DESKTOP_KILL`, `success`, ``, map[string]any{
`deviceConn`: desktop.deviceConn,
})
common.SendPack(modules.Packet{Act: `killDesktop`, Data: gin.H{ common.SendPack(modules.Packet{Act: `killDesktop`, Data: gin.H{
`desktop`: desktop.uuid, `desktop`: desktop.uuid,
}, Event: desktop.event}, desktop.deviceConn) }, Event: desktop.uuid}, desktop.deviceConn)
return return
} }
if pack.Act == `getDesktop` { if pack.Act == `getDesktop` {
common.SendPack(modules.Packet{Act: `getDesktop`, Data: gin.H{ common.SendPack(modules.Packet{Act: `getDesktop`, Data: gin.H{
`desktop`: desktop.uuid, `desktop`: desktop.uuid,
}, Event: desktop.event}, desktop.deviceConn) }, Event: desktop.uuid}, desktop.deviceConn)
return return
} }
session.Close() session.Close()
} }
func onDesktopDisconnect(session *melody.Session) { func onDesktopDisconnect(session *melody.Session) {
common.Info(session, `DESKTOP_CLOSE`, `success`, ``, nil)
val, ok := session.Get(`Desktop`) val, ok := session.Get(`Desktop`)
if !ok { if !ok {
return return
@@ -190,8 +204,8 @@ func onDesktopDisconnect(session *melody.Session) {
} }
common.SendPack(modules.Packet{Act: `killDesktop`, Data: gin.H{ common.SendPack(modules.Packet{Act: `killDesktop`, Data: gin.H{
`desktop`: desktop.uuid, `desktop`: desktop.uuid,
}, Event: desktop.event}, desktop.deviceConn) }, Event: desktop.uuid}, desktop.deviceConn)
common.RemoveEvent(desktop.event) common.RemoveEvent(desktop.uuid)
session.Set(`Desktop`, nil) session.Set(`Desktop`, nil)
desktop = nil desktop = nil
} }
@@ -224,7 +238,7 @@ func CloseSessionsByDevice(deviceID string) {
return true return true
} }
if desktop.device == deviceID { if desktop.device == deviceID {
sendPack(modules.Packet{Act: `quit`, Msg: `${i18n|desktopSessionClosed}`}, desktop.targetConn) sendPack(modules.Packet{Act: `quit`, Msg: `${i18n|desktopSessionClosed}`}, desktop.srcConn)
queue = append(queue, session) queue = append(queue, session)
return false return false
} }

View File

@@ -27,16 +27,29 @@ func RemoveDeviceFiles(ctx *gin.Context) {
if !ok { if !ok {
return return
} }
if len(form.Files) == 0 {
ctx.AbortWithStatusJSON(http.StatusBadRequest, modules.Packet{Code: -1, Msg: `${i18n|invalidParameter}`})
return
}
trigger := utils.GetStrUUID() trigger := utils.GetStrUUID()
common.SendPackByUUID(modules.Packet{Code: 0, Act: `removeFiles`, Data: gin.H{`files`: form.Files}, Event: trigger}, target) common.SendPackByUUID(modules.Packet{Code: 0, Act: `removeFiles`, Data: gin.H{`files`: form.Files}, Event: trigger}, target)
ok = common.AddEventOnce(func(p modules.Packet, _ *melody.Session) { ok = common.AddEventOnce(func(p modules.Packet, _ *melody.Session) {
if p.Code != 0 { if p.Code != 0 {
common.Warn(ctx, `REMOVE_FILES`, `fail`, p.Msg, map[string]any{
`files`: form.Files,
})
ctx.AbortWithStatusJSON(http.StatusInternalServerError, modules.Packet{Code: 1, Msg: p.Msg}) ctx.AbortWithStatusJSON(http.StatusInternalServerError, modules.Packet{Code: 1, Msg: p.Msg})
} else { } else {
common.Info(ctx, `REMOVE_FILES`, `success`, ``, map[string]any{
`files`: form.Files,
})
ctx.JSON(http.StatusOK, modules.Packet{Code: 0}) ctx.JSON(http.StatusOK, modules.Packet{Code: 0})
} }
}, target, trigger, 5*time.Second) }, target, trigger, 5*time.Second)
if !ok { if !ok {
common.Warn(ctx, `REMOVE_FILES`, `fail`, `timeout`, map[string]any{
`files`: form.Files,
})
ctx.AbortWithStatusJSON(http.StatusGatewayTimeout, modules.Packet{Code: 1, Msg: `${i18n|responseTimeout}`}) ctx.AbortWithStatusJSON(http.StatusGatewayTimeout, modules.Packet{Code: 1, Msg: `${i18n|responseTimeout}`})
} }
} }
@@ -124,11 +137,14 @@ func GetDeviceFiles(ctx *gin.Context) {
wait := make(chan bool) wait := make(chan bool)
called := false called := false
common.AddEvent(func(p modules.Packet, _ *melody.Session) { common.AddEvent(func(p modules.Packet, _ *melody.Session) {
wait <- false
called = true called = true
bridge.RemoveBridge(bridgeID) bridge.RemoveBridge(bridgeID)
common.RemoveEvent(trigger) common.RemoveEvent(trigger)
common.Warn(ctx, `READ_FILES`, `fail`, p.Msg, map[string]any{
`files`: form.Files,
})
ctx.AbortWithStatusJSON(http.StatusInternalServerError, modules.Packet{Code: 1, Msg: p.Msg}) ctx.AbortWithStatusJSON(http.StatusInternalServerError, modules.Packet{Code: 1, Msg: p.Msg})
wait <- false
}, target, trigger) }, target, trigger)
instance := bridge.AddBridgeWithDst(nil, bridgeID, ctx) instance := bridge.AddBridgeWithDst(nil, bridgeID, ctx)
instance.OnPush = func(bridge *bridge.Bridge) { instance.OnPush = func(bridge *bridge.Bridge) {
@@ -177,6 +193,11 @@ func GetDeviceFiles(ctx *gin.Context) {
} }
} }
instance.OnFinish = func(bridge *bridge.Bridge) { instance.OnFinish = func(bridge *bridge.Bridge) {
if called {
common.Info(ctx, `READ_FILES`, `success`, ``, map[string]any{
`files`: form.Files,
})
}
wait <- false wait <- false
} }
select { select {
@@ -185,6 +206,9 @@ func GetDeviceFiles(ctx *gin.Context) {
if !called { if !called {
bridge.RemoveBridge(bridgeID) bridge.RemoveBridge(bridgeID)
common.RemoveEvent(trigger) common.RemoveEvent(trigger)
common.Warn(ctx, `READ_FILES`, `fail`, `timeout`, map[string]any{
`files`: form.Files,
})
ctx.AbortWithStatusJSON(http.StatusGatewayTimeout, modules.Packet{Code: 1, Msg: `${i18n|responseTimeout}`}) ctx.AbortWithStatusJSON(http.StatusGatewayTimeout, modules.Packet{Code: 1, Msg: `${i18n|responseTimeout}`})
} else { } else {
<-wait <-wait
@@ -203,6 +227,10 @@ func GetDeviceTextFile(ctx *gin.Context) {
if !ok { if !ok {
return return
} }
if len(form.File) == 0 {
ctx.AbortWithStatusJSON(http.StatusBadRequest, modules.Packet{Code: -1, Msg: `${i18n|invalidParameter}`})
return
}
bridgeID := utils.GetStrUUID() bridgeID := utils.GetStrUUID()
trigger := utils.GetStrUUID() trigger := utils.GetStrUUID()
common.SendPackByUUID(modules.Packet{Code: 0, Act: `uploadTextFile`, Data: gin.H{ common.SendPackByUUID(modules.Packet{Code: 0, Act: `uploadTextFile`, Data: gin.H{
@@ -212,11 +240,14 @@ func GetDeviceTextFile(ctx *gin.Context) {
wait := make(chan bool) wait := make(chan bool)
called := false called := false
common.AddEvent(func(p modules.Packet, _ *melody.Session) { common.AddEvent(func(p modules.Packet, _ *melody.Session) {
wait <- false
called = true called = true
bridge.RemoveBridge(bridgeID) bridge.RemoveBridge(bridgeID)
common.RemoveEvent(trigger) common.RemoveEvent(trigger)
common.Warn(ctx, `READ_TEXT_FILE`, `fail`, p.Msg, map[string]any{
`file`: form.File,
})
ctx.AbortWithStatusJSON(http.StatusInternalServerError, modules.Packet{Code: 1, Msg: p.Msg}) ctx.AbortWithStatusJSON(http.StatusInternalServerError, modules.Packet{Code: 1, Msg: p.Msg})
wait <- false
}, target, trigger) }, target, trigger)
instance := bridge.AddBridgeWithDst(nil, bridgeID, ctx) instance := bridge.AddBridgeWithDst(nil, bridgeID, ctx)
instance.OnPush = func(bridge *bridge.Bridge) { instance.OnPush = func(bridge *bridge.Bridge) {
@@ -239,6 +270,11 @@ func GetDeviceTextFile(ctx *gin.Context) {
ctx.Status(http.StatusOK) ctx.Status(http.StatusOK)
} }
instance.OnFinish = func(bridge *bridge.Bridge) { instance.OnFinish = func(bridge *bridge.Bridge) {
if called {
common.Info(ctx, `READ_TEXT_FILE`, `success`, ``, map[string]any{
`file`: form.File,
})
}
wait <- false wait <- false
} }
select { select {
@@ -247,6 +283,9 @@ func GetDeviceTextFile(ctx *gin.Context) {
if !called { if !called {
bridge.RemoveBridge(bridgeID) bridge.RemoveBridge(bridgeID)
common.RemoveEvent(trigger) common.RemoveEvent(trigger)
common.Warn(ctx, `READ_TEXT_FILE`, `fail`, `timeout`, map[string]any{
`file`: form.File,
})
ctx.AbortWithStatusJSON(http.StatusGatewayTimeout, modules.Packet{Code: 1, Msg: `${i18n|responseTimeout}`}) ctx.AbortWithStatusJSON(http.StatusGatewayTimeout, modules.Packet{Code: 1, Msg: `${i18n|responseTimeout}`})
} else { } else {
<-wait <-wait
@@ -266,18 +305,28 @@ func UploadToDevice(ctx *gin.Context) {
if !ok { if !ok {
return return
} }
if len(form.File) == 0 || len(form.Path) == 0 {
ctx.AbortWithStatusJSON(http.StatusBadRequest, modules.Packet{Code: -1, Msg: `${i18n|invalidParameter}`})
return
}
bridgeID := utils.GetStrUUID() bridgeID := utils.GetStrUUID()
trigger := utils.GetStrUUID() trigger := utils.GetStrUUID()
wait := make(chan bool) wait := make(chan bool)
called := false called := false
response := false response := false
fileDest := path.Join(form.Path, form.File)
fileSize := ctx.Request.ContentLength
common.AddEvent(func(p modules.Packet, _ *melody.Session) { common.AddEvent(func(p modules.Packet, _ *melody.Session) {
wait <- false
called = true called = true
response = true response = true
bridge.RemoveBridge(bridgeID) bridge.RemoveBridge(bridgeID)
common.RemoveEvent(trigger) common.RemoveEvent(trigger)
common.Warn(ctx, `UPLOAD_FILE`, `fail`, p.Msg, map[string]any{
`dest`: fileDest,
`size`: fileSize,
})
ctx.AbortWithStatusJSON(http.StatusInternalServerError, modules.Packet{Code: 1, Msg: p.Msg}) ctx.AbortWithStatusJSON(http.StatusInternalServerError, modules.Packet{Code: 1, Msg: p.Msg})
wait <- false
}, target, trigger) }, target, trigger)
instance := bridge.AddBridgeWithSrc(nil, bridgeID, ctx) instance := bridge.AddBridgeWithSrc(nil, bridgeID, ctx)
instance.OnPull = func(bridge *bridge.Bridge) { instance.OnPull = func(bridge *bridge.Bridge) {
@@ -293,6 +342,12 @@ func UploadToDevice(ctx *gin.Context) {
dst.Header(`Content-Disposition`, fmt.Sprintf(`attachment; filename="%s"; filename*=UTF-8''%s`, form.File, url.PathEscape(form.File))) dst.Header(`Content-Disposition`, fmt.Sprintf(`attachment; filename="%s"; filename*=UTF-8''%s`, form.File, url.PathEscape(form.File)))
} }
instance.OnFinish = func(bridge *bridge.Bridge) { instance.OnFinish = func(bridge *bridge.Bridge) {
if called {
common.Info(ctx, `UPLOAD_FILE`, `success`, ``, map[string]any{
`dest`: fileDest,
`size`: fileSize,
})
}
wait <- false wait <- false
} }
common.SendPackByUUID(modules.Packet{Code: 0, Act: `fetchFile`, Data: gin.H{ common.SendPackByUUID(modules.Packet{Code: 0, Act: `fetchFile`, Data: gin.H{
@@ -310,6 +365,10 @@ func UploadToDevice(ctx *gin.Context) {
bridge.RemoveBridge(bridgeID) bridge.RemoveBridge(bridgeID)
common.RemoveEvent(trigger) common.RemoveEvent(trigger)
if !response { if !response {
common.Warn(ctx, `UPLOAD_FILE`, `fail`, `timeout`, map[string]any{
`dest`: fileDest,
`size`: fileSize,
})
ctx.AbortWithStatusJSON(http.StatusGatewayTimeout, modules.Packet{Code: 1, Msg: `${i18n|responseTimeout}`}) ctx.AbortWithStatusJSON(http.StatusGatewayTimeout, modules.Packet{Code: 1, Msg: `${i18n|responseTimeout}`})
} }
} else { } else {

View File

@@ -86,7 +86,7 @@ func GenerateClient(ctx *gin.Context) {
return return
} }
clientUUID := utils.GetUUID() clientUUID := utils.GetUUID()
clientKey, err := common.EncAES(clientUUID, config.Config.StdSalt) clientKey, err := common.EncAES(clientUUID, config.Config.SaltBytes)
if err != nil { if err != nil {
ctx.AbortWithStatusJSON(http.StatusInternalServerError, modules.Packet{Code: 1, Msg: `${i18n|configGenerateFailed}`}) ctx.AbortWithStatusJSON(http.StatusInternalServerError, modules.Packet{Code: 1, Msg: `${i18n|configGenerateFailed}`})
return return

View File

@@ -29,6 +29,7 @@ func InitRouter(ctx *gin.RouterGroup) {
group.POST(`/device/file/list`, file.ListDeviceFiles) group.POST(`/device/file/list`, file.ListDeviceFiles)
group.POST(`/device/file/text`, file.GetDeviceTextFile) group.POST(`/device/file/text`, file.GetDeviceTextFile)
group.POST(`/device/file/get`, file.GetDeviceFiles) group.POST(`/device/file/get`, file.GetDeviceFiles)
group.POST(`/device/exec`, utility.ExecDeviceCmd)
group.POST(`/device/list`, utility.GetDevices) group.POST(`/device/list`, utility.GetDevices)
group.POST(`/device/:act`, utility.CallDevice) group.POST(`/device/:act`, utility.CallDevice)
group.POST(`/client/check`, generate.CheckClient) group.POST(`/client/check`, generate.CheckClient)

View File

@@ -46,11 +46,20 @@ func KillDeviceProcess(ctx *gin.Context) {
ok = common.AddEventOnce(func(p modules.Packet, _ *melody.Session) { ok = common.AddEventOnce(func(p modules.Packet, _ *melody.Session) {
if p.Code != 0 { if p.Code != 0 {
ctx.AbortWithStatusJSON(http.StatusInternalServerError, modules.Packet{Code: 1, Msg: p.Msg}) ctx.AbortWithStatusJSON(http.StatusInternalServerError, modules.Packet{Code: 1, Msg: p.Msg})
common.Warn(ctx, `KILL_PROCESS`, `fail`, p.Msg, map[string]any{
`pid`: form.Pid,
})
} else { } else {
ctx.JSON(http.StatusOK, modules.Packet{Code: 0}) ctx.JSON(http.StatusOK, modules.Packet{Code: 0})
common.Info(ctx, `KILL_PROCESS`, `success`, ``, map[string]any{
`pid`: form.Pid,
})
} }
}, target, trigger, 5*time.Second) }, target, trigger, 5*time.Second)
if !ok { if !ok {
ctx.AbortWithStatusJSON(http.StatusGatewayTimeout, modules.Packet{Code: 1, Msg: `${i18n|responseTimeout}`}) ctx.AbortWithStatusJSON(http.StatusGatewayTimeout, modules.Packet{Code: 1, Msg: `${i18n|responseTimeout}`})
common.Warn(ctx, `KILL_PROCESS`, `fail`, `timeout`, map[string]any{
`pid`: form.Pid,
})
} }
} }

View File

@@ -24,11 +24,12 @@ func GetScreenshot(ctx *gin.Context) {
called := false called := false
common.SendPackByUUID(modules.Packet{Code: 0, Act: `screenshot`, Data: gin.H{`bridge`: bridgeID}, Event: trigger}, target) common.SendPackByUUID(modules.Packet{Code: 0, Act: `screenshot`, Data: gin.H{`bridge`: bridgeID}, Event: trigger}, target)
common.AddEvent(func(p modules.Packet, _ *melody.Session) { common.AddEvent(func(p modules.Packet, _ *melody.Session) {
wait <- false
called = true called = true
bridge.RemoveBridge(bridgeID) bridge.RemoveBridge(bridgeID)
common.RemoveEvent(trigger) common.RemoveEvent(trigger)
ctx.AbortWithStatusJSON(http.StatusInternalServerError, modules.Packet{Code: 1, Msg: p.Msg}) ctx.AbortWithStatusJSON(http.StatusInternalServerError, modules.Packet{Code: 1, Msg: p.Msg})
common.Warn(ctx, `TAKE_SCREENSHOT`, `fail`, p.Msg, nil)
wait <- false
}, target, trigger) }, target, trigger)
instance := bridge.AddBridgeWithDst(nil, bridgeID, ctx) instance := bridge.AddBridgeWithDst(nil, bridgeID, ctx)
instance.OnPush = func(bridge *bridge.Bridge) { instance.OnPush = func(bridge *bridge.Bridge) {
@@ -37,6 +38,9 @@ func GetScreenshot(ctx *gin.Context) {
ctx.Header(`Content-Type`, `image/png`) ctx.Header(`Content-Type`, `image/png`)
} }
instance.OnFinish = func(bridge *bridge.Bridge) { instance.OnFinish = func(bridge *bridge.Bridge) {
if called {
common.Info(ctx, `TAKE_SCREENSHOT`, `success`, ``, nil)
}
wait <- false wait <- false
} }
select { select {
@@ -46,6 +50,7 @@ func GetScreenshot(ctx *gin.Context) {
bridge.RemoveBridge(bridgeID) bridge.RemoveBridge(bridgeID)
common.RemoveEvent(trigger) common.RemoveEvent(trigger)
ctx.AbortWithStatusJSON(http.StatusGatewayTimeout, modules.Packet{Code: 1, Msg: `${i18n|responseTimeout}`}) ctx.AbortWithStatusJSON(http.StatusGatewayTimeout, modules.Packet{Code: 1, Msg: `${i18n|responseTimeout}`})
common.Warn(ctx, `TAKE_SCREENSHOT`, `fail`, `timeout`, nil)
} else { } else {
<-wait <-wait
} }

View File

@@ -9,11 +9,11 @@ import (
"encoding/hex" "encoding/hex"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"net/http" "net/http"
"reflect"
) )
type terminal struct { type terminal struct {
uuid string uuid string
event string
device string device string
session *melody.Session session *melody.Session
deviceConn *melody.Session deviceConn *melody.Session
@@ -58,7 +58,7 @@ func InitTerminal(ctx *gin.Context) {
terminalSessions.HandleRequestWithKeys(ctx.Writer, ctx.Request, nil, gin.H{ terminalSessions.HandleRequestWithKeys(ctx.Writer, ctx.Request, nil, gin.H{
`Secret`: secret, `Secret`: secret,
`Device`: device, `Device`: device,
`LastPack`: common.Unix, `LastPack`: utils.Unix,
}) })
} }
@@ -75,8 +75,15 @@ func terminalEventWrapper(terminal *terminal) common.EventCallback {
msg += `${i18n|unknownError}` msg += `${i18n|unknownError}`
} }
sendPack(modules.Packet{Act: `warn`, Msg: msg}, terminal.session) sendPack(modules.Packet{Act: `warn`, Msg: msg}, terminal.session)
common.RemoveEvent(terminal.event) common.RemoveEvent(terminal.uuid)
terminal.session.Close() terminal.session.Close()
common.Warn(terminal.session, `TERMINAL_INIT`, `fail`, msg, map[string]any{
`deviceConn`: terminal.deviceConn,
})
} else {
common.Info(terminal.session, `TERMINAL_INIT`, `success`, ``, map[string]any{
`deviceConn`: terminal.deviceConn,
})
} }
return return
} }
@@ -86,8 +93,11 @@ func terminalEventWrapper(terminal *terminal) common.EventCallback {
msg = pack.Msg msg = pack.Msg
} }
sendPack(modules.Packet{Act: `warn`, Msg: msg}, terminal.session) sendPack(modules.Packet{Act: `warn`, Msg: msg}, terminal.session)
common.RemoveEvent(terminal.event) common.RemoveEvent(terminal.uuid)
terminal.session.Close() terminal.session.Close()
common.Info(terminal.session, `TERMINAL_QUIT`, ``, msg, map[string]any{
`deviceConn`: terminal.deviceConn,
})
return return
} }
if pack.Act == `outputTerminal` { if pack.Act == `outputTerminal` {
@@ -123,19 +133,20 @@ func onTerminalConnect(session *melody.Session) {
return return
} }
termUUID := utils.GetStrUUID() termUUID := utils.GetStrUUID()
eventUUID := utils.GetStrUUID()
terminal := &terminal{ terminal := &terminal{
uuid: termUUID, uuid: termUUID,
event: eventUUID,
device: device.(string), device: device.(string),
session: session, session: session,
deviceConn: deviceConn, deviceConn: deviceConn,
} }
session.Set(`Terminal`, terminal) session.Set(`Terminal`, terminal)
common.AddEvent(terminalEventWrapper(terminal), connUUID, eventUUID) common.AddEvent(terminalEventWrapper(terminal), connUUID, termUUID)
common.SendPack(modules.Packet{Act: `initTerminal`, Data: gin.H{ common.SendPack(modules.Packet{Act: `initTerminal`, Data: gin.H{
`terminal`: termUUID, `terminal`: termUUID,
}, Event: eventUUID}, deviceConn) }, Event: termUUID}, deviceConn)
common.Info(terminal.session, `TERMINAL_CONN`, `success`, ``, map[string]any{
`deviceConn`: terminal.deviceConn,
})
} }
func onTerminalMessage(session *melody.Session, data []byte) { func onTerminalMessage(session *melody.Session, data []byte) {
@@ -151,16 +162,21 @@ func onTerminalMessage(session *melody.Session, data []byte) {
return return
} }
terminal := val.(*terminal) terminal := val.(*terminal)
session.Set(`LastPack`, common.Unix) session.Set(`LastPack`, utils.Unix)
if pack.Act == `inputTerminal` { if pack.Act == `inputTerminal` {
if pack.Data == nil { if pack.Data == nil {
return return
} }
if input, ok := pack.Data[`input`]; ok { if input, ok := pack.GetData(`input`, reflect.String); ok {
rawInput, _ := hex.DecodeString(input.(string))
common.Info(terminal.session, `TERMINAL_INPUT`, ``, ``, map[string]any{
`deviceConn`: terminal.deviceConn,
`input`: utils.BytesToString(rawInput),
})
common.SendPack(modules.Packet{Act: `inputTerminal`, Data: gin.H{ common.SendPack(modules.Packet{Act: `inputTerminal`, Data: gin.H{
`input`: input, `input`: input,
`terminal`: terminal.uuid, `terminal`: terminal.uuid,
}, Event: terminal.event}, terminal.deviceConn) }, Event: terminal.uuid}, terminal.deviceConn)
} }
return return
} }
@@ -174,7 +190,7 @@ func onTerminalMessage(session *melody.Session, data []byte) {
`width`: width, `width`: width,
`height`: height, `height`: height,
`terminal`: terminal.uuid, `terminal`: terminal.uuid,
}, Event: terminal.event}, terminal.deviceConn) }, Event: terminal.uuid}, terminal.deviceConn)
} }
} }
return return
@@ -183,9 +199,12 @@ func onTerminalMessage(session *melody.Session, data []byte) {
if pack.Data == nil { if pack.Data == nil {
return return
} }
common.Info(terminal.session, `TERMINAL_KILL`, `success`, ``, map[string]any{
`deviceConn`: terminal.deviceConn,
})
common.SendPack(modules.Packet{Act: `killTerminal`, Data: gin.H{ common.SendPack(modules.Packet{Act: `killTerminal`, Data: gin.H{
`terminal`: terminal.uuid, `terminal`: terminal.uuid,
}, Event: terminal.event}, terminal.deviceConn) }, Event: terminal.uuid}, terminal.deviceConn)
return return
} }
if pack.Act == `ping` { if pack.Act == `ping` {
@@ -194,13 +213,14 @@ func onTerminalMessage(session *melody.Session, data []byte) {
} }
common.SendPack(modules.Packet{Act: `pingTerminal`, Data: gin.H{ common.SendPack(modules.Packet{Act: `pingTerminal`, Data: gin.H{
`terminal`: terminal.uuid, `terminal`: terminal.uuid,
}, Event: terminal.event}, terminal.deviceConn) }, Event: terminal.uuid}, terminal.deviceConn)
return return
} }
session.Close() session.Close()
} }
func onTerminalDisconnect(session *melody.Session) { func onTerminalDisconnect(session *melody.Session) {
common.Info(session, `TERMINAL_CLOSE`, `success`, ``, nil)
val, ok := session.Get(`Terminal`) val, ok := session.Get(`Terminal`)
if !ok { if !ok {
return return
@@ -211,8 +231,8 @@ func onTerminalDisconnect(session *melody.Session) {
} }
common.SendPack(modules.Packet{Act: `killTerminal`, Data: gin.H{ common.SendPack(modules.Packet{Act: `killTerminal`, Data: gin.H{
`terminal`: terminal.uuid, `terminal`: terminal.uuid,
}, Event: terminal.event}, terminal.deviceConn) }, Event: terminal.uuid}, terminal.deviceConn)
common.RemoveEvent(terminal.event) common.RemoveEvent(terminal.uuid)
session.Set(`Terminal`, nil) session.Set(`Terminal`, nil)
terminal = nil terminal = nil
} }

View File

@@ -7,6 +7,7 @@ import (
"Spark/utils" "Spark/utils"
"Spark/utils/melody" "Spark/utils/melody"
"bytes" "bytes"
"context"
"crypto/aes" "crypto/aes"
"crypto/cipher" "crypto/cipher"
"fmt" "fmt"
@@ -40,6 +41,7 @@ func CheckForm(ctx *gin.Context, form any) (string, bool) {
ctx.AbortWithStatusJSON(http.StatusBadGateway, modules.Packet{Code: 1, Msg: `${i18n|deviceNotExists}`}) ctx.AbortWithStatusJSON(http.StatusBadGateway, modules.Packet{Code: 1, Msg: `${i18n|deviceNotExists}`})
return ``, false return ``, false
} }
ctx.Request = ctx.Request.WithContext(context.WithValue(ctx.Request.Context(), `ConnUUID`, connUUID))
return connUUID, true return connUUID, true
} }
@@ -88,6 +90,12 @@ func OnDevicePack(data []byte, session *melody.Session) error {
common.Devices.Remove(exSession) common.Devices.Remove(exSession)
} }
common.Devices.Set(session.UUID, &pack.Device) common.Devices.Set(session.UUID, &pack.Device)
common.Info(nil, `CLIENT_ONLINE`, ``, ``, map[string]any{
`device`: map[string]any{
`name`: pack.Device.Hostname,
`ip`: pack.Device.WAN,
},
})
} else { } else {
val, ok := common.Devices.Get(session.UUID) val, ok := common.Devices.Get(session.UUID)
if ok { if ok {
@@ -111,36 +119,84 @@ func CheckUpdate(ctx *gin.Context) {
Commit string `form:"commit" binding:"required"` Commit string `form:"commit" binding:"required"`
} }
if err := ctx.ShouldBind(&form); err != nil { if err := ctx.ShouldBind(&form); err != nil {
golog.Error(err)
ctx.AbortWithStatusJSON(http.StatusBadRequest, modules.Packet{Code: -1, Msg: `${i18n|invalidParameter}`}) ctx.AbortWithStatusJSON(http.StatusBadRequest, modules.Packet{Code: -1, Msg: `${i18n|invalidParameter}`})
return return
} }
if form.Commit == config.COMMIT { if form.Commit == config.COMMIT {
ctx.JSON(http.StatusOK, modules.Packet{Code: 0}) ctx.JSON(http.StatusOK, modules.Packet{Code: 0})
common.Warn(ctx, `CLIENT_UPDATE`, `success`, `latest`, map[string]any{
`client`: map[string]any{
`os`: form.OS,
`arch`: form.Arch,
`commit`: form.Commit,
},
`server`: config.COMMIT,
})
return return
} }
tpl, err := os.Open(fmt.Sprintf(config.BuiltPath, form.OS, form.Arch)) tpl, err := os.Open(fmt.Sprintf(config.BuiltPath, form.OS, form.Arch))
if err != nil { if err != nil {
ctx.AbortWithStatusJSON(http.StatusNotFound, modules.Packet{Code: 1, Msg: `${i18n|osOrArchNotPrebuilt}`}) ctx.AbortWithStatusJSON(http.StatusNotFound, modules.Packet{Code: 1, Msg: `${i18n|osOrArchNotPrebuilt}`})
common.Warn(ctx, `CLIENT_UPDATE`, `fail`, `no prebuild asset`, map[string]any{
`client`: map[string]any{
`os`: form.OS,
`arch`: form.Arch,
`commit`: form.Commit,
},
`server`: config.COMMIT,
})
return return
} }
const MaxBodySize = 384 // This is size of client config buffer. const MaxBodySize = 384 // This is size of client config buffer.
if ctx.Request.ContentLength > MaxBodySize { if ctx.Request.ContentLength > MaxBodySize {
ctx.AbortWithStatusJSON(http.StatusRequestEntityTooLarge, modules.Packet{Code: 1}) ctx.AbortWithStatusJSON(http.StatusRequestEntityTooLarge, modules.Packet{Code: 1})
common.Warn(ctx, `CLIENT_UPDATE`, `fail`, `config too large`, map[string]any{
`client`: map[string]any{
`os`: form.OS,
`arch`: form.Arch,
`commit`: form.Commit,
},
`server`: config.COMMIT,
})
return return
} }
body, err := ctx.GetRawData() body, err := ctx.GetRawData()
if err != nil { if err != nil {
ctx.AbortWithStatusJSON(http.StatusBadRequest, modules.Packet{Code: 1}) ctx.AbortWithStatusJSON(http.StatusBadRequest, modules.Packet{Code: 1})
common.Warn(ctx, `CLIENT_UPDATE`, `fail`, `read config fail`, map[string]any{
`client`: map[string]any{
`os`: form.OS,
`arch`: form.Arch,
`commit`: form.Commit,
},
`server`: config.COMMIT,
})
return return
} }
session := common.CheckClientReq(ctx) session := common.CheckClientReq(ctx)
if session == nil { if session == nil {
ctx.AbortWithStatusJSON(http.StatusUnauthorized, modules.Packet{Code: 1}) ctx.AbortWithStatusJSON(http.StatusUnauthorized, modules.Packet{Code: 1})
common.Warn(ctx, `CLIENT_UPDATE`, `fail`, `check config fail`, map[string]any{
`client`: map[string]any{
`os`: form.OS,
`arch`: form.Arch,
`commit`: form.Commit,
},
`server`: config.COMMIT,
})
return return
} }
common.Info(ctx, `CLIENT_UPDATE`, `success`, `updating`, map[string]any{
`client`: map[string]any{
`os`: form.OS,
`arch`: form.Arch,
`commit`: form.Commit,
},
`server`: config.COMMIT,
})
ctx.Header(`Spark-Commit`, config.COMMIT) ctx.Header(`Spark-Commit`, config.COMMIT)
ctx.Header(`Accept-Ranges`, `none`) ctx.Header(`Accept-Ranges`, `none`)
ctx.Header(`Content-Transfer-Encoding`, `binary`) ctx.Header(`Content-Transfer-Encoding`, `binary`)
@@ -171,6 +227,46 @@ func CheckUpdate(ctx *gin.Context) {
} }
} }
// ExecDeviceCmd execute command on device.
func ExecDeviceCmd(ctx *gin.Context) {
var form struct {
Cmd string `json:"cmd" yaml:"cmd" form:"cmd" binding:"required"`
Args string `json:"args" yaml:"args" form:"args"`
}
target, ok := CheckForm(ctx, &form)
if !ok {
return
}
if len(form.Cmd) == 0 {
ctx.AbortWithStatusJSON(http.StatusBadRequest, modules.Packet{Code: -1, Msg: `${i18n|invalidParameter}`})
return
}
trigger := utils.GetStrUUID()
common.SendPackByUUID(modules.Packet{Code: 0, Act: `execCommand`, Data: gin.H{`cmd`: form.Cmd, `args`: form.Args}, Event: trigger}, target)
ok = common.AddEventOnce(func(p modules.Packet, _ *melody.Session) {
if p.Code != 0 {
common.Warn(ctx, `EXEC_COMMAND`, `fail`, p.Msg, map[string]any{
`cmd`: form.Cmd,
`args`: form.Args,
})
ctx.AbortWithStatusJSON(http.StatusInternalServerError, modules.Packet{Code: 1, Msg: p.Msg})
} else {
common.Info(ctx, `EXEC_COMMAND`, `success`, ``, map[string]any{
`cmd`: form.Cmd,
`args`: form.Args,
})
ctx.JSON(http.StatusOK, modules.Packet{Code: 0})
}
}, target, trigger, 5*time.Second)
if !ok {
common.Warn(ctx, `EXEC_COMMAND`, `fail`, `timeout`, map[string]any{
`cmd`: form.Cmd,
`args`: form.Args,
})
ctx.AbortWithStatusJSON(http.StatusGatewayTimeout, modules.Packet{Code: 1, Msg: `${i18n|responseTimeout}`})
}
}
// GetDevices will return all info about all clients. // GetDevices will return all info about all clients.
func GetDevices(ctx *gin.Context) { func GetDevices(ctx *gin.Context) {
devices := map[string]any{} devices := map[string]any{}
@@ -199,6 +295,9 @@ func CallDevice(ctx *gin.Context) {
} }
} }
if !ok { if !ok {
common.Warn(ctx, `CALL_DEVICE`, `fail`, `invalid act`, map[string]any{
`act`: act,
})
ctx.AbortWithStatusJSON(http.StatusBadRequest, modules.Packet{Code: -1, Msg: `${i18n|invalidParameter}`}) ctx.AbortWithStatusJSON(http.StatusBadRequest, modules.Packet{Code: -1, Msg: `${i18n|invalidParameter}`})
return return
} }
@@ -211,14 +310,23 @@ func CallDevice(ctx *gin.Context) {
common.SendPackByUUID(modules.Packet{Act: act, Event: trigger}, connUUID) common.SendPackByUUID(modules.Packet{Act: act, Event: trigger}, connUUID)
ok = common.AddEventOnce(func(p modules.Packet, _ *melody.Session) { ok = common.AddEventOnce(func(p modules.Packet, _ *melody.Session) {
if p.Code != 0 { if p.Code != 0 {
common.Warn(ctx, `CALL_DEVICE`, `fail`, p.Msg, map[string]any{
`act`: act,
})
ctx.AbortWithStatusJSON(http.StatusInternalServerError, modules.Packet{Code: 1, Msg: p.Msg}) ctx.AbortWithStatusJSON(http.StatusInternalServerError, modules.Packet{Code: 1, Msg: p.Msg})
} else { } else {
common.Info(ctx, `CALL_DEVICE`, `success`, ``, map[string]any{
`act`: act,
})
ctx.JSON(http.StatusOK, modules.Packet{Code: 0}) ctx.JSON(http.StatusOK, modules.Packet{Code: 0})
} }
}, connUUID, trigger, 5*time.Second) }, connUUID, trigger, 5*time.Second)
if !ok { if !ok {
//This means the client is offline. //This means the client is offline.
//So we take this as a success. //So we take this as a success.
common.Info(ctx, `CALL_DEVICE`, `success`, ``, map[string]any{
`act`: act,
})
ctx.JSON(http.StatusOK, modules.Packet{Code: 0}) ctx.JSON(http.StatusOK, modules.Packet{Code: 0})
} }
} }

View File

@@ -28,51 +28,24 @@ import (
"Spark/utils/melody" "Spark/utils/melody"
"net/http" "net/http"
"github.com/gin-contrib/pprof"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/kataras/golog" "github.com/kataras/golog"
) )
var blocked = cmap.New()
var lastRequest = time.Now().Unix() var lastRequest = time.Now().Unix()
func main() { func main() {
golog.SetTimeFormat(`2006/01/02 15:04:05`)
data, err := os.ReadFile(`./Config.json`)
if err != nil {
golog.Fatal(`Failed to read config file: `, err)
return
}
err = utils.JSON.Unmarshal(data, &config.Config)
if err != nil {
golog.Fatal(`Failed to parse config file: `, err)
return
}
if len(config.Config.Salt) > 24 {
golog.Fatal(`Length of Salt should be less than 24.`)
return
}
config.Config.StdSalt = []byte(config.Config.Salt)
config.Config.StdSalt = append(config.Config.StdSalt, bytes.Repeat([]byte{25}, 24)...)
config.Config.StdSalt = config.Config.StdSalt[:24]
webFS, err := fs.NewWithNamespace(`web`) webFS, err := fs.NewWithNamespace(`web`)
if err != nil { if err != nil {
golog.Fatal(`Failed to load static resources: `, err) common.Fatal(nil, `LOAD_STATIC_RES`, `fail`, err.Error(), nil)
return return
} }
if config.Config.Debug.Gin { gin.SetMode(gin.ReleaseMode)
gin.SetMode(gin.DebugMode)
} else {
gin.SetMode(gin.ReleaseMode)
}
app := gin.New() app := gin.New()
app.Use(gin.Recovery()) app.Use(gin.Recovery())
if config.Config.Debug.Pprof {
pprof.Register(app)
}
{ {
handler.AuthHandler = authCheck() handler.AuthHandler = checkAuth()
handler.InitRouter(app.Group(`/api`)) handler.InitRouter(app.Group(`/api`))
app.Any(`/ws`, wsHandshake) app.Any(`/ws`, wsHandshake)
app.NoRoute(handler.AuthHandler, func(ctx *gin.Context) { app.NoRoute(handler.AuthHandler, func(ctx *gin.Context) {
@@ -82,7 +55,7 @@ func main() {
}) })
} }
common.Melody.Config.MaxMessageSize = 32768 + 1024 common.Melody.Config.MaxMessageSize = common.MaxMessageSize
common.Melody.HandleConnect(wsOnConnect) common.Melody.HandleConnect(wsOnConnect)
common.Melody.HandleMessage(wsOnMessage) common.Melody.HandleMessage(wsOnMessage)
common.Melody.HandleMessageBinary(wsOnMessageBinary) common.Melody.HandleMessageBinary(wsOnMessageBinary)
@@ -93,33 +66,43 @@ func main() {
Addr: config.Config.Listen, Addr: config.Config.Listen,
Handler: app, Handler: app,
ConnContext: func(ctx context.Context, c net.Conn) context.Context { ConnContext: func(ctx context.Context, c net.Conn) context.Context {
return context.WithValue(ctx, `Conn`, c) ctx = context.WithValue(ctx, `Conn`, c)
ctx = context.WithValue(ctx, `ClientIP`, common.GetAddrIP(c.RemoteAddr()))
return ctx
}, },
} }
go func() { {
if err = srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { go func() {
golog.Fatal(`Failed to bind address: `, err) err = srv.ListenAndServe()
}()
if err != nil {
common.Fatal(nil, `SERVICE_INIT`, `fail`, err.Error(), nil)
} else {
common.Info(nil, `SERVICE_INIT`, ``, ``, map[string]any{
`listen`: config.Config.Listen,
})
} }
}() }
quit := make(chan os.Signal, 3) quit := make(chan os.Signal, 3)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit <-quit
golog.Warn(`Server is shutting down ...`) common.Warn(nil, `SERVICE_EXITING`, ``, ``, nil)
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel() defer cancel()
if err := srv.Shutdown(ctx); err != nil { if err := srv.Shutdown(ctx); err != nil {
golog.Fatal(`Server shutdown: `, err) common.Warn(nil, `SERVICE_EXIT`, `error`, err.Error(), nil)
} }
<-ctx.Done() <-ctx.Done()
golog.Info(`Server exited.`) common.Warn(nil, `SERVICE_EXIT`, `success`, ``, nil)
common.CloseLog()
} }
func wsHandshake(ctx *gin.Context) { func wsHandshake(ctx *gin.Context) {
if !ctx.IsWebsocket() { if !ctx.IsWebsocket() {
// When message is too large to transport via websocket, // When message is too large to transport via websocket,
// client will try to send these data via http. // client will try to send these data via http.
const MaxBodySize = 2 << 18 //524288 512KB const MaxBodySize = 2 << 18 // 524288 512KB
if ctx.Request.ContentLength > MaxBodySize { if ctx.Request.ContentLength > MaxBodySize {
ctx.AbortWithStatusJSON(http.StatusRequestEntityTooLarge, modules.Packet{Code: 1}) ctx.AbortWithStatusJSON(http.StatusRequestEntityTooLarge, modules.Packet{Code: 1})
return return
@@ -145,7 +128,7 @@ func wsHandshake(ctx *gin.Context) {
ctx.AbortWithStatus(http.StatusUnauthorized) ctx.AbortWithStatus(http.StatusUnauthorized)
return return
} }
decrypted, err := common.DecAES(clientKey, config.Config.StdSalt) decrypted, err := common.DecAES(clientKey, config.Config.SaltBytes)
if err != nil || !bytes.Equal(decrypted, clientUUID) { if err != nil || !bytes.Equal(decrypted, clientUUID) {
ctx.AbortWithStatus(http.StatusUnauthorized) ctx.AbortWithStatus(http.StatusUnauthorized)
return return
@@ -155,7 +138,7 @@ func wsHandshake(ctx *gin.Context) {
`Secret`: []string{hex.EncodeToString(secret)}, `Secret`: []string{hex.EncodeToString(secret)},
}, gin.H{ }, gin.H{
`Secret`: secret, `Secret`: secret,
`LastPack`: common.Unix, `LastPack`: utils.Unix,
`Address`: common.GetRemoteAddr(ctx), `Address`: common.GetRemoteAddr(ctx),
}) })
if err != nil { if err != nil {
@@ -169,7 +152,7 @@ func wsOnConnect(session *melody.Session) {
pingDevice(session) pingDevice(session)
} }
func wsOnMessage(session *melody.Session, bytes []byte) { func wsOnMessage(session *melody.Session, _ []byte) {
session.Close() session.Close()
} }
@@ -200,7 +183,7 @@ func wsOnMessageBinary(session *melody.Session, data []byte) {
return return
} }
if pack.Act == `report` || pack.Act == `setDevice` { if pack.Act == `report` || pack.Act == `setDevice` {
session.Set(`LastPack`, common.Unix) session.Set(`LastPack`, utils.Unix)
utility.OnDevicePack(data, session) utility.OnDevicePack(data, session)
return return
} }
@@ -209,7 +192,7 @@ func wsOnMessageBinary(session *melody.Session, data []byte) {
return return
} }
common.CallEvent(pack, session) common.CallEvent(pack, session)
session.Set(`LastPack`, common.Unix) session.Set(`LastPack`, utils.Unix)
} }
func wsOnDisconnect(session *melody.Session) { func wsOnDisconnect(session *melody.Session) {
@@ -217,6 +200,18 @@ func wsOnDisconnect(session *melody.Session) {
deviceInfo := val.(*modules.Device) deviceInfo := val.(*modules.Device)
terminal.CloseSessionsByDevice(deviceInfo.ID) terminal.CloseSessionsByDevice(deviceInfo.ID)
desktop.CloseSessionsByDevice(deviceInfo.ID) desktop.CloseSessionsByDevice(deviceInfo.ID)
common.Info(nil, `CLIENT_OFFLINE`, ``, ``, map[string]any{
`device`: map[string]any{
`name`: deviceInfo.Hostname,
`ip`: deviceInfo.WAN,
},
})
} else {
common.Info(nil, `CLIENT_OFFLINE`, ``, ``, map[string]any{
`device`: map[string]any{
`ip`: common.GetAddrIP(session.GetWSConn().UnderlyingConn().RemoteAddr()),
},
})
} }
common.Devices.Remove(session.UUID) common.Devices.Remove(session.UUID)
} }
@@ -231,7 +226,7 @@ func wsHealthCheck(container *melody.Melody) {
var pingInterval int64 = 3 var pingInterval int64 = 3
for range time.NewTicker(3 * time.Second).C { for range time.NewTicker(3 * time.Second).C {
tick += 3 tick += 3
if tick >= common.Unix-lastRequest { if tick >= utils.Unix-lastRequest {
pingInterval = 3 pingInterval = 3
} }
if tick >= 3 && (tick >= pingInterval || tick >= MaxPingInterval) { if tick >= 3 && (tick >= pingInterval || tick >= MaxPingInterval) {
@@ -286,7 +281,7 @@ func pingDevice(s *melody.Session) {
}, s.UUID, trigger, 3*time.Second) }, s.UUID, trigger, 3*time.Second)
} }
func authCheck() gin.HandlerFunc { func checkAuth() gin.HandlerFunc {
// Token as key and update timestamp as value. // Token as key and update timestamp as value.
// Stores authenticated tokens. // Stores authenticated tokens.
tokens := cmap.New() tokens := cmap.New()
@@ -300,19 +295,30 @@ func authCheck() gin.HandlerFunc {
return true return true
}) })
tokens.Remove(queue...) tokens.Remove(queue...)
queue = nil
blocked.IterCb(func(addr string, v any) bool {
if now.Unix() > v.(int64) {
queue = append(queue, addr)
}
return true
})
blocked.Remove(queue...)
} }
}() }()
if config.Config.Auth == nil || len(config.Config.Auth) == 0 { if config.Config.Auth == nil || len(config.Config.Auth) == 0 {
return func(ctx *gin.Context) { return func(ctx *gin.Context) {
lastRequest = common.Unix lastRequest = utils.Unix
ctx.Next() ctx.Next()
} }
} }
auth := gin.BasicAuth(config.Config.Auth) auth := gin.BasicAuth(config.Config.Auth)
return func(ctx *gin.Context) { return func(ctx *gin.Context) {
now := common.Unix now := utils.Unix
passed := false passed := false
if token, err := ctx.Cookie(`Authorization`); err == nil { if token, err := ctx.Cookie(`Authorization`); err == nil {
if tokens.Has(token) { if tokens.Has(token) {
lastRequest = now lastRequest = now
@@ -321,11 +327,32 @@ func authCheck() gin.HandlerFunc {
return return
} }
} }
if !passed { if !passed {
addr := common.GetRealIP(ctx)
if expire, ok := blocked.Get(addr); ok {
if now < expire.(int64) {
ctx.AbortWithStatusJSON(http.StatusTooManyRequests, modules.Packet{Code: 1})
return
}
blocked.Remove(addr)
}
auth(ctx) auth(ctx)
user, _, _ := ctx.Request.BasicAuth()
if ctx.IsAborted() { if ctx.IsAborted() {
blocked.Set(addr, now+1)
user = utils.If(len(user) == 0, `EMPTY`, user)
common.Warn(ctx, `LOGIN_ATTEMPT`, `fail`, ``, map[string]any{
`user`: user,
})
return return
} }
common.Warn(ctx, `LOGIN_ATTEMPT`, `success`, ``, map[string]any{
`user`: user,
})
token := utils.GetStrUUID() token := utils.GetStrUUID()
tokens.Set(token, now) tokens.Set(token, now)
ctx.Header(`Set-Cookie`, fmt.Sprintf(`Authorization=%s; Path=/; HttpOnly`, token)) ctx.Header(`Set-Cookie`, fmt.Sprintf(`Authorization=%s; Path=/; HttpOnly`, token))
@@ -366,7 +393,7 @@ func serveGzip(ctx *gin.Context, statikFS http.FileSystem) bool {
} }
ctx.Header(`Cache-Control`, `max-age=604800`) ctx.Header(`Cache-Control`, `max-age=604800`)
ctx.Header(`ETag`, etag) ctx.Header(`ETag`, etag)
ctx.Header(`Expires`, common.Now.Add(7*24*time.Hour).Format(`Mon, 02 Jan 2006 15:04:05 GMT`)) ctx.Header(`Expires`, utils.Now.Add(7*24*time.Hour).Format(`Mon, 02 Jan 2006 15:04:05 GMT`))
ctx.Writer.Header().Del(`Content-Length`) ctx.Writer.Header().Del(`Content-Length`)
ctx.Header(`Content-Encoding`, `gzip`) ctx.Header(`Content-Encoding`, `gzip`)
@@ -386,7 +413,7 @@ func serveGzip(ctx *gin.Context, statikFS http.FileSystem) bool {
break break
} }
} }
conn.SetWriteDeadline(common.Now.Add(10 * time.Second)) conn.SetWriteDeadline(utils.Now.Add(10 * time.Second))
_, err = ctx.Writer.Write(buf[:n]) _, err = ctx.Writer.Write(buf[:n])
if eof || err != nil { if eof || err != nil {
break break
@@ -408,6 +435,6 @@ func checkCache(ctx *gin.Context, _ http.FileSystem) bool {
} }
ctx.Header(`ETag`, etag) ctx.Header(`ETag`, etag)
ctx.Header(`Cache-Control`, `max-age=604800`) ctx.Header(`Cache-Control`, `max-age=604800`)
ctx.Header(`Expires`, common.Now.Add(7*24*time.Hour).Format(`Mon, 02 Jan 2006 15:04:05 GMT`)) ctx.Header(`Expires`, utils.Now.Add(7*24*time.Hour).Format(`Mon, 02 Jan 2006 15:04:05 GMT`))
return false return false
} }

View File

@@ -1,4 +1,4 @@
package common package utils
import "time" import "time"

View File

@@ -8,6 +8,7 @@ import (
"crypto/rand" "crypto/rand"
"encoding/hex" "encoding/hex"
"errors" "errors"
"fmt"
jsoniter "github.com/json-iterator/go" jsoniter "github.com/json-iterator/go"
"reflect" "reflect"
"unsafe" "unsafe"
@@ -107,6 +108,57 @@ func Decrypt(data []byte, key []byte) ([]byte, error) {
return decBuffer, nil return decBuffer, nil
} }
func FormatSize(size int64) string {
sizes := []string{`B`, `KB`, `MB`, `GB`, `TB`, `PB`, `EB`, `ZB`, `YB`}
i := 0
for size >= 1024 && i < len(sizes)-1 {
size /= 1024
i++
}
return fmt.Sprintf(`%d%s`, size, sizes[i])
}
func BytesToString(b []byte, r ...int) string {
sh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
bytesPtr := sh.Data
bytesLen := sh.Len
switch len(r) {
case 1:
r[0] = If(r[0] > bytesLen, bytesLen, r[0])
bytesLen -= r[0]
bytesPtr += uintptr(r[0])
case 2:
r[0] = If(r[0] > bytesLen, bytesLen, r[0])
bytesLen = If(r[1] > bytesLen, bytesLen, r[1]) - r[0]
bytesPtr += uintptr(r[0])
}
return *(*string)(unsafe.Pointer(&reflect.StringHeader{
Data: bytesPtr,
Len: bytesLen,
}))
}
func StringToBytes(s string, r ...int) []byte {
sh := (*reflect.StringHeader)(unsafe.Pointer(&s))
strPtr := sh.Data
strLen := sh.Len
switch len(r) {
case 1:
r[0] = If(r[0] > strLen, strLen, r[0])
strLen -= r[0]
strPtr += uintptr(r[0])
case 2:
r[0] = If(r[0] > strLen, strLen, r[0])
strLen = If(r[1] > strLen, strLen, r[1]) - r[0]
strPtr += uintptr(r[0])
}
return *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{
Data: strPtr,
Len: strLen,
Cap: strLen,
}))
}
func GetSlicePrefix[T any](data *[]T, n int) *[]T { func GetSlicePrefix[T any](data *[]T, n int) *[]T {
sliceHeader := (*reflect.SliceHeader)(unsafe.Pointer(data)) sliceHeader := (*reflect.SliceHeader)(unsafe.Pointer(data))
return (*[]T)(unsafe.Pointer(&reflect.SliceHeader{ return (*[]T)(unsafe.Pointer(&reflect.SliceHeader{

View File

@@ -9,7 +9,7 @@
<script src="./ext-modelist.js"></script> <script src="./ext-modelist.js"></script>
</head> </head>
<body> <body>
<noscript>You need to enable JavaScript to run this app.</noscript> <noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div> <div id="root"></div>
</body> </body>
</html> </html>

View File

@@ -14,7 +14,7 @@ import {
Spin Spin
} from "antd"; } from "antd";
import ProTable, {TableDropdown} from "@ant-design/pro-table"; import ProTable, {TableDropdown} from "@ant-design/pro-table";
import {catchBlobReq, formatSize, orderCompare, post, preventClose, request, translate, waitTime} from "../utils/utils"; import {catchBlobReq, formatSize, orderCompare, post, preventClose, request, waitTime} from "../utils/utils";
import dayjs from "dayjs"; import dayjs from "dayjs";
import i18n from "../locale/locale"; import i18n from "../locale/locale";
import {VList} from "virtuallist-antd"; import {VList} from "virtuallist-antd";

View File

@@ -15,13 +15,11 @@ function Generate(props) {
} }
form.secure = location.protocol === 'https:' ? 'true' : 'false'; form.secure = location.protocol === 'https:' ? 'true' : 'false';
let basePath = location.origin + location.pathname + 'api/client/'; let basePath = location.origin + location.pathname + 'api/client/';
request(basePath + 'check', form) request(basePath + 'check', form).then(res => {
.then((res) => { if (res.data.code === 0) {
if (res.data.code === 0) { post(basePath += 'generate', form);
post(basePath += 'generate', form); }
} }).catch();
})
.catch()
} }
function getInitValues() { function getInitValues() {

View File

@@ -0,0 +1,52 @@
import React from 'react';
import {ModalForm, ProFormText} from '@ant-design/pro-form';
import {request} from "../utils/utils";
import i18n from "../locale/locale";
import {message} from "antd";
function Runner(props) {
async function onFinish(form) {
form.device = props.device.id;
let basePath = location.origin + location.pathname + 'api/device/';
request(basePath + 'exec', form).then(res => {
if (res.data.code === 0) {
message.success(i18n.t('executionSuccess'));
}
});
}
return (
<ModalForm
modalProps={{
destroyOnClose: true,
maskClosable: false,
}}
title={i18n.t('run')}
width={380}
onFinish={onFinish}
onVisibleChange={visible => {
if (!visible) props.onCancel();
}}
submitter={{
render: (_, elems) => elems.pop()
}}
{...props}
>
<ProFormText
width="md"
name="cmd"
label={i18n.t('cmdPlaceholder')}
rules={[{
required: true
}]}
/>
<ProFormText
width="md"
name="args"
label={i18n.t('argsPlaceholder')}
/>
</ModalForm>
)
}
export default Runner;

View File

@@ -25,6 +25,7 @@
"terminal": "Terminal", "terminal": "Terminal",
"procMgr": "ProcMgr", "procMgr": "ProcMgr",
"fileMgr": "Explorer", "fileMgr": "Explorer",
"run": "Run",
"screenshot": "Screenshot", "screenshot": "Screenshot",
"desktop": "Desktop", "desktop": "Desktop",
"lock": "Lock", "lock": "Lock",
@@ -116,5 +117,9 @@
"screenshotObtainFailed": "Failed to obtain screenshot", "screenshotObtainFailed": "Failed to obtain screenshot",
"noDisplayFound": "No display found", "noDisplayFound": "No display found",
"executionSuccess": "Execution success",
"cmdPlaceholder": "Command",
"argsPlaceholder": "Arguments (separated by space)",
"colon": ": " "colon": ": "
} }

View File

@@ -25,6 +25,7 @@
"terminal": "终端", "terminal": "终端",
"procMgr": "进程", "procMgr": "进程",
"fileMgr": "文件", "fileMgr": "文件",
"run": "运行",
"screenshot": "截屏", "screenshot": "截屏",
"desktop": "桌面", "desktop": "桌面",
"lock": "锁屏", "lock": "锁屏",
@@ -116,5 +117,9 @@
"screenshotObtainFailed": "截屏读取失败", "screenshotObtainFailed": "截屏读取失败",
"noDisplayFound": "设备未连接显示器", "noDisplayFound": "设备未连接显示器",
"executionSuccess": "执行成功",
"cmdPlaceholder": "命令",
"argsPlaceholder": "参数(以空格分隔)",
"colon": "" "colon": ""
} }

View File

@@ -7,6 +7,7 @@ import Explorer from "../components/explorer";
import Terminal from "../components/terminal"; import Terminal from "../components/terminal";
import ProcMgr from "../components/procmgr"; import ProcMgr from "../components/procmgr";
import Desktop from "../components/desktop"; import Desktop from "../components/desktop";
import Runner from "../components/runner";
import {QuestionCircleOutlined} from "@ant-design/icons"; import {QuestionCircleOutlined} from "@ant-design/icons";
import i18n from "../locale/locale"; import i18n from "../locale/locale";
@@ -38,6 +39,7 @@ function UsageBar(props) {
} }
function overview(props) { function overview(props) {
const [runner, setRunner] = useState(false);
const [desktop, setDesktop] = useState(false); const [desktop, setDesktop] = useState(false);
const [procMgr, setProcMgr] = useState(false); const [procMgr, setProcMgr] = useState(false);
const [explorer, setExplorer] = useState(false); const [explorer, setExplorer] = useState(false);
@@ -271,8 +273,9 @@ function overview(props) {
} }
function renderOperation(device) { function renderOperation(device) {
let menus = [ let menus = [
{key: 'screenshot', name: i18n.t('screenshot')}, {key: 'run', name: i18n.t('run')},
{key: 'desktop', name: i18n.t('desktop')}, {key: 'desktop', name: i18n.t('desktop')},
{key: 'screenshot', name: i18n.t('screenshot')},
{key: 'lock', name: i18n.t('lock')}, {key: 'lock', name: i18n.t('lock')},
{key: 'logoff', name: i18n.t('logoff')}, {key: 'logoff', name: i18n.t('logoff')},
{key: 'hibernate', name: i18n.t('hibernate')}, {key: 'hibernate', name: i18n.t('hibernate')},
@@ -299,6 +302,14 @@ function overview(props) {
} }
function callDevice(act, device) { function callDevice(act, device) {
if (act === 'run') {
setRunner(device);
return;
}
if (act === 'desktop') {
setDesktop(device);
return;
}
if (act === 'screenshot') { if (act === 'screenshot') {
request('/api/device/screenshot/get', {device: device.id}, {}, { request('/api/device/screenshot/get', {device: device.id}, {}, {
responseType: 'blob' responseType: 'blob'
@@ -312,10 +323,6 @@ function overview(props) {
}).catch(catchBlobReq); }).catch(catchBlobReq);
return; return;
} }
if (act === 'desktop') {
setDesktop(device);
return;
}
Modal.confirm({ Modal.confirm({
title: i18n.t('operationConfirm').replace('{0}', i18n.t(act).toUpperCase()), title: i18n.t('operationConfirm').replace('{0}', i18n.t(act).toUpperCase()),
icon: <QuestionCircleOutlined/>, icon: <QuestionCircleOutlined/>,
@@ -409,6 +416,11 @@ function overview(props) {
device={procMgr} device={procMgr}
onCancel={setProcMgr.bind(null, false)} onCancel={setProcMgr.bind(null, false)}
/> />
<Runner
visible={runner}
device={runner}
onCancel={setRunner.bind(null, false)}
/>
<Desktop <Desktop
visible={desktop} visible={desktop}
device={desktop} device={desktop}