mirror of
https://github.com/XZB-1248/Spark
synced 2025-09-27 04:26:20 +08:00
173 lines
5.4 KiB
Go
173 lines
5.4 KiB
Go
package generate
|
|
|
|
import (
|
|
"Spark/modules"
|
|
"Spark/server/common"
|
|
"Spark/server/config"
|
|
"Spark/utils"
|
|
"bytes"
|
|
"encoding/hex"
|
|
"errors"
|
|
"fmt"
|
|
"github.com/gin-gonic/gin"
|
|
"math/big"
|
|
"net/http"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
type clientCfg struct {
|
|
Secure bool `json:"secure"`
|
|
Host string `json:"host"`
|
|
Port int `json:"port"`
|
|
Path string `json:"path"`
|
|
UUID string `json:"uuid"`
|
|
Key string `json:"key"`
|
|
}
|
|
|
|
var (
|
|
ErrTooLargeEntity = errors.New(`length of data can not excess buffer size`)
|
|
)
|
|
|
|
func CheckClient(ctx *gin.Context) {
|
|
var form struct {
|
|
OS string `json:"os" yaml:"os" form:"os" binding:"required"`
|
|
Arch string `json:"arch" yaml:"arch" form:"arch" binding:"required"`
|
|
Host string `json:"host" yaml:"host" form:"host" binding:"required"`
|
|
Port uint16 `json:"port" yaml:"port" form:"port" binding:"required"`
|
|
Path string `json:"path" yaml:"path" form:"path" binding:"required"`
|
|
Secure string `json:"secure" yaml:"secure" form:"secure"`
|
|
}
|
|
if err := ctx.ShouldBind(&form); err != nil {
|
|
ctx.AbortWithStatusJSON(http.StatusBadRequest, modules.Packet{Code: -1, Msg: `${i18n|invalidParameter}`})
|
|
return
|
|
}
|
|
_, err := os.Stat(fmt.Sprintf(config.BuiltPath, form.OS, form.Arch))
|
|
if err != nil {
|
|
ctx.AbortWithStatusJSON(http.StatusNotFound, modules.Packet{Code: 1, Msg: `${i18n|osOrArchNotPrebuilt}`})
|
|
return
|
|
}
|
|
_, err = genConfig(clientCfg{
|
|
Secure: form.Secure == `true`,
|
|
Host: form.Host,
|
|
Port: int(form.Port),
|
|
Path: form.Path,
|
|
UUID: strings.Repeat(`FF`, 16),
|
|
Key: strings.Repeat(`FF`, 32),
|
|
})
|
|
if err != nil {
|
|
if err == ErrTooLargeEntity {
|
|
ctx.AbortWithStatusJSON(http.StatusRequestEntityTooLarge, modules.Packet{Code: 1, Msg: `${i18n|tooLargeConfig}`})
|
|
return
|
|
}
|
|
ctx.AbortWithStatusJSON(http.StatusInternalServerError, modules.Packet{Code: 1, Msg: `${i18n|configGenerateFailed}`})
|
|
return
|
|
}
|
|
ctx.JSON(http.StatusOK, modules.Packet{Code: 0})
|
|
}
|
|
|
|
func GenerateClient(ctx *gin.Context) {
|
|
var form struct {
|
|
OS string `json:"os" yaml:"os" form:"os" binding:"required"`
|
|
Arch string `json:"arch" yaml:"arch" form:"arch" binding:"required"`
|
|
Host string `json:"host" yaml:"host" form:"host" binding:"required"`
|
|
Port uint16 `json:"port" yaml:"port" form:"port" binding:"required"`
|
|
Path string `json:"path" yaml:"path" form:"path" binding:"required"`
|
|
Secure string `json:"secure" yaml:"secure" form:"secure"`
|
|
}
|
|
if err := ctx.ShouldBind(&form); err != nil {
|
|
ctx.AbortWithStatusJSON(http.StatusBadRequest, modules.Packet{Code: -1, Msg: `${i18n|invalidParameter}`})
|
|
return
|
|
}
|
|
tpl, err := os.Open(fmt.Sprintf(config.BuiltPath, form.OS, form.Arch))
|
|
if err != nil {
|
|
ctx.AbortWithStatusJSON(http.StatusNotFound, modules.Packet{Code: 1, Msg: `${i18n|osOrArchNotPrebuilt}`})
|
|
return
|
|
}
|
|
clientUUID := utils.GetUUID()
|
|
clientKey, err := common.EncAES(clientUUID, config.Config.SaltBytes)
|
|
if err != nil {
|
|
ctx.AbortWithStatusJSON(http.StatusInternalServerError, modules.Packet{Code: 1, Msg: `${i18n|configGenerateFailed}`})
|
|
return
|
|
}
|
|
cfgBytes, err := genConfig(clientCfg{
|
|
Secure: form.Secure == `true`,
|
|
Host: form.Host,
|
|
Port: int(form.Port),
|
|
Path: form.Path,
|
|
UUID: hex.EncodeToString(clientUUID),
|
|
Key: hex.EncodeToString(clientKey),
|
|
})
|
|
if err != nil {
|
|
if err == ErrTooLargeEntity {
|
|
ctx.AbortWithStatusJSON(http.StatusRequestEntityTooLarge, modules.Packet{Code: 1, Msg: `${i18n|tooLargeConfig}`})
|
|
return
|
|
}
|
|
ctx.AbortWithStatusJSON(http.StatusInternalServerError, modules.Packet{Code: 1, Msg: `${i18n|configGenerateFailed}`})
|
|
return
|
|
}
|
|
ctx.Header(`Accept-Ranges`, `none`)
|
|
ctx.Header(`Content-Transfer-Encoding`, `binary`)
|
|
ctx.Header(`Content-Type`, `application/octet-stream`)
|
|
if stat, err := tpl.Stat(); err == nil {
|
|
ctx.Header(`Content-Length`, strconv.FormatInt(stat.Size(), 10))
|
|
}
|
|
if form.OS == `windows` {
|
|
ctx.Header(`Content-Disposition`, `attachment; filename=client.exe; filename*=UTF-8''client.exe`)
|
|
} else {
|
|
ctx.Header(`Content-Disposition`, `attachment; filename=client; filename*=UTF-8''client`)
|
|
}
|
|
// Find and replace plain buffer with encrypted configuration.
|
|
cfgBuffer := bytes.Repeat([]byte{'\x19'}, 384)
|
|
prevBuffer := make([]byte, 0)
|
|
for {
|
|
thisBuffer := make([]byte, 1024)
|
|
n, err := tpl.Read(thisBuffer)
|
|
thisBuffer = thisBuffer[:n]
|
|
tempBuffer := append(prevBuffer, thisBuffer...)
|
|
bufIndex := bytes.Index(tempBuffer, cfgBuffer)
|
|
if bufIndex > -1 {
|
|
tempBuffer = bytes.Replace(tempBuffer, cfgBuffer, cfgBytes, -1)
|
|
}
|
|
ctx.Writer.Write(tempBuffer[:len(prevBuffer)])
|
|
prevBuffer = tempBuffer[len(prevBuffer):]
|
|
if err != nil {
|
|
break
|
|
}
|
|
}
|
|
if len(prevBuffer) > 0 {
|
|
ctx.Writer.Write(prevBuffer)
|
|
prevBuffer = []byte{}
|
|
}
|
|
}
|
|
|
|
func genConfig(cfg clientCfg) ([]byte, error) {
|
|
data, err := utils.JSON.Marshal(cfg)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
key := utils.GetUUID()
|
|
data, err = common.EncAES(data, key)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
final := append(key, data...)
|
|
if len(final) > 384-2 {
|
|
return nil, ErrTooLargeEntity
|
|
}
|
|
|
|
// Get the length of encrypted buffer as a 2-byte big-endian integer.
|
|
// And append encrypted buffer to the end of the data length.
|
|
dataLen := big.NewInt(int64(len(final))).Bytes()
|
|
dataLen = append(bytes.Repeat([]byte{'\x00'}, 2-len(dataLen)), dataLen...)
|
|
|
|
// If the length of encrypted buffer is less than 384,
|
|
// append the remaining bytes with random bytes.
|
|
final = append(dataLen, final...)
|
|
for len(final) < 384 {
|
|
final = append(final, utils.GetUUID()...)
|
|
}
|
|
return final[:384], nil
|
|
}
|