initial commit
This commit is contained in:
@@ -0,0 +1,293 @@
|
||||
//go:build windows
|
||||
|
||||
package platform
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
type PipeSession struct {
|
||||
mu sync.Mutex
|
||||
hPipe windows.Handle
|
||||
hProcess windows.Handle
|
||||
pid uint32
|
||||
ownsProcess bool
|
||||
job windows.Handle
|
||||
closed bool
|
||||
}
|
||||
|
||||
var (
|
||||
modKernel32Pipe = windows.NewLazySystemDLL("kernel32.dll")
|
||||
modAdvapi32 = windows.NewLazySystemDLL("advapi32.dll")
|
||||
|
||||
procCreateNamedPipeW = modKernel32Pipe.NewProc("CreateNamedPipeW")
|
||||
procConnectNamedPipe = modKernel32Pipe.NewProc("ConnectNamedPipe")
|
||||
procDisconnectNamedPipe = modKernel32Pipe.NewProc("DisconnectNamedPipe")
|
||||
procWaitForSingleObject = modKernel32Pipe.NewProc("WaitForSingleObject")
|
||||
procPeekNamedPipe = modKernel32Pipe.NewProc("PeekNamedPipe")
|
||||
)
|
||||
|
||||
func createPipeName() string {
|
||||
b := make([]byte, 8)
|
||||
rand.Read(b)
|
||||
return fmt.Sprintf(`\\.\pipe\%s`, hex.EncodeToString(b))
|
||||
}
|
||||
|
||||
func createPipeServer(pipeName string) (windows.Handle, error) {
|
||||
namePtr, err := syscall.UTF16PtrFromString(pipeName)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
const (
|
||||
PIPE_ACCESS_DUPLEX = 0x3
|
||||
PIPE_TYPE_BYTE = 0x0
|
||||
PIPE_READMODE_BYTE = 0x0
|
||||
PIPE_WAIT = 0x0
|
||||
PIPE_UNLIMITED_INSTANCES = 0xFF
|
||||
)
|
||||
|
||||
r, _, err := procCreateNamedPipeW.Call(
|
||||
uintptr(unsafe.Pointer(namePtr)),
|
||||
PIPE_ACCESS_DUPLEX|windows.FILE_FLAG_OVERLAPPED,
|
||||
PIPE_TYPE_BYTE|PIPE_READMODE_BYTE|PIPE_WAIT,
|
||||
PIPE_UNLIMITED_INSTANCES,
|
||||
65536, // output buffer
|
||||
65536, // input buffer
|
||||
15000, // timeout ms
|
||||
0,
|
||||
)
|
||||
if r == ^uintptr(0) {
|
||||
return 0, fmt.Errorf("CreateNamedPipeW: %w", err)
|
||||
}
|
||||
return windows.Handle(r), nil
|
||||
}
|
||||
|
||||
func waitPipeConnect(hPipe windows.Handle, timeoutMs uint32) error {
|
||||
hEvent, err := windows.CreateEvent(nil, 1, 0, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("CreateEvent: %w", err)
|
||||
}
|
||||
defer windows.CloseHandle(hEvent)
|
||||
|
||||
ov := windows.Overlapped{HEvent: hEvent}
|
||||
|
||||
r, _, err := procConnectNamedPipe.Call(uintptr(hPipe), uintptr(unsafe.Pointer(&ov)))
|
||||
if r != 0 {
|
||||
return nil // already connected
|
||||
}
|
||||
|
||||
if err == windows.ERROR_PIPE_CONNECTED {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err != windows.ERROR_IO_PENDING {
|
||||
return fmt.Errorf("ConnectNamedPipe: %w", err)
|
||||
}
|
||||
|
||||
ret, _, _ := procWaitForSingleObject.Call(uintptr(hEvent), uintptr(timeoutMs))
|
||||
if ret != uintptr(windows.WAIT_OBJECT_0) {
|
||||
return fmt.Errorf("pipe connect timeout")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *PipeSession) pipeSend(data []byte) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
if s.closed {
|
||||
return fmt.Errorf("pipe session closed")
|
||||
}
|
||||
|
||||
length := uint32(len(data))
|
||||
lengthBytes := []byte{
|
||||
byte(length),
|
||||
byte(length >> 8),
|
||||
byte(length >> 16),
|
||||
byte(length >> 24),
|
||||
}
|
||||
|
||||
var written uint32
|
||||
err := windows.WriteFile(s.hPipe, lengthBytes, &written, nil)
|
||||
if err != nil || written != 4 {
|
||||
return fmt.Errorf("write length: %w", err)
|
||||
}
|
||||
|
||||
if length > 0 {
|
||||
var totalWritten uint32
|
||||
for totalWritten < length {
|
||||
var n uint32
|
||||
err = windows.WriteFile(s.hPipe, data[totalWritten:], &n, nil)
|
||||
if err != nil || n == 0 {
|
||||
return fmt.Errorf("write data: %w", err)
|
||||
}
|
||||
totalWritten += n
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *PipeSession) pipeRecv() (status byte, data []byte, err error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
if s.closed {
|
||||
return 0, nil, fmt.Errorf("pipe session closed")
|
||||
}
|
||||
|
||||
var lengthBuf [4]byte
|
||||
var totalRead uint32
|
||||
deadline := time.Now().Add(10 * time.Second)
|
||||
|
||||
for totalRead < 4 {
|
||||
if time.Now().After(deadline) {
|
||||
return 0, nil, fmt.Errorf("pipe recv timeout")
|
||||
}
|
||||
|
||||
var avail uint32
|
||||
r, _, _ := procPeekNamedPipe.Call(uintptr(s.hPipe), 0, 0, 0, uintptr(unsafe.Pointer(&avail)), 0)
|
||||
if r == 0 {
|
||||
return 0, nil, fmt.Errorf("PeekNamedPipe failed")
|
||||
}
|
||||
if avail < 4-totalRead {
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
|
||||
var n uint32
|
||||
err = windows.ReadFile(s.hPipe, lengthBuf[totalRead:4], &n, nil)
|
||||
if err != nil || n == 0 {
|
||||
return 0, nil, fmt.Errorf("read length: %w", err)
|
||||
}
|
||||
totalRead += n
|
||||
}
|
||||
|
||||
totalLen := uint32(lengthBuf[0]) | uint32(lengthBuf[1])<<8 | uint32(lengthBuf[2])<<16 | uint32(lengthBuf[3])<<24
|
||||
if totalLen < 1 || totalLen > 100*1024*1024 {
|
||||
return 0, nil, fmt.Errorf("invalid message length: %d", totalLen)
|
||||
}
|
||||
|
||||
buf := make([]byte, totalLen)
|
||||
totalRead = 0
|
||||
for totalRead < totalLen {
|
||||
var n uint32
|
||||
err = windows.ReadFile(s.hPipe, buf[totalRead:], &n, nil)
|
||||
if err != nil || n == 0 {
|
||||
return 0, nil, fmt.Errorf("read data: %w", err)
|
||||
}
|
||||
totalRead += n
|
||||
}
|
||||
|
||||
status = buf[0]
|
||||
data = buf[1:]
|
||||
return status, data, nil
|
||||
}
|
||||
|
||||
func (s *PipeSession) Close() {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
if s.closed {
|
||||
return
|
||||
}
|
||||
s.closed = true
|
||||
|
||||
s.sendExitLocked()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
procDisconnectNamedPipe.Call(uintptr(s.hPipe))
|
||||
windows.CloseHandle(s.hPipe)
|
||||
|
||||
if s.ownsProcess && s.hProcess != 0 {
|
||||
if s.job != 0 {
|
||||
windows.CloseHandle(s.job)
|
||||
s.job = 0
|
||||
}
|
||||
windows.TerminateProcess(s.hProcess, 0)
|
||||
windows.WaitForSingleObject(s.hProcess, 3000)
|
||||
windows.CloseHandle(s.hProcess)
|
||||
} else if s.hProcess != 0 {
|
||||
windows.CloseHandle(s.hProcess)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *PipeSession) watchExit(label string, timeoutMs uint32) {
|
||||
h := s.hProcess
|
||||
if h == 0 {
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
ret, _, _ := procWaitForSingleObject.Call(uintptr(h), uintptr(timeoutMs))
|
||||
if ret != uintptr(windows.WAIT_OBJECT_0) {
|
||||
return
|
||||
}
|
||||
s.mu.Lock()
|
||||
wasClosed := s.closed
|
||||
s.mu.Unlock()
|
||||
if wasClosed {
|
||||
return
|
||||
}
|
||||
var code uint32
|
||||
if err := windows.GetExitCodeProcess(h, &code); err != nil {
|
||||
return
|
||||
}
|
||||
logf("process %d (%s) died before pipe connect (exit code 0x%08x)", s.pid, label, code)
|
||||
}()
|
||||
}
|
||||
|
||||
func (s *PipeSession) sendExitLocked() {
|
||||
exitCmd := []byte("EXIT")
|
||||
length := uint32(len(exitCmd))
|
||||
lengthBytes := []byte{byte(length), byte(length >> 8), byte(length >> 16), byte(length >> 24)}
|
||||
windows.WriteFile(s.hPipe, lengthBytes, nil, nil)
|
||||
windows.WriteFile(s.hPipe, exitCmd, nil, nil)
|
||||
}
|
||||
|
||||
func (s *PipeSession) GetV20Key(browserName string, encKeyBase64 string) ([]byte, error) {
|
||||
cmd := fmt.Sprintf("KEY:%s:%s", browserName, encKeyBase64)
|
||||
if err := s.pipeSend([]byte(cmd)); err != nil {
|
||||
return nil, fmt.Errorf("send KEY command: %w", err)
|
||||
}
|
||||
|
||||
status, data, err := s.pipeRecv()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("recv KEY response: %w", err)
|
||||
}
|
||||
|
||||
if status != 0 {
|
||||
return nil, fmt.Errorf("decrypt failed: %s", string(data))
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (s *PipeSession) ReadFile(path string) ([]byte, error) {
|
||||
cmd := fmt.Sprintf("READ:%s", path)
|
||||
if err := s.pipeSend([]byte(cmd)); err != nil {
|
||||
return nil, fmt.Errorf("send READ command: %w", err)
|
||||
}
|
||||
|
||||
status, data, err := s.pipeRecv()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("recv READ response: %w", err)
|
||||
}
|
||||
|
||||
if status != 0 {
|
||||
return nil, fmt.Errorf("read failed: %s", string(data))
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
var ActivePipeSession *PipeSession
|
||||
Reference in New Issue
Block a user