Files
Spark/server/handler/generate/generate.go
2022-10-14 19:49:13 +08:00

173 lines
5.5 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|COMMON.INVALID_PARAMETER}`})
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|GENERATOR.NO_PREBUILT_FOUND}`})
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|GENERATOR.CONFIG_TOO_LARGE}`})
return
}
ctx.AbortWithStatusJSON(http.StatusInternalServerError, modules.Packet{Code: 1, Msg: `${i18n|GENERATOR.CONFIG_GENERATE_FAILED}`})
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|COMMON.INVALID_PARAMETER}`})
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|GENERATOR.NO_PREBUILT_FOUND}`})
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|GENERATOR.CONFIG_GENERATE_FAILED}`})
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|GENERATOR.CONFIG_TOO_LARGE}`})
return
}
ctx.AbortWithStatusJSON(http.StatusInternalServerError, modules.Packet{Code: 1, Msg: `${i18n|GENERATOR.CONFIG_GENERATE_FAILED}`})
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
}