mirror of
https://github.com/yuanyuanxiang/SimpleRemoter.git
synced 2026-01-21 23:13:08 +08:00
Server/go: authorization client automatically exit if verify succeed
This commit is contained in:
@@ -7,6 +7,8 @@
|
||||
```
|
||||
server/go/
|
||||
├── go.mod # Go 模块定义
|
||||
├── auth/
|
||||
│ └── auth.go # 授权验证模块 (TOKEN_AUTH + Heartbeat HMAC)
|
||||
├── buffer/
|
||||
│ └── buffer.go # 线程安全的动态缓冲区
|
||||
├── connection/
|
||||
@@ -32,6 +34,7 @@ server/go/
|
||||
- **高并发**: 基于 Goroutine 池管理并发连接
|
||||
- **协议兼容**: 支持原有 C++ 客户端的多种协议标识 (Hell/Hello/Shine/Fuck)
|
||||
- **协议头解密**: 支持8种协议头加密方式 (V0-V6 + Default)
|
||||
- **授权验证**: 支持 TOKEN_AUTH 和 Heartbeat HMAC-SHA256 双重授权验证
|
||||
- **XOR编码**: 支持 XOREncoder16 数据编码/解码
|
||||
- **ZSTD 压缩**: 使用高效的 ZSTD 算法进行数据压缩
|
||||
- **GBK编码**: 自动将 Windows 客户端的 GBK 编码转换为 UTF-8
|
||||
@@ -46,9 +49,10 @@ server/go/
|
||||
|
||||
| 命令 | 值 | 说明 |
|
||||
|------|-----|------|
|
||||
| TOKEN_AUTH | 100 | 授权请求 |
|
||||
| TOKEN_HEARTBEAT | 101 | 心跳包 |
|
||||
| TOKEN_AUTH | 100 | 授权请求 (验证 SN + Passcode + HMAC) |
|
||||
| TOKEN_HEARTBEAT | 101 | 心跳包 (支持 HMAC 授权验证,返回 Authorized 状态) |
|
||||
| TOKEN_LOGIN | 102 | 客户端登录 |
|
||||
| CMD_HEARTBEAT_ACK | 216 | 心跳响应 (包含 Authorized 字段) |
|
||||
|
||||
其他命令会被记录为 Debug 日志,可按需扩展。
|
||||
|
||||
@@ -75,6 +79,25 @@ go build -o simpleremoter-server ./cmd
|
||||
|
||||
服务器默认监听 6543 端口,日志输出到 `logs/server.log`。
|
||||
|
||||
### 环境变量
|
||||
|
||||
| 变量 | 说明 | 示例 |
|
||||
|------|------|------|
|
||||
| `YAMA_PWDHASH` | 密码的 SHA256 哈希值 (64位十六进制) | `61f04dd6...` |
|
||||
| `YAMA_PWD` | 超级密码,用于 HMAC 签名验证 | `your_super_password` |
|
||||
|
||||
```bash
|
||||
# Linux/macOS
|
||||
export YAMA_PWDHASH="61f04dd637a74ee34493fc1025de2c131022536da751c29e3ff4e9024d8eec43"
|
||||
export YAMA_PWD="your_super_password"
|
||||
./simpleremoter-server
|
||||
|
||||
# Windows PowerShell
|
||||
$env:YAMA_PWDHASH="61f04dd637a74ee34493fc1025de2c131022536da751c29e3ff4e9024d8eec43"
|
||||
$env:YAMA_PWD="your_super_password"
|
||||
.\simpleremoter-server.exe
|
||||
```
|
||||
|
||||
## 使用示例
|
||||
|
||||
```go
|
||||
@@ -229,6 +252,46 @@ func main() {
|
||||
| szStartTime | 456 | 20 | 启动时间 |
|
||||
| szReserved | 476 | 512 | 扩展字段 (用`|`分隔) |
|
||||
|
||||
### Heartbeat 结构
|
||||
|
||||
客户端心跳包结构 (1024 字节):
|
||||
|
||||
| 字段 | 偏移 | 大小 | 说明 |
|
||||
|------|------|------|------|
|
||||
| Time | 0 | 8 | 时间戳 (uint64) |
|
||||
| ActiveWnd | 8 | 512 | 当前活动窗口 |
|
||||
| Ping | 520 | 4 | 延迟 (int) |
|
||||
| HasSoftware | 524 | 4 | 软件标识 (int) |
|
||||
| SN | 528 | 20 | 序列号 (用于授权验证) |
|
||||
| Passcode | 548 | 44 | 授权码 (格式: v0-v1-v2-v3-v4-v5) |
|
||||
| PwdHmac | 592 | 8 | HMAC 签名 (uint64) |
|
||||
| Reserved | 600 | 424 | 保留字段 |
|
||||
|
||||
### HeartbeatACK 结构
|
||||
|
||||
服务端心跳响应结构 (32 字节):
|
||||
|
||||
| 字段 | 偏移 | 大小 | 说明 |
|
||||
|------|------|------|------|
|
||||
| Time | 0 | 8 | 原始时间戳 (uint64) |
|
||||
| Authorized | 8 | 1 | 授权状态 (1=已授权, 0=未授权) |
|
||||
| Reserved | 9 | 23 | 保留字段 |
|
||||
|
||||
### 授权验证流程
|
||||
|
||||
```
|
||||
客户端 Heartbeat 服务端
|
||||
│ │
|
||||
│ SN + Passcode + PwdHmac │
|
||||
│ ────────────────────────────────► │
|
||||
│ │ 1. 验证 Passcode 格式
|
||||
│ │ 2. 验证 Passcode 哈希
|
||||
│ │ 3. 验证 HMAC 签名
|
||||
│ HeartbeatACK │
|
||||
│ ◄──────────────────────────────── │
|
||||
│ (Authorized=1 或 0) │
|
||||
```
|
||||
|
||||
## API 参考
|
||||
|
||||
### Server
|
||||
|
||||
@@ -206,3 +206,102 @@ func GenHMAC(pwdHash, superPass string) string {
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// HeartbeatAuthResult contains the result of heartbeat authentication
|
||||
type HeartbeatAuthResult struct {
|
||||
Authorized bool
|
||||
SN string
|
||||
Passcode string
|
||||
PwdHmac uint64
|
||||
}
|
||||
|
||||
// AuthenticateHeartbeat validates authorization info from a Heartbeat message
|
||||
// Data format (after TOKEN_HEARTBEAT byte):
|
||||
// - offset 0: Time (8 bytes, uint64)
|
||||
// - offset 8: ActiveWnd (512 bytes)
|
||||
// - offset 520: Ping (4 bytes, int)
|
||||
// - offset 524: HasSoftware (4 bytes, int)
|
||||
// - offset 528: SN (20 bytes)
|
||||
// - offset 548: Passcode (44 bytes)
|
||||
// - offset 592: PwdHmac (8 bytes, uint64)
|
||||
func (a *Authenticator) AuthenticateHeartbeat(data []byte) *HeartbeatAuthResult {
|
||||
result := &HeartbeatAuthResult{
|
||||
Authorized: false,
|
||||
}
|
||||
|
||||
// Minimum length check: need at least SN + Passcode + PwdHmac
|
||||
// Offset 528 + 20 (SN) + 44 (Passcode) + 8 (PwdHmac) = 600 bytes
|
||||
if len(data) < 600 {
|
||||
return result
|
||||
}
|
||||
|
||||
// Extract SN (offset 528, 20 bytes)
|
||||
snBytes := data[528:548]
|
||||
// Find null terminator
|
||||
snEnd := bytes.IndexByte(snBytes, 0)
|
||||
if snEnd == -1 {
|
||||
snEnd = len(snBytes)
|
||||
}
|
||||
sn := string(snBytes[:snEnd])
|
||||
result.SN = sn
|
||||
|
||||
// Extract Passcode (offset 548, 44 bytes)
|
||||
passcodeBytes := data[548:592]
|
||||
passcodeEnd := bytes.IndexByte(passcodeBytes, 0)
|
||||
if passcodeEnd == -1 {
|
||||
passcodeEnd = len(passcodeBytes)
|
||||
}
|
||||
passcode := string(passcodeBytes[:passcodeEnd])
|
||||
result.Passcode = passcode
|
||||
|
||||
// Extract PwdHmac (offset 592, 8 bytes)
|
||||
pwdHmac := binary.LittleEndian.Uint64(data[592:600])
|
||||
result.PwdHmac = pwdHmac
|
||||
|
||||
// If SN, Passcode, or PwdHmac is empty/zero, not authorized
|
||||
if sn == "" || passcode == "" || pwdHmac == 0 {
|
||||
return result
|
||||
}
|
||||
|
||||
// Split passcode by '-'
|
||||
parts := strings.Split(passcode, "-")
|
||||
if len(parts) != 6 && len(parts) != 7 {
|
||||
return result
|
||||
}
|
||||
|
||||
// Get last 4 parts as subvector
|
||||
subvector := parts[len(parts)-4:]
|
||||
|
||||
// Build password string: v[0] + " - " + v[1] + ": " + PwdHash + (optional: ": " + v[2])
|
||||
password := parts[0] + " - " + parts[1] + ": " + a.config.PwdHash
|
||||
if len(parts) == 7 {
|
||||
password += ": " + parts[2]
|
||||
}
|
||||
|
||||
// Derive key from password and SN
|
||||
finalKey := DeriveKey(password, sn)
|
||||
|
||||
// Get fixed length ID
|
||||
hash256 := strings.Join(subvector, "-")
|
||||
fixedKey := GetFixedLengthID(finalKey)
|
||||
|
||||
// Compare passcode
|
||||
if hash256 != fixedKey {
|
||||
return result
|
||||
}
|
||||
|
||||
// Passcode validation successful, now verify HMAC
|
||||
superPass := os.Getenv("YAMA_PWD")
|
||||
if superPass == "" {
|
||||
superPass = a.config.SuperPass
|
||||
}
|
||||
|
||||
if superPass != "" {
|
||||
verified := VerifyMessage(superPass, []byte(passcode), pwdHmac)
|
||||
if verified {
|
||||
result.Authorized = true
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -132,6 +132,19 @@ func (h *MyHandler) handleAuth(ctx *connection.Context, data []byte) {
|
||||
}
|
||||
|
||||
// handleHeartbeat handles heartbeat from client (TOKEN_HEARTBEAT = 101)
|
||||
// Heartbeat structure (after command byte):
|
||||
// - offset 0: Time (8 bytes, uint64)
|
||||
// - offset 8: ActiveWnd (512 bytes)
|
||||
// - offset 520: Ping (4 bytes, int)
|
||||
// - offset 524: HasSoftware (4 bytes, int)
|
||||
// - offset 528: SN (20 bytes)
|
||||
// - offset 548: Passcode (44 bytes)
|
||||
// - offset 592: PwdHmac (8 bytes, uint64)
|
||||
//
|
||||
// HeartbeatACK structure:
|
||||
// - offset 0: Time (8 bytes, uint64)
|
||||
// - offset 8: Authorized (1 byte, char)
|
||||
// - offset 9: Reserved (23 bytes)
|
||||
func (h *MyHandler) handleHeartbeat(ctx *connection.Context, data []byte) {
|
||||
|
||||
// Parse Time from heartbeat request (offset 1, 8 bytes)
|
||||
@@ -141,6 +154,23 @@ func (h *MyHandler) handleHeartbeat(ctx *connection.Context, data []byte) {
|
||||
uint64(data[5])<<32 | uint64(data[6])<<40 | uint64(data[7])<<48 | uint64(data[8])<<56
|
||||
}
|
||||
|
||||
// Authenticate heartbeat if it contains authorization info
|
||||
// data[1:] skips the command byte to get the raw Heartbeat structure
|
||||
var authorized byte = 0
|
||||
if len(data) > 1 {
|
||||
authResult := h.auth.AuthenticateHeartbeat(data[1:])
|
||||
if authResult.Authorized {
|
||||
authorized = 1
|
||||
// Log authorization success (only log once per connection to avoid spam)
|
||||
if !ctx.IsAuthorized.Load() {
|
||||
ctx.IsAuthorized.Store(true)
|
||||
info := ctx.GetInfo()
|
||||
h.log.Info("Heartbeat auth success: clientID=%s computer=%s ip=%s sn=%s passcode=%s pwdHmac=%d",
|
||||
info.ClientID, info.ComputerName, ctx.GetPeerIP(), authResult.SN, authResult.Passcode, authResult.PwdHmac)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Build HeartbeatACK response: CMD_HEARTBEAT_ACK(1) + HeartbeatACK(32)
|
||||
resp := make([]byte, 33)
|
||||
resp[0] = protocol.CommandHeartbeat // CMD_HEARTBEAT_ACK = 216
|
||||
@@ -153,7 +183,9 @@ func (h *MyHandler) handleHeartbeat(ctx *connection.Context, data []byte) {
|
||||
resp[6] = byte(hbTime >> 40)
|
||||
resp[7] = byte(hbTime >> 48)
|
||||
resp[8] = byte(hbTime >> 56)
|
||||
// Reserved[24] at offset 9 is already zero
|
||||
// Authorized at offset 9 (1 byte)
|
||||
resp[9] = authorized
|
||||
// Reserved[23] at offset 10 is already zero
|
||||
|
||||
if err := h.srv.Send(ctx, resp); err != nil {
|
||||
h.log.Error("Failed to send heartbeat ACK to client %d: %v", ctx.ID, err)
|
||||
|
||||
@@ -37,8 +37,9 @@ type Context struct {
|
||||
OutBuffer *buffer.Buffer // Decompressed data for processing
|
||||
|
||||
// Client info
|
||||
Info ClientInfo
|
||||
IsLoggedIn atomic.Bool
|
||||
Info ClientInfo
|
||||
IsLoggedIn atomic.Bool
|
||||
IsAuthorized atomic.Bool // Whether client is authorized via heartbeat
|
||||
|
||||
// Connection state
|
||||
OnlineTime time.Time
|
||||
|
||||
@@ -260,7 +260,28 @@ func (s *Server) processData(ctx *connection.Context) {
|
||||
if err == protocol.ErrNeedMore {
|
||||
return
|
||||
}
|
||||
s.logger.Error("Parse error for connection %d: %v", ctx.ID, err)
|
||||
// Log more details for unsupported protocol errors
|
||||
if err == protocol.ErrUnsupported {
|
||||
// Get first 32 bytes for debugging
|
||||
peekLen := 32
|
||||
if ctx.InBuffer.Len() < peekLen {
|
||||
peekLen = ctx.InBuffer.Len()
|
||||
}
|
||||
rawData := ctx.InBuffer.Peek(peekLen)
|
||||
// Sanitize for safe logging (replace non-printable chars)
|
||||
ascii := make([]byte, len(rawData))
|
||||
for i, b := range rawData {
|
||||
if b >= 32 && b < 127 {
|
||||
ascii[i] = b
|
||||
} else {
|
||||
ascii[i] = '.'
|
||||
}
|
||||
}
|
||||
s.logger.Warn("Unsupported protocol from ip=%s conn=%d raw=%x ascii=%s",
|
||||
ctx.GetPeerIP(), ctx.ID, rawData, string(ascii))
|
||||
} else {
|
||||
s.logger.Error("Parse error for connection %d: %v", ctx.ID, err)
|
||||
}
|
||||
_ = ctx.Close()
|
||||
return
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user