feat: 完善堡垒机与 Agent 智能运维能力
- 支持 Windows Agent 构建、安装脚本和统一上报协议 - 增强 RDP 剪贴板、虚拟盘和文件传输能力 - 新增 AIOps 上下文工具、告警 RCA trace 落库和回放接口 - 重构 docs 模块文档并补充终端、堡垒机、监控、AI Agent 设计
This commit is contained in:
1 parent
a9513c2056
commit
e0d652d211
169 files changed
+12192
-9116
No files matched your search
@@ -0,0 +1,602 @@
|
||||
package rdpdr
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"log"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf16"
|
||||
)
|
||||
|
||||
const (
|
||||
irpCreate uint32 = 0x00000000
|
||||
irpClose uint32 = 0x00000002
|
||||
irpRead uint32 = 0x00000003
|
||||
irpWrite uint32 = 0x00000004
|
||||
irpQueryInfo uint32 = 0x00000005
|
||||
irpSetInfo uint32 = 0x00000006
|
||||
irpDeviceCtrl uint32 = 0x0000000e
|
||||
irpQueryVolume uint32 = 0x0000000a
|
||||
irpDirControl uint32 = 0x0000000c
|
||||
irpLockControl uint32 = 0x00000011
|
||||
|
||||
irpMnQueryDir uint32 = 0x00000001
|
||||
irpMnNotifyChangeDir uint32 = 0x00000002
|
||||
|
||||
statusSuccess uint32 = 0x00000000
|
||||
statusNoMoreFiles uint32 = 0x80000006
|
||||
statusAccessDenied uint32 = 0xc0000022
|
||||
statusObjectNameNotFound uint32 = 0xc0000034
|
||||
statusNoSuchFile uint32 = 0xc000000f
|
||||
statusFileIsDirectory uint32 = 0xc00000ba
|
||||
statusNotSupported uint32 = 0xc00000bb
|
||||
statusInvalidDeviceReq uint32 = 0xc0000010
|
||||
statusInvalidParameter uint32 = 0xc000000d
|
||||
statusEndOfFile uint32 = 0xc0000011
|
||||
|
||||
fileOpen uint32 = 0x00000001
|
||||
fileCreate uint32 = 0x00000002
|
||||
fileOpenIf uint32 = 0x00000003
|
||||
fileOverwrite uint32 = 0x00000004
|
||||
fileOverwriteIf uint32 = 0x00000005
|
||||
|
||||
fileDirectoryFile uint32 = 0x00000001
|
||||
fileNonDirectoryFile uint32 = 0x00000040
|
||||
|
||||
fileDirectoryInformation uint32 = 1
|
||||
fileFullDirectoryInformation uint32 = 2
|
||||
fileBothDirectoryInformation uint32 = 3
|
||||
fileBasicInformation uint32 = 4
|
||||
fileStandardInformation uint32 = 5
|
||||
fileInternalInformation uint32 = 6
|
||||
fileEaInformation uint32 = 7
|
||||
fileAccessInformation uint32 = 8
|
||||
fileNameInformation uint32 = 9
|
||||
fileNamesInformation uint32 = 12
|
||||
filePositionInformation uint32 = 14
|
||||
fileModeInformation uint32 = 16
|
||||
fileAlignmentInformation uint32 = 17
|
||||
fileEndOfFileInformation uint32 = 20
|
||||
fileNetworkOpenInformation uint32 = 34
|
||||
fileAttributeTagInformation uint32 = 35
|
||||
|
||||
fileFsVolumeInformation uint32 = 1
|
||||
fileFsSizeInformation uint32 = 3
|
||||
fileFsDeviceInformation uint32 = 4
|
||||
fileFsAttributeInformation uint32 = 5
|
||||
fileFsFullSizeInformation uint32 = 7
|
||||
|
||||
fileAttrDirectory uint32 = 0x00000010
|
||||
fileAttrNormal uint32 = 0x00000080
|
||||
fileAttrArchive uint32 = 0x00000020
|
||||
|
||||
fileOpened byte = 0x01
|
||||
fileCreated byte = 0x02
|
||||
fileOverwritten byte = 0x03
|
||||
|
||||
downloadDirName = "Download"
|
||||
uploadDirName = "Upload"
|
||||
)
|
||||
|
||||
const windowsFiletimeOffset = 116444736000000000
|
||||
|
||||
type ioRequest struct {
|
||||
deviceID uint32
|
||||
fileID uint32
|
||||
completionID uint32
|
||||
majorFn uint32
|
||||
minorFn uint32
|
||||
payload []byte
|
||||
}
|
||||
|
||||
type openFile struct {
|
||||
path string
|
||||
name string
|
||||
isDir bool
|
||||
data []byte
|
||||
written bool
|
||||
dirEntries []dirEntry
|
||||
dirIndex int
|
||||
}
|
||||
|
||||
type dirEntry struct {
|
||||
name string
|
||||
isDir bool
|
||||
size uint64
|
||||
}
|
||||
|
||||
func (h *Handler) handleIORequest(body []byte) {
|
||||
if len(body) < 20 {
|
||||
return
|
||||
}
|
||||
req := ioRequest{
|
||||
deviceID: binary.LittleEndian.Uint32(body[0:4]),
|
||||
fileID: binary.LittleEndian.Uint32(body[4:8]),
|
||||
completionID: binary.LittleEndian.Uint32(body[8:12]),
|
||||
majorFn: binary.LittleEndian.Uint32(body[12:16]),
|
||||
minorFn: binary.LittleEndian.Uint32(body[16:20]),
|
||||
payload: body[20:],
|
||||
}
|
||||
log.Printf("[rdpdr] io request device=%d file=%d completion=%d major=0x%x minor=0x%x payload=%d", req.deviceID, req.fileID, req.completionID, req.majorFn, req.minorFn, len(req.payload))
|
||||
switch req.majorFn {
|
||||
case irpCreate:
|
||||
h.handleCreate(req)
|
||||
case irpClose:
|
||||
h.handleClose(req)
|
||||
case irpRead:
|
||||
h.handleRead(req)
|
||||
case irpWrite:
|
||||
h.handleWrite(req)
|
||||
case irpQueryInfo:
|
||||
h.handleQueryInfo(req)
|
||||
case irpSetInfo:
|
||||
h.handleSetInfo(req)
|
||||
case irpDeviceCtrl:
|
||||
h.handleDeviceControl(req)
|
||||
case irpQueryVolume:
|
||||
h.handleQueryVolume(req)
|
||||
case irpDirControl:
|
||||
if req.minorFn == irpMnQueryDir {
|
||||
h.handleQueryDirectory(req)
|
||||
return
|
||||
}
|
||||
if req.minorFn == irpMnNotifyChangeDir {
|
||||
h.handleNotifyChangeDirectory(req)
|
||||
return
|
||||
}
|
||||
h.sendIOCompletion(req.deviceID, req.completionID, statusNotSupported, nil)
|
||||
case irpLockControl:
|
||||
h.handleLockControl(req)
|
||||
default:
|
||||
h.sendIOCompletion(req.deviceID, req.completionID, statusNotSupported, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) handleCreate(req ioRequest) {
|
||||
var out [5]byte
|
||||
if len(req.payload) < 32 {
|
||||
h.sendIOCompletion(req.deviceID, req.completionID, statusInvalidParameter, out[:])
|
||||
return
|
||||
}
|
||||
disposition := binary.LittleEndian.Uint32(req.payload[20:24])
|
||||
options := binary.LittleEndian.Uint32(req.payload[24:28])
|
||||
pathLen := binary.LittleEndian.Uint32(req.payload[28:32])
|
||||
if uint32(len(req.payload)) < 32+pathLen {
|
||||
h.sendIOCompletion(req.deviceID, req.completionID, statusInvalidParameter, out[:])
|
||||
return
|
||||
}
|
||||
vpath := normalizeVirtualPath(decodeUTF16(req.payload[32 : 32+pathLen]))
|
||||
isDir := options&fileDirectoryFile != 0
|
||||
kind, name := classifyVirtualPath(vpath)
|
||||
if kind == "missing" {
|
||||
h.sendIOCompletion(req.deviceID, req.completionID, statusObjectNameNotFound, out[:])
|
||||
return
|
||||
}
|
||||
switch kind {
|
||||
case "root", "download", "upload":
|
||||
isDir = true
|
||||
case "file", "upload_file":
|
||||
// Explorer 拖拽复制时可能带上目录相关 CreateOptions,虚拟盘文件必须按真实文件打开。// Logan.Li
|
||||
isDir = false
|
||||
}
|
||||
var initialData []byte
|
||||
if kind == "upload_file" {
|
||||
data, ok := h.getUpload(name)
|
||||
if !ok {
|
||||
h.sendIOCompletion(req.deviceID, req.completionID, statusObjectNameNotFound, out[:])
|
||||
return
|
||||
}
|
||||
initialData = data
|
||||
}
|
||||
if isDir && options&fileNonDirectoryFile != 0 {
|
||||
h.sendIOCompletion(req.deviceID, req.completionID, statusFileIsDirectory, out[:])
|
||||
return
|
||||
}
|
||||
if kind == "file" && disposition == fileOpen {
|
||||
// 虚拟 Download 目录只负责接收远端写入,读取历史文件留给上传目录实现。// Logan.Li
|
||||
h.sendIOCompletion(req.deviceID, req.completionID, statusObjectNameNotFound, out[:])
|
||||
return
|
||||
}
|
||||
info := fileOpened
|
||||
if disposition == fileCreate {
|
||||
info = fileCreated
|
||||
}
|
||||
if disposition == fileOverwrite || disposition == fileOverwriteIf {
|
||||
info = fileOverwritten
|
||||
}
|
||||
fileID := h.storeOpenFile(&openFile{path: vpath, name: name, isDir: isDir, data: initialData})
|
||||
binary.LittleEndian.PutUint32(out[0:4], fileID)
|
||||
out[4] = info
|
||||
log.Printf("[rdpdr] create path=%q fileID=%d dir=%t", vpath, fileID, isDir)
|
||||
h.sendIOCompletion(req.deviceID, req.completionID, statusSuccess, out[:])
|
||||
}
|
||||
|
||||
func (h *Handler) handleClose(req ioRequest) {
|
||||
file, ok := h.removeOpenFile(req.fileID)
|
||||
if ok && !file.isDir && file.written && len(file.data) > 0 && h.onDownload != nil {
|
||||
data := append([]byte(nil), file.data...)
|
||||
h.onDownload(Download{Name: file.name, Data: data})
|
||||
log.Printf("[rdpdr] download captured name=%s size=%d", file.name, len(data))
|
||||
}
|
||||
var out [5]byte
|
||||
h.sendIOCompletion(req.deviceID, req.completionID, statusSuccess, out[:])
|
||||
}
|
||||
|
||||
func (h *Handler) handleRead(req ioRequest) {
|
||||
if len(req.payload) < 20 {
|
||||
h.sendIOCompletion(req.deviceID, req.completionID, statusInvalidParameter, nil)
|
||||
return
|
||||
}
|
||||
length := int(binary.LittleEndian.Uint32(req.payload[0:4]))
|
||||
offset := int(binary.LittleEndian.Uint64(req.payload[4:12]))
|
||||
file, ok := h.getOpenFile(req.fileID)
|
||||
if !ok || file.isDir {
|
||||
log.Printf("[rdpdr] read missing file=%d offset=%d length=%d", req.fileID, offset, length)
|
||||
h.sendIOCompletion(req.deviceID, req.completionID, statusNoSuchFile, nil)
|
||||
return
|
||||
}
|
||||
if length <= 0 {
|
||||
var out [4]byte
|
||||
log.Printf("[rdpdr] read zero file=%d path=%q offset=%d", req.fileID, file.path, offset)
|
||||
h.sendIOCompletion(req.deviceID, req.completionID, statusSuccess, out[:])
|
||||
return
|
||||
}
|
||||
if offset >= len(file.data) {
|
||||
var out [4]byte
|
||||
// Explorer 复制虚拟盘文件时更稳定地接受 0 字节成功读作为 EOF。// Logan.Li
|
||||
log.Printf("[rdpdr] read eof file=%d path=%q offset=%d size=%d", req.fileID, file.path, offset, len(file.data))
|
||||
h.sendIOCompletion(req.deviceID, req.completionID, statusSuccess, out[:])
|
||||
return
|
||||
}
|
||||
end := offset + length
|
||||
if end > len(file.data) {
|
||||
end = len(file.data)
|
||||
}
|
||||
out := make([]byte, 4+end-offset)
|
||||
binary.LittleEndian.PutUint32(out[0:4], uint32(end-offset))
|
||||
copy(out[4:], file.data[offset:end])
|
||||
log.Printf("[rdpdr] read file=%d path=%q offset=%d requested=%d returned=%d size=%d", req.fileID, file.path, offset, length, end-offset, len(file.data))
|
||||
h.sendIOCompletion(req.deviceID, req.completionID, statusSuccess, out)
|
||||
}
|
||||
|
||||
func (h *Handler) handleWrite(req ioRequest) {
|
||||
if len(req.payload) < 32 {
|
||||
h.sendIOCompletion(req.deviceID, req.completionID, statusInvalidParameter, nil)
|
||||
return
|
||||
}
|
||||
length := int(binary.LittleEndian.Uint32(req.payload[0:4]))
|
||||
offset := int(binary.LittleEndian.Uint64(req.payload[4:12]))
|
||||
chunk := req.payload[32:]
|
||||
if length > len(chunk) {
|
||||
length = len(chunk)
|
||||
}
|
||||
file, ok := h.getOpenFile(req.fileID)
|
||||
if !ok || file.isDir {
|
||||
h.sendIOCompletion(req.deviceID, req.completionID, statusNoSuchFile, nil)
|
||||
return
|
||||
}
|
||||
need := offset + length
|
||||
if need > len(file.data) {
|
||||
next := make([]byte, need)
|
||||
copy(next, file.data)
|
||||
file.data = next
|
||||
}
|
||||
copy(file.data[offset:need], chunk[:length])
|
||||
file.written = true
|
||||
var out [5]byte
|
||||
binary.LittleEndian.PutUint32(out[0:4], uint32(length))
|
||||
h.sendIOCompletion(req.deviceID, req.completionID, statusSuccess, out[:])
|
||||
}
|
||||
|
||||
func (h *Handler) handleQueryInfo(req ioRequest) {
|
||||
if len(req.payload) < 4 {
|
||||
h.sendIOCompletion(req.deviceID, req.completionID, statusInvalidParameter, nil)
|
||||
return
|
||||
}
|
||||
infoClass := binary.LittleEndian.Uint32(req.payload[0:4])
|
||||
file, ok := h.getOpenFile(req.fileID)
|
||||
if !ok {
|
||||
h.sendIOCompletion(req.deviceID, req.completionID, statusNoSuchFile, nil)
|
||||
return
|
||||
}
|
||||
attrs := fileAttrArchive
|
||||
if file.isDir {
|
||||
attrs = fileAttrDirectory
|
||||
}
|
||||
size := uint64(len(file.data))
|
||||
log.Printf("[rdpdr] query info file=%d class=%d path=%q dir=%t size=%d", req.fileID, infoClass, file.path, file.isDir, size)
|
||||
switch infoClass {
|
||||
case fileBasicInformation:
|
||||
out := make([]byte, 40)
|
||||
binary.LittleEndian.PutUint32(out[0:4], 36)
|
||||
writeFileTimes(out[4:36])
|
||||
binary.LittleEndian.PutUint32(out[36:40], attrs)
|
||||
h.sendIOCompletion(req.deviceID, req.completionID, statusSuccess, out)
|
||||
case fileStandardInformation:
|
||||
out := make([]byte, 26)
|
||||
binary.LittleEndian.PutUint32(out[0:4], 22)
|
||||
binary.LittleEndian.PutUint64(out[4:12], align4096(size))
|
||||
binary.LittleEndian.PutUint64(out[12:20], size)
|
||||
binary.LittleEndian.PutUint32(out[20:24], 1)
|
||||
if file.isDir {
|
||||
out[25] = 1
|
||||
}
|
||||
h.sendIOCompletion(req.deviceID, req.completionID, statusSuccess, out)
|
||||
case fileAttributeTagInformation:
|
||||
out := make([]byte, 12)
|
||||
binary.LittleEndian.PutUint32(out[0:4], 8)
|
||||
binary.LittleEndian.PutUint32(out[4:8], attrs)
|
||||
h.sendIOCompletion(req.deviceID, req.completionID, statusSuccess, out)
|
||||
case fileInternalInformation:
|
||||
out := make([]byte, 12)
|
||||
binary.LittleEndian.PutUint32(out[0:4], 8)
|
||||
binary.LittleEndian.PutUint64(out[4:12], uint64(req.fileID))
|
||||
h.sendIOCompletion(req.deviceID, req.completionID, statusSuccess, out)
|
||||
case fileEaInformation:
|
||||
out := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint32(out[0:4], 4)
|
||||
h.sendIOCompletion(req.deviceID, req.completionID, statusSuccess, out)
|
||||
case fileAccessInformation, fileModeInformation, fileAlignmentInformation:
|
||||
out := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint32(out[0:4], 4)
|
||||
h.sendIOCompletion(req.deviceID, req.completionID, statusSuccess, out)
|
||||
case fileNameInformation:
|
||||
name := encodeUTF16(fileDisplayName(file))
|
||||
out := make([]byte, 8+len(name))
|
||||
binary.LittleEndian.PutUint32(out[0:4], uint32(4+len(name)))
|
||||
binary.LittleEndian.PutUint32(out[4:8], uint32(len(name)))
|
||||
copy(out[8:], name)
|
||||
h.sendIOCompletion(req.deviceID, req.completionID, statusSuccess, out)
|
||||
case filePositionInformation:
|
||||
out := make([]byte, 12)
|
||||
binary.LittleEndian.PutUint32(out[0:4], 8)
|
||||
h.sendIOCompletion(req.deviceID, req.completionID, statusSuccess, out)
|
||||
case fileNetworkOpenInformation:
|
||||
out := make([]byte, 60)
|
||||
binary.LittleEndian.PutUint32(out[0:4], 56)
|
||||
writeFileTimes(out[4:36])
|
||||
binary.LittleEndian.PutUint64(out[36:44], align4096(size))
|
||||
binary.LittleEndian.PutUint64(out[44:52], size)
|
||||
binary.LittleEndian.PutUint32(out[52:56], attrs)
|
||||
h.sendIOCompletion(req.deviceID, req.completionID, statusSuccess, out)
|
||||
default:
|
||||
log.Printf("[rdpdr] query info unsupported class=%d file=%d path=%q", infoClass, req.fileID, file.path)
|
||||
h.sendIOCompletion(req.deviceID, req.completionID, statusNotSupported, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) handleSetInfo(req ioRequest) {
|
||||
var out [4]byte
|
||||
h.sendIOCompletion(req.deviceID, req.completionID, statusSuccess, out[:])
|
||||
}
|
||||
|
||||
func (h *Handler) handleQueryVolume(req ioRequest) {
|
||||
if len(req.payload) < 4 {
|
||||
h.sendIOCompletion(req.deviceID, req.completionID, statusInvalidParameter, nil)
|
||||
return
|
||||
}
|
||||
infoClass := binary.LittleEndian.Uint32(req.payload[0:4])
|
||||
switch infoClass {
|
||||
case fileFsVolumeInformation:
|
||||
label := encodeUTF16(h.driveName)
|
||||
out := make([]byte, 21+len(label))
|
||||
binary.LittleEndian.PutUint32(out[0:4], uint32(17+len(label)))
|
||||
binary.LittleEndian.PutUint64(out[4:12], filetimeNow())
|
||||
binary.LittleEndian.PutUint32(out[12:16], 0x41494f50)
|
||||
binary.LittleEndian.PutUint32(out[16:20], uint32(len(label)))
|
||||
copy(out[21:], label)
|
||||
h.sendIOCompletion(req.deviceID, req.completionID, statusSuccess, out)
|
||||
case fileFsSizeInformation:
|
||||
out := make([]byte, 28)
|
||||
binary.LittleEndian.PutUint32(out[0:4], 24)
|
||||
binary.LittleEndian.PutUint64(out[4:12], 1024*1024)
|
||||
binary.LittleEndian.PutUint64(out[12:20], 1024*1024)
|
||||
binary.LittleEndian.PutUint32(out[20:24], 8)
|
||||
binary.LittleEndian.PutUint32(out[24:28], 512)
|
||||
h.sendIOCompletion(req.deviceID, req.completionID, statusSuccess, out)
|
||||
case fileFsDeviceInformation:
|
||||
out := make([]byte, 12)
|
||||
binary.LittleEndian.PutUint32(out[0:4], 8)
|
||||
binary.LittleEndian.PutUint32(out[4:8], 0x00000007)
|
||||
h.sendIOCompletion(req.deviceID, req.completionID, statusSuccess, out)
|
||||
case fileFsAttributeInformation:
|
||||
fsName := encodeUTF16("AIOPS")
|
||||
out := make([]byte, 16+len(fsName))
|
||||
binary.LittleEndian.PutUint32(out[0:4], uint32(12+len(fsName)))
|
||||
binary.LittleEndian.PutUint32(out[4:8], 0x00000007)
|
||||
binary.LittleEndian.PutUint32(out[8:12], 255)
|
||||
binary.LittleEndian.PutUint32(out[12:16], uint32(len(fsName)))
|
||||
copy(out[16:], fsName)
|
||||
h.sendIOCompletion(req.deviceID, req.completionID, statusSuccess, out)
|
||||
case fileFsFullSizeInformation:
|
||||
out := make([]byte, 36)
|
||||
binary.LittleEndian.PutUint32(out[0:4], 32)
|
||||
binary.LittleEndian.PutUint64(out[4:12], 1024*1024)
|
||||
binary.LittleEndian.PutUint64(out[12:20], 1024*1024)
|
||||
binary.LittleEndian.PutUint64(out[20:28], 1024*1024)
|
||||
binary.LittleEndian.PutUint32(out[28:32], 8)
|
||||
binary.LittleEndian.PutUint32(out[32:36], 512)
|
||||
h.sendIOCompletion(req.deviceID, req.completionID, statusSuccess, out)
|
||||
default:
|
||||
h.sendIOCompletion(req.deviceID, req.completionID, statusNotSupported, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) handleDeviceControl(req ioRequest) {
|
||||
var out [4]byte
|
||||
ioctl := uint32(0)
|
||||
if len(req.payload) >= 12 {
|
||||
ioctl = binary.LittleEndian.Uint32(req.payload[8:12])
|
||||
}
|
||||
// 资源管理器复制时会探测若干 FSCTL;明确返回“不支持”比 invalid parameter 更接近本地盘行为。// Logan.Li
|
||||
log.Printf("[rdpdr] device control file=%d ioctl=0x%x payload=%d", req.fileID, ioctl, len(req.payload))
|
||||
h.sendIOCompletion(req.deviceID, req.completionID, statusInvalidDeviceReq, out[:])
|
||||
}
|
||||
|
||||
func (h *Handler) handleNotifyChangeDirectory(req ioRequest) {
|
||||
// Windows 资源管理器会长期挂起目录变更通知;立即成功返回会触发重复刷新和“地址无效”。// Logan.Li
|
||||
log.Printf("[rdpdr] notify change pending file=%d", req.fileID)
|
||||
}
|
||||
|
||||
func (h *Handler) handleLockControl(req ioRequest) {
|
||||
var out [4]byte
|
||||
// 上传暂存文件是只读内存文件,锁请求直接成功,避免 Explorer 复制流程中断。// Logan.Li
|
||||
log.Printf("[rdpdr] lock control file=%d payload=%d", req.fileID, len(req.payload))
|
||||
h.sendIOCompletion(req.deviceID, req.completionID, statusSuccess, out[:])
|
||||
}
|
||||
|
||||
func (h *Handler) handleQueryDirectory(req ioRequest) {
|
||||
if len(req.payload) < 32 {
|
||||
h.sendIOCompletion(req.deviceID, req.completionID, statusInvalidParameter, nil)
|
||||
return
|
||||
}
|
||||
infoClass := binary.LittleEndian.Uint32(req.payload[0:4])
|
||||
initial := req.payload[4] != 0
|
||||
file, ok := h.getOpenFile(req.fileID)
|
||||
if !ok || !file.isDir {
|
||||
h.sendIOCompletion(req.deviceID, req.completionID, statusNoSuchFile, nil)
|
||||
return
|
||||
}
|
||||
if initial {
|
||||
file.dirIndex = 0
|
||||
if file.path == "" {
|
||||
file.dirEntries = []dirEntry{{name: "."}, {name: ".."}, {name: downloadDirName, isDir: true}, {name: uploadDirName, isDir: true}}
|
||||
} else if strings.EqualFold(file.path, uploadDirName) {
|
||||
file.dirEntries = append([]dirEntry{{name: "."}, {name: ".."}}, h.listUploads()...)
|
||||
} else {
|
||||
file.dirEntries = []dirEntry{{name: "."}, {name: ".."}}
|
||||
}
|
||||
}
|
||||
log.Printf("[rdpdr] query dir file=%d class=%d path=%q initial=%t index=%d entries=%d", req.fileID, infoClass, file.path, initial, file.dirIndex, len(file.dirEntries))
|
||||
if file.dirIndex >= len(file.dirEntries) {
|
||||
var out [5]byte
|
||||
h.sendIOCompletion(req.deviceID, req.completionID, statusNoMoreFiles, out[:])
|
||||
return
|
||||
}
|
||||
entry := file.dirEntries[file.dirIndex]
|
||||
file.dirIndex++
|
||||
h.sendIOCompletion(req.deviceID, req.completionID, statusSuccess, buildDirInfo(infoClass, entry))
|
||||
}
|
||||
|
||||
func buildDirInfo(infoClass uint32, entry dirEntry) []byte {
|
||||
name := encodeUTF16(entry.name)
|
||||
nameWithNull := append(append([]byte(nil), name...), 0, 0)
|
||||
attrs := fileAttrNormal
|
||||
if entry.isDir || entry.name == "." || entry.name == ".." {
|
||||
attrs = fileAttrDirectory
|
||||
}
|
||||
if infoClass == fileNamesInformation {
|
||||
out := make([]byte, 16+len(nameWithNull))
|
||||
binary.LittleEndian.PutUint32(out[0:4], uint32(12+len(nameWithNull)))
|
||||
binary.LittleEndian.PutUint32(out[12:16], uint32(len(nameWithNull)))
|
||||
copy(out[16:], nameWithNull)
|
||||
return out
|
||||
}
|
||||
fixed := 64
|
||||
if infoClass == fileFullDirectoryInformation {
|
||||
fixed = 68
|
||||
}
|
||||
if infoClass == fileBothDirectoryInformation {
|
||||
fixed = 93
|
||||
}
|
||||
out := make([]byte, 4+fixed+len(nameWithNull))
|
||||
binary.LittleEndian.PutUint32(out[0:4], uint32(fixed+len(nameWithNull)))
|
||||
writeFileTimes(out[12:44])
|
||||
binary.LittleEndian.PutUint64(out[44:52], entry.size)
|
||||
binary.LittleEndian.PutUint64(out[52:60], align4096(entry.size))
|
||||
binary.LittleEndian.PutUint32(out[60:64], attrs)
|
||||
binary.LittleEndian.PutUint32(out[64:68], uint32(len(nameWithNull)))
|
||||
copy(out[4+fixed:], nameWithNull)
|
||||
return out
|
||||
}
|
||||
|
||||
func fileDisplayName(file *openFile) string {
|
||||
if file.name != "" {
|
||||
return file.name
|
||||
}
|
||||
if file.path != "" {
|
||||
parts := strings.Split(file.path, "\\")
|
||||
return parts[len(parts)-1]
|
||||
}
|
||||
return "\\"
|
||||
}
|
||||
|
||||
func classifyVirtualPath(vpath string) (string, string) {
|
||||
if vpath == "" {
|
||||
return "root", ""
|
||||
}
|
||||
parts := strings.Split(vpath, "\\")
|
||||
if len(parts) == 1 && strings.EqualFold(parts[0], downloadDirName) {
|
||||
return "download", downloadDirName
|
||||
}
|
||||
if len(parts) == 1 && strings.EqualFold(parts[0], uploadDirName) {
|
||||
return "upload", uploadDirName
|
||||
}
|
||||
if len(parts) >= 2 && strings.EqualFold(parts[0], downloadDirName) {
|
||||
name := parts[len(parts)-1]
|
||||
if name == "" || name == "." || name == ".." {
|
||||
return "missing", ""
|
||||
}
|
||||
return "file", name
|
||||
}
|
||||
if len(parts) >= 2 && strings.EqualFold(parts[0], uploadDirName) {
|
||||
name := parts[len(parts)-1]
|
||||
if name == "" || name == "." || name == ".." {
|
||||
return "missing", ""
|
||||
}
|
||||
return "upload_file", name
|
||||
}
|
||||
return "missing", ""
|
||||
}
|
||||
|
||||
func normalizeVirtualPath(raw string) string {
|
||||
clean := strings.Trim(strings.ReplaceAll(raw, "/", "\\"), "\\\x00 ")
|
||||
if clean == "" || clean == "." {
|
||||
return ""
|
||||
}
|
||||
parts := strings.FieldsFunc(clean, func(r rune) bool { return r == '\\' })
|
||||
safe := make([]string, 0, len(parts))
|
||||
for _, part := range parts {
|
||||
if part == "" || part == "." || part == ".." {
|
||||
continue
|
||||
}
|
||||
safe = append(safe, part)
|
||||
}
|
||||
return strings.ReplaceAll(path.Clean(strings.Join(safe, "/")), "/", "\\")
|
||||
}
|
||||
|
||||
func encodeUTF16(s string) []byte {
|
||||
u16 := utf16.Encode([]rune(s))
|
||||
out := make([]byte, len(u16)*2)
|
||||
for i, v := range u16 {
|
||||
binary.LittleEndian.PutUint16(out[i*2:], v)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func decodeUTF16(data []byte) string {
|
||||
u16 := make([]uint16, len(data)/2)
|
||||
for i := range u16 {
|
||||
u16[i] = binary.LittleEndian.Uint16(data[i*2:])
|
||||
}
|
||||
for len(u16) > 0 && u16[len(u16)-1] == 0 {
|
||||
u16 = u16[:len(u16)-1]
|
||||
}
|
||||
return string(utf16.Decode(u16))
|
||||
}
|
||||
|
||||
func writeFileTimes(out []byte) {
|
||||
ft := filetimeNow()
|
||||
for i := 0; i+8 <= len(out); i += 8 {
|
||||
binary.LittleEndian.PutUint64(out[i:i+8], ft)
|
||||
}
|
||||
}
|
||||
|
||||
func filetimeNow() uint64 {
|
||||
return uint64(time.Now().UnixNano()/100) + windowsFiletimeOffset
|
||||
}
|
||||
|
||||
func align4096(v uint64) uint64 {
|
||||
return (v + 4095) &^ 4095
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package rdpdr
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type captureSender struct {
|
||||
data [][]byte
|
||||
}
|
||||
|
||||
func (s *captureSender) SendChannelPDU(_ uint16, data []byte) error {
|
||||
s.data = append(s.data, append([]byte(nil), data...))
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestClassifyVirtualPathUploadFile(t *testing.T) {
|
||||
kind, name := classifyVirtualPath(`Upload\php.ini`)
|
||||
if kind != "upload_file" || name != "php.ini" {
|
||||
t.Fatalf("Upload file should be classified as upload_file, got kind=%q name=%q", kind, name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleCreateUploadFileOpensAsFile(t *testing.T) {
|
||||
sender := &captureSender{}
|
||||
handler := NewHandler("goclient", "AIOps")
|
||||
handler.Attach(sender, 1004)
|
||||
handler.AddUpload("php.ini", []byte("ok"))
|
||||
|
||||
encodedPath := encodeUTF16(`Upload\php.ini`)
|
||||
payload := make([]byte, 32+len(encodedPath))
|
||||
binary.LittleEndian.PutUint32(payload[20:24], fileOpen)
|
||||
binary.LittleEndian.PutUint32(payload[24:28], fileDirectoryFile)
|
||||
binary.LittleEndian.PutUint32(payload[28:32], uint32(len(encodedPath)))
|
||||
copy(payload[32:], encodedPath)
|
||||
|
||||
handler.handleCreate(ioRequest{
|
||||
deviceID: 1,
|
||||
completionID: 7,
|
||||
majorFn: irpCreate,
|
||||
payload: payload,
|
||||
})
|
||||
if len(sender.data) == 0 {
|
||||
t.Fatal("expected create completion")
|
||||
}
|
||||
completion := sender.data[len(sender.data)-1]
|
||||
if len(completion) < 21 {
|
||||
t.Fatalf("completion too short: %d", len(completion))
|
||||
}
|
||||
if status := binary.LittleEndian.Uint32(completion[12:16]); status != statusSuccess {
|
||||
t.Fatalf("expected success, got status=0x%08x", status)
|
||||
}
|
||||
fileID := binary.LittleEndian.Uint32(completion[16:20])
|
||||
file, ok := handler.getOpenFile(fileID)
|
||||
if !ok {
|
||||
t.Fatalf("open file %d not stored", fileID)
|
||||
}
|
||||
if file.isDir {
|
||||
t.Fatalf("upload file opened as directory: %#v", file)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleReadEOFReturnsZeroLengthSuccess(t *testing.T) {
|
||||
sender := &captureSender{}
|
||||
handler := NewHandler("goclient", "AIOps")
|
||||
handler.Attach(sender, 1004)
|
||||
fileID := handler.storeOpenFile(&openFile{path: `Upload\x.yaml`, name: "x.yaml", data: []byte("abc")})
|
||||
|
||||
payload := make([]byte, 20)
|
||||
binary.LittleEndian.PutUint32(payload[0:4], 4096)
|
||||
binary.LittleEndian.PutUint64(payload[4:12], 3)
|
||||
handler.handleRead(ioRequest{
|
||||
deviceID: 1,
|
||||
fileID: fileID,
|
||||
completionID: 9,
|
||||
payload: payload,
|
||||
})
|
||||
|
||||
completion := sender.data[len(sender.data)-1]
|
||||
if status := binary.LittleEndian.Uint32(completion[12:16]); status != statusSuccess {
|
||||
t.Fatalf("expected EOF success, got status=0x%08x", status)
|
||||
}
|
||||
if got := binary.LittleEndian.Uint32(completion[16:20]); got != 0 {
|
||||
t.Fatalf("expected zero read length at EOF, got %d", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleDeviceControlReturnsInvalidDeviceRequest(t *testing.T) {
|
||||
sender := &captureSender{}
|
||||
handler := NewHandler("goclient", "AIOps")
|
||||
handler.Attach(sender, 1004)
|
||||
|
||||
payload := make([]byte, 32)
|
||||
binary.LittleEndian.PutUint32(payload[8:12], 0x000900a8)
|
||||
handler.handleDeviceControl(ioRequest{
|
||||
deviceID: 1,
|
||||
fileID: 2,
|
||||
completionID: 10,
|
||||
payload: payload,
|
||||
})
|
||||
|
||||
completion := sender.data[len(sender.data)-1]
|
||||
if status := binary.LittleEndian.Uint32(completion[12:16]); status != statusInvalidDeviceReq {
|
||||
t.Fatalf("expected invalid device request, got status=0x%08x", status)
|
||||
}
|
||||
if got := binary.LittleEndian.Uint32(completion[16:20]); got != 0 {
|
||||
t.Fatalf("expected empty device control output, got %d", got)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,388 @@
|
||||
// Package rdpdr 实现 RDP 设备重定向静态虚拟通道的最小握手。// Logan.Li
|
||||
package rdpdr
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"log"
|
||||
"sync"
|
||||
"unicode/utf16"
|
||||
)
|
||||
|
||||
type Sender interface {
|
||||
SendChannelPDU(channelID uint16, data []byte) error
|
||||
}
|
||||
|
||||
const ChannelName = "rdpdr"
|
||||
|
||||
const (
|
||||
componentRDPDR = 0x4472
|
||||
|
||||
pakIDServerAnnounce = 0x496e
|
||||
pakIDClientIDConfirm = 0x4343
|
||||
pakIDClientName = 0x434e
|
||||
pakIDServerCoreCap = 0x5350
|
||||
pakIDClientCoreCap = 0x4350
|
||||
pakIDDeviceListAnnounce = 0x4441
|
||||
pakIDDeviceReply = 0x6472
|
||||
pakIDDeviceIORequest = 0x4952
|
||||
pakIDDeviceIOCompletion = 0x4943
|
||||
pakIDUserLoggedOn = 0x554c
|
||||
|
||||
capGeneralType = 0x0001
|
||||
capDriveType = 0x0004
|
||||
|
||||
deviceTypeDisk = 0x00000008
|
||||
)
|
||||
|
||||
type Download struct {
|
||||
Name string
|
||||
Data []byte
|
||||
}
|
||||
|
||||
type Handler struct {
|
||||
sender Sender
|
||||
channelID uint16
|
||||
clientID uint32
|
||||
gotServerCap bool
|
||||
gotIDConfirm bool
|
||||
serverIOCode1 uint32
|
||||
computerName string
|
||||
driveName string
|
||||
devicePreannounced bool
|
||||
deviceAnnounced bool
|
||||
mu sync.Mutex
|
||||
nextFileID uint32
|
||||
files map[uint32]*openFile
|
||||
uploads map[string][]byte
|
||||
onDownload func(Download)
|
||||
}
|
||||
|
||||
func NewHandler(computerName string, driveName string) *Handler {
|
||||
if computerName == "" {
|
||||
computerName = "goclient"
|
||||
}
|
||||
if driveName == "" {
|
||||
driveName = "AIOPS"
|
||||
}
|
||||
return &Handler{computerName: computerName, driveName: driveName, nextFileID: 1, files: make(map[uint32]*openFile), uploads: make(map[string][]byte)}
|
||||
}
|
||||
|
||||
func (h *Handler) SetDownloadHandler(fn func(Download)) {
|
||||
h.onDownload = fn
|
||||
}
|
||||
|
||||
func (h *Handler) AddUpload(name string, data []byte) {
|
||||
if name == "" || len(data) == 0 {
|
||||
return
|
||||
}
|
||||
h.mu.Lock()
|
||||
h.uploads[name] = append([]byte(nil), data...)
|
||||
h.mu.Unlock()
|
||||
log.Printf("[rdpdr] upload staged name=%s size=%d", name, len(data))
|
||||
}
|
||||
|
||||
func (h *Handler) listUploads() []dirEntry {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
out := make([]dirEntry, 0, len(h.uploads))
|
||||
for name, data := range h.uploads {
|
||||
out = append(out, dirEntry{name: name, size: uint64(len(data))})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (h *Handler) getUpload(name string) ([]byte, bool) {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
data, ok := h.uploads[name]
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
return append([]byte(nil), data...), true
|
||||
}
|
||||
|
||||
func (h *Handler) Attach(sender Sender, channelID uint16) {
|
||||
h.sender = sender
|
||||
h.channelID = channelID
|
||||
log.Printf("[rdpdr] attached channelID=%d", channelID)
|
||||
}
|
||||
|
||||
func (h *Handler) PreannounceDevices() {
|
||||
h.mu.Lock()
|
||||
if h.devicePreannounced || h.sender == nil || h.channelID == 0 {
|
||||
h.mu.Unlock()
|
||||
return
|
||||
}
|
||||
h.devicePreannounced = true
|
||||
h.mu.Unlock()
|
||||
// 部分 Windows 会在图形首帧后才启动 rdpdr;预公告客户端能力和磁盘设备,尽早完成驱动器重定向。// Logan.Li
|
||||
log.Printf("[rdpdr] preannounce devices client=%s drive=%s", h.computerName, h.driveName)
|
||||
h.sendClientName()
|
||||
h.sendCoreCapResponse()
|
||||
h.sendDeviceList(true)
|
||||
}
|
||||
|
||||
func (h *Handler) Process(data []byte) {
|
||||
if len(data) < 4 {
|
||||
return
|
||||
}
|
||||
component := binary.LittleEndian.Uint16(data[0:2])
|
||||
packetID := binary.LittleEndian.Uint16(data[2:4])
|
||||
body := data[4:]
|
||||
if component != componentRDPDR {
|
||||
log.Printf("[rdpdr] ignore component=0x%x packet=0x%x len=%d", component, packetID, len(data))
|
||||
return
|
||||
}
|
||||
log.Printf("[rdpdr] recv packet=0x%x len=%d", packetID, len(body))
|
||||
switch packetID {
|
||||
case pakIDServerAnnounce:
|
||||
h.handleServerAnnounce(body)
|
||||
case pakIDServerCoreCap:
|
||||
h.handleServerCoreCap(body)
|
||||
case pakIDClientIDConfirm:
|
||||
h.handleClientIDConfirm(body)
|
||||
case pakIDDeviceReply:
|
||||
h.handleDeviceReply(body)
|
||||
case pakIDDeviceIORequest:
|
||||
h.handleIORequest(body)
|
||||
case pakIDUserLoggedOn:
|
||||
h.sendDeviceListOnce()
|
||||
default:
|
||||
log.Printf("[rdpdr] unhandled packet=0x%x len=%d", packetID, len(body))
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) handleServerAnnounce(body []byte) {
|
||||
if len(body) < 8 {
|
||||
return
|
||||
}
|
||||
h.clientID = binary.LittleEndian.Uint32(body[4:8])
|
||||
log.Printf("[rdpdr] server announce clientID=%d version=%d.%d", h.clientID, binary.LittleEndian.Uint16(body[0:2]), binary.LittleEndian.Uint16(body[2:4]))
|
||||
|
||||
buf := make([]byte, 12)
|
||||
binary.LittleEndian.PutUint16(buf[0:2], componentRDPDR)
|
||||
binary.LittleEndian.PutUint16(buf[2:4], pakIDClientIDConfirm)
|
||||
binary.LittleEndian.PutUint16(buf[4:6], 1)
|
||||
binary.LittleEndian.PutUint16(buf[6:8], 0x000c)
|
||||
binary.LittleEndian.PutUint32(buf[8:12], h.clientID)
|
||||
h.send(buf)
|
||||
h.sendClientName()
|
||||
}
|
||||
|
||||
func (h *Handler) sendClientName() {
|
||||
runes := utf16.Encode([]rune(h.computerName + "\x00"))
|
||||
name := make([]byte, len(runes)*2)
|
||||
for i, r := range runes {
|
||||
binary.LittleEndian.PutUint16(name[i*2:], r)
|
||||
}
|
||||
buf := make([]byte, 16+len(name))
|
||||
binary.LittleEndian.PutUint16(buf[0:2], componentRDPDR)
|
||||
binary.LittleEndian.PutUint16(buf[2:4], pakIDClientName)
|
||||
binary.LittleEndian.PutUint32(buf[4:8], 1)
|
||||
binary.LittleEndian.PutUint32(buf[12:16], uint32(len(name)))
|
||||
copy(buf[16:], name)
|
||||
h.send(buf)
|
||||
}
|
||||
|
||||
func (h *Handler) handleServerCoreCap(body []byte) {
|
||||
if len(body) < 4 {
|
||||
return
|
||||
}
|
||||
count := binary.LittleEndian.Uint16(body[0:2])
|
||||
offset := 4
|
||||
for i := 0; i < int(count) && offset+8 <= len(body); i++ {
|
||||
capType := binary.LittleEndian.Uint16(body[offset:])
|
||||
capLen := int(binary.LittleEndian.Uint16(body[offset+2:]))
|
||||
if capLen < 8 || offset+capLen > len(body) {
|
||||
break
|
||||
}
|
||||
if capType == capGeneralType && capLen >= 24 {
|
||||
h.serverIOCode1 = binary.LittleEndian.Uint32(body[offset+20 : offset+24])
|
||||
}
|
||||
offset += capLen
|
||||
}
|
||||
h.gotServerCap = true
|
||||
h.trySendReady()
|
||||
}
|
||||
|
||||
func (h *Handler) handleClientIDConfirm(body []byte) {
|
||||
if len(body) >= 8 {
|
||||
h.clientID = binary.LittleEndian.Uint32(body[4:8])
|
||||
}
|
||||
h.gotIDConfirm = true
|
||||
h.trySendReady()
|
||||
}
|
||||
|
||||
func (h *Handler) trySendReady() {
|
||||
if !h.gotServerCap || !h.gotIDConfirm {
|
||||
return
|
||||
}
|
||||
h.sendCoreCapResponse()
|
||||
// 能力协商完成后立即公告虚拟盘,避免首次登录后资源管理器等待网络位置刷新。// Logan.Li
|
||||
h.sendDeviceListOnce()
|
||||
}
|
||||
|
||||
func (h *Handler) sendCoreCapResponse() {
|
||||
const generalCapLen = 44
|
||||
const driveCapLen = 8
|
||||
buf := bytes.Buffer{}
|
||||
writeLE16(&buf, componentRDPDR)
|
||||
writeLE16(&buf, pakIDClientCoreCap)
|
||||
writeLE16(&buf, 2)
|
||||
writeLE16(&buf, 0)
|
||||
|
||||
writeLE16(&buf, capGeneralType)
|
||||
writeLE16(&buf, generalCapLen)
|
||||
writeLE32(&buf, 2)
|
||||
writeLE32(&buf, 0)
|
||||
writeLE32(&buf, 0)
|
||||
writeLE16(&buf, 1)
|
||||
writeLE16(&buf, 0x000c)
|
||||
ioCode1 := h.serverIOCode1 & 0x0000ffff
|
||||
if ioCode1 == 0 {
|
||||
ioCode1 = 0x0000ffff
|
||||
}
|
||||
writeLE32(&buf, ioCode1)
|
||||
writeLE32(&buf, 0)
|
||||
writeLE32(&buf, 0x00000007)
|
||||
writeLE32(&buf, 0x00000001)
|
||||
writeLE32(&buf, 0)
|
||||
writeLE32(&buf, 0)
|
||||
|
||||
writeLE16(&buf, capDriveType)
|
||||
writeLE16(&buf, driveCapLen)
|
||||
writeLE32(&buf, 2)
|
||||
h.send(buf.Bytes())
|
||||
}
|
||||
|
||||
func (h *Handler) sendDeviceList(includeDevices bool) {
|
||||
deviceData := []byte(nil)
|
||||
deviceCount := uint32(0)
|
||||
totalLen := 8
|
||||
if includeDevices {
|
||||
deviceCount = 1
|
||||
deviceData = []byte(h.driveName)
|
||||
totalLen += 20 + len(deviceData)
|
||||
}
|
||||
if deviceCount == 0 {
|
||||
// FreeRDP 在没有预登录设备时直接跳过空公告,避免 Windows 记录一个无设备列表。// Logan.Li
|
||||
log.Printf("[rdpdr] skip empty device list announce")
|
||||
return
|
||||
}
|
||||
buf := make([]byte, totalLen)
|
||||
binary.LittleEndian.PutUint16(buf[0:2], componentRDPDR)
|
||||
binary.LittleEndian.PutUint16(buf[2:4], pakIDDeviceListAnnounce)
|
||||
binary.LittleEndian.PutUint32(buf[4:8], deviceCount)
|
||||
if includeDevices {
|
||||
// 公告一个磁盘设备,Windows 里显示为 \\goclient\AIOPS。// Logan.Li
|
||||
binary.LittleEndian.PutUint32(buf[8:12], deviceTypeDisk)
|
||||
binary.LittleEndian.PutUint32(buf[12:16], 1)
|
||||
copy(buf[16:24], []byte(dosDeviceName(h.driveName)))
|
||||
binary.LittleEndian.PutUint32(buf[24:28], uint32(len(deviceData)))
|
||||
copy(buf[28:], deviceData)
|
||||
}
|
||||
h.send(buf)
|
||||
log.Printf("[rdpdr] device list announced devices=%d name=%s", deviceCount, h.driveName)
|
||||
}
|
||||
|
||||
func (h *Handler) sendDeviceListOnce() {
|
||||
h.mu.Lock()
|
||||
if h.deviceAnnounced {
|
||||
h.mu.Unlock()
|
||||
return
|
||||
}
|
||||
h.deviceAnnounced = true
|
||||
h.mu.Unlock()
|
||||
h.sendDeviceList(true)
|
||||
}
|
||||
|
||||
func dosDeviceName(name string) string {
|
||||
if name == "" {
|
||||
return "AIOPS"
|
||||
}
|
||||
if len(name) > 8 {
|
||||
return name[:8]
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
func (h *Handler) send(data []byte) {
|
||||
if h.sender == nil || h.channelID == 0 {
|
||||
return
|
||||
}
|
||||
if err := h.sender.SendChannelPDU(h.channelID, data); err != nil {
|
||||
log.Printf("[rdpdr] send failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) sendIOCompletion(deviceID, completionID, status uint32, output []byte) {
|
||||
buf := make([]byte, 16+len(output))
|
||||
binary.LittleEndian.PutUint16(buf[0:2], componentRDPDR)
|
||||
binary.LittleEndian.PutUint16(buf[2:4], pakIDDeviceIOCompletion)
|
||||
binary.LittleEndian.PutUint32(buf[4:8], deviceID)
|
||||
binary.LittleEndian.PutUint32(buf[8:12], completionID)
|
||||
binary.LittleEndian.PutUint32(buf[12:16], status)
|
||||
copy(buf[16:], output)
|
||||
h.send(buf)
|
||||
}
|
||||
|
||||
func (h *Handler) allocFileID() uint32 {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
id := h.nextFileID
|
||||
h.nextFileID++
|
||||
return id
|
||||
}
|
||||
|
||||
func (h *Handler) storeOpenFile(file *openFile) uint32 {
|
||||
id := h.allocFileID()
|
||||
h.mu.Lock()
|
||||
h.files[id] = file
|
||||
h.mu.Unlock()
|
||||
return id
|
||||
}
|
||||
|
||||
func (h *Handler) getOpenFile(id uint32) (*openFile, bool) {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
file, ok := h.files[id]
|
||||
return file, ok
|
||||
}
|
||||
|
||||
func (h *Handler) removeOpenFile(id uint32) (*openFile, bool) {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
file, ok := h.files[id]
|
||||
if ok {
|
||||
delete(h.files, id)
|
||||
}
|
||||
return file, ok
|
||||
}
|
||||
|
||||
func (h *Handler) handleDeviceReply(body []byte) {
|
||||
if len(body) < 8 {
|
||||
return
|
||||
}
|
||||
deviceID := binary.LittleEndian.Uint32(body[0:4])
|
||||
status := binary.LittleEndian.Uint32(body[4:8])
|
||||
if status == statusSuccess {
|
||||
h.mu.Lock()
|
||||
h.deviceAnnounced = true
|
||||
h.mu.Unlock()
|
||||
}
|
||||
log.Printf("[rdpdr] device reply deviceID=%d status=0x%08x", deviceID, status)
|
||||
}
|
||||
|
||||
func writeLE16(w *bytes.Buffer, v uint16) {
|
||||
_ = binary.Write(w, binary.LittleEndian, v)
|
||||
}
|
||||
|
||||
func writeLE32(w *bytes.Buffer, v uint32) {
|
||||
_ = binary.Write(w, binary.LittleEndian, v)
|
||||
}
|
||||
|
||||
func (h *Handler) String() string {
|
||||
return fmt.Sprintf("rdpdr(channel=%d)", h.channelID)
|
||||
}
|
||||
Reference in new issue
Block a user