Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
38e44f7b4a |
+5
-2
@@ -31,7 +31,10 @@ pass, err := remote.GetVncPassword()
|
|||||||
cfg := remote.VncServerDefaultConfig(true /* 随机密码 */) // VncServerConfig
|
cfg := remote.VncServerDefaultConfig(true /* 随机密码 */) // VncServerConfig
|
||||||
err = remote.WriteVncServerConfig(cfg)
|
err = remote.WriteVncServerConfig(cfg)
|
||||||
cfg, err = remote.ReadVncServerConfig()
|
cfg, err = remote.ReadVncServerConfig()
|
||||||
_, msg, err := remote.InstallVncServer() // 安装服务(需管理员权限)
|
// 绿色安装最新版 TightVNC:校验 exe → 写注册表全集(随机密码, 需管理员权限) →
|
||||||
|
// 注册系统服务(装而不启, 远程时再 VncStart; 已运行则重启加载新配置)。
|
||||||
|
// 返回生成的明文密码供调用方上报/展示。
|
||||||
|
pass, err := remote.InstallVncServer(`D:\app\tvnc\tvnserver.exe`)
|
||||||
err = remote.VncInit() // 初始化/修复配置
|
err = remote.VncInit() // 初始化/修复配置
|
||||||
err = remote.VncStart() // VncStop / VncRestart
|
err = remote.VncStart() // VncStop / VncRestart
|
||||||
err = remote.VncInstall() // VncUninstall
|
err = remote.VncInstall() // VncUninstall
|
||||||
@@ -55,6 +58,6 @@ err = remote.EnableSoftwareSASGeneration()
|
|||||||
|
|
||||||
- `GetServiceStatus` 系列对未安装服务返回 -1 而非错误;`GetRdpServiceStatus` /
|
- `GetServiceStatus` 系列对未安装服务返回 -1 而非错误;`GetRdpServiceStatus` /
|
||||||
`GetVncServiceStatus` 走 `kardianos/service`,未安装返回 error。
|
`GetVncServiceStatus` 走 `kardianos/service`,未安装返回 error。
|
||||||
- `InstallVncServer` / `VncInit` / CAD 开关需要管理员权限,失败信息见返回 msg 或 error。
|
- `InstallVncServer` / `VncInit` / CAD 开关需要管理员权限,失败信息见返回 error;安装配置对齐最新版 TightVNC(2.8.x) 注册表 37 项全集。
|
||||||
- VNC 密码格式与 TightVNC 兼容(`KarpelesLab/vncpasswd` 实现),加解密为确定性
|
- VNC 密码格式与 TightVNC 兼容(`KarpelesLab/vncpasswd` 实现),加解密为确定性
|
||||||
变换,注意密文落盘位置的访问控制。
|
变换,注意密文落盘位置的访问控制。
|
||||||
|
|||||||
@@ -49,3 +49,45 @@ func TestVncServerDefaultConfig(t *testing.T) {
|
|||||||
t.Error("HttpPort 不应为 0")
|
t.Error("HttpPort 不应为 0")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestVncClientDefaultConfigLatest 锁定对最新版 TightVNC(2.8.x) 注册表全集的支持:
|
||||||
|
// 值与本机最新版安装器落盘值逐项对齐(AcceptHttpConnections/RunControlInterface
|
||||||
|
// 为安全策略有意取 0:不开 5800 Web 通道、不显托盘)。
|
||||||
|
func TestVncClientDefaultConfigLatest(t *testing.T) {
|
||||||
|
cfg := VncClientDefaultConfig(false)
|
||||||
|
if cfg.RfbPort != 5900 || cfg.HttpPort != 5800 {
|
||||||
|
t.Errorf("端口 = %d/%d, want 5900/5800", cfg.RfbPort, cfg.HttpPort)
|
||||||
|
}
|
||||||
|
// 最新版捕获能力:镜像驱动 + D3D + 透明窗口 + 视频区域识别
|
||||||
|
if cfg.UseMirrorDriver != 1 || cfg.UseD3D != 1 {
|
||||||
|
t.Errorf("捕获开关 = mirror:%d d3d:%d, want 1/1", cfg.UseMirrorDriver, cfg.UseD3D)
|
||||||
|
}
|
||||||
|
if cfg.GrabTransparentWindows != 1 || cfg.VideoRecognitionInterval != 3000 {
|
||||||
|
t.Errorf("透明窗口/视频识别 = %d/%d, want 1/3000", cfg.GrabTransparentWindows, cfg.VideoRecognitionInterval)
|
||||||
|
}
|
||||||
|
// 安全策略:不开 HTTP 通道、不出托盘控制台、不接 RDP 会话
|
||||||
|
if cfg.AcceptHttpConnections != 0 || cfg.RunControlInterface != 0 || cfg.ConnectToRdp != 0 {
|
||||||
|
t.Errorf("安全策略 = http:%d tray:%d rdp:%d, want 0/0/0", cfg.AcceptHttpConnections, cfg.RunControlInterface, cfg.ConnectToRdp)
|
||||||
|
}
|
||||||
|
// 连接行为:VNC 认证 + 接受 RFB + 本机输入优先 + 断开踢旧连接
|
||||||
|
if cfg.AcceptRfbConnections != 1 || cfg.UseVncAuthentication != 1 || cfg.LocalInputPriority != 1 || cfg.DisconnectClients != 1 {
|
||||||
|
t.Errorf("连接行为异常: %+v", cfg)
|
||||||
|
}
|
||||||
|
if cfg.QueryTimeout != 30 || cfg.LocalInputPriorityTimeout != 3 || cfg.PollingInterval != 1000 || cfg.AllowLoopback != 1 {
|
||||||
|
t.Errorf("超时/轮询/环回异常: %+v", cfg)
|
||||||
|
}
|
||||||
|
if len(cfg.Password) == 0 {
|
||||||
|
t.Error("固定密码密文不应为空")
|
||||||
|
}
|
||||||
|
if dec := VncPasswordDecrypt(cfg.Password); dec != "tvncpass" {
|
||||||
|
t.Errorf("固定密码回解 = %q, want tvncpass", dec)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestInstallVncServerMissingExe 安装入口对 tvnserver.exe 缺失必须报错(不触注册表/服务)
|
||||||
|
func TestInstallVncServerMissingExe(t *testing.T) {
|
||||||
|
_, err := InstallVncServer(`Z:\no\such\dir\tvnserver.exe`)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("exe 不存在时应返回错误")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+39
-48
@@ -3,6 +3,9 @@
|
|||||||
package remote
|
package remote
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/KarpelesLab/vncpasswd"
|
"github.com/KarpelesLab/vncpasswd"
|
||||||
"github.com/kardianos/service"
|
"github.com/kardianos/service"
|
||||||
"golang.org/x/sys/windows/registry"
|
"golang.org/x/sys/windows/registry"
|
||||||
@@ -10,47 +13,9 @@ import (
|
|||||||
|
|
||||||
var VncService service.Service
|
var VncService service.Service
|
||||||
|
|
||||||
// ReadVncServerConfig 从注册表读取 TightVNC 服务端配置
|
// ReadVncServerConfig 从注册表读取 TightVNC 服务端配置(键值缺失按零值返回)
|
||||||
func ReadVncServerConfig() (VncServerConfig, error) {
|
func ReadVncServerConfig() (VncServerConfig, error) {
|
||||||
config := VncServerConfig{
|
var config VncServerConfig
|
||||||
ExtraPorts: "",
|
|
||||||
QueryTimeout: 0,
|
|
||||||
QueryAcceptOnTimeout: 0,
|
|
||||||
LocalInputPriorityTimeout: 0,
|
|
||||||
LocalInputPriority: 0,
|
|
||||||
BlockRemoteInput: 0,
|
|
||||||
BlockLocalInput: 0,
|
|
||||||
IpAccessControl: "",
|
|
||||||
RfbPort: 0,
|
|
||||||
HttpPort: 0,
|
|
||||||
DisconnectAction: 0,
|
|
||||||
AcceptRfbConnections: 0,
|
|
||||||
UseVncAuthentication: 0,
|
|
||||||
UseControlAuthentication: 0,
|
|
||||||
RepeatControlAuthentication: 0,
|
|
||||||
LoopbackOnly: 0,
|
|
||||||
AcceptHttpConnections: 0,
|
|
||||||
LogLevel: 0,
|
|
||||||
EnableFileTransfers: 0,
|
|
||||||
RemoveWallpaper: 0,
|
|
||||||
UseD3D: 0,
|
|
||||||
UseMirrorDriver: 0,
|
|
||||||
EnableUrlParams: 0,
|
|
||||||
Password: []byte{},
|
|
||||||
AlwaysShared: 0,
|
|
||||||
NeverShared: 0,
|
|
||||||
DisconnectClients: 0,
|
|
||||||
PollingInterval: 0,
|
|
||||||
AllowLoopback: 0,
|
|
||||||
VideoRecognitionInterval: 0,
|
|
||||||
GrabTransparentWindows: 0,
|
|
||||||
SaveLogToAllUsersPath: 0,
|
|
||||||
RunControlInterface: 0,
|
|
||||||
IdleTimeout: 0,
|
|
||||||
VideoClasses: "",
|
|
||||||
VideoRects: "",
|
|
||||||
ConnectToRdp: 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
key, err := registry.OpenKey(registry.LOCAL_MACHINE, "SOFTWARE\\TightVNC\\Server", registry.ALL_ACCESS)
|
key, err := registry.OpenKey(registry.LOCAL_MACHINE, "SOFTWARE\\TightVNC\\Server", registry.ALL_ACCESS)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -93,7 +58,7 @@ func ReadVncServerConfig() (VncServerConfig, error) {
|
|||||||
config.IdleTimeout, _, _ = GetDwordValue(key, "IdleTimeout")
|
config.IdleTimeout, _, _ = GetDwordValue(key, "IdleTimeout")
|
||||||
config.VideoClasses, _, _ = key.GetStringValue("VideoClasses")
|
config.VideoClasses, _, _ = key.GetStringValue("VideoClasses")
|
||||||
config.VideoRects, _, _ = key.GetStringValue("VideoRects")
|
config.VideoRects, _, _ = key.GetStringValue("VideoRects")
|
||||||
config.ConnectToRdp, _, _ = GetDwordValue(key, "ExtraPorts")
|
config.ConnectToRdp, _, _ = GetDwordValue(key, "ConnectToRdp")
|
||||||
|
|
||||||
return config, nil
|
return config, nil
|
||||||
}
|
}
|
||||||
@@ -109,16 +74,42 @@ func GetDwordValue(k registry.Key, name string) (val uint32, valtype uint32, err
|
|||||||
var vncSvcConfig = &service.Config{
|
var vncSvcConfig = &service.Config{
|
||||||
Name: "tvnserver",
|
Name: "tvnserver",
|
||||||
DisplayName: "TightVNC Server",
|
DisplayName: "TightVNC Server",
|
||||||
Description: "",
|
Description: "TightVNC Server (managed by remote-core)",
|
||||||
Arguments: []string{"-service"},
|
Arguments: []string{"-service"},
|
||||||
}
|
}
|
||||||
|
|
||||||
// InstallVncServer 安装VNC服务端 返回:结果,密码,错误
|
// InstallVncServer 绿色安装 TightVNC 服务端(需管理员权限)。
|
||||||
func InstallVncServer() (bool, string, error) {
|
// 最新版注册表全集配置(VncClientDefaultConfig 37 项, 随机 8 位密码)→ 注册系统服务。
|
||||||
//安装服务
|
// 装而不启:服务注册后保持 Stopped, 等远程指令再 VncStart(避免常驻监听开端口);
|
||||||
//导入注册表
|
// 若服务已在运行则重启以加载新配置。返回生成的明文密码(调用方自行上报/展示)。
|
||||||
//生成随机密码
|
func InstallVncServer(exePath string) (string, error) {
|
||||||
return true, "12345678", nil
|
if finfo, err := os.Stat(exePath); err != nil || finfo.IsDir() {
|
||||||
|
return "", fmt.Errorf("tvnserver.exe 不存在: %s", exePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 最新版 TightVNC 注册表配置全集(含随机密码)
|
||||||
|
config := VncClientDefaultConfig(true)
|
||||||
|
if err := WriteVncServerConfig(config); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注册系统服务:未安装→安装; 运行中→重启加载新配置; 已停止→保持
|
||||||
|
vncSvcConfig.Executable = exePath
|
||||||
|
if err := VncInit(); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
status, err := VncService.Status()
|
||||||
|
switch {
|
||||||
|
case err != nil: // 未安装(kardianos 对未安装服务 Status 返回错误)
|
||||||
|
if err := VncInstall(); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
case status == service.StatusRunning:
|
||||||
|
if err := VncRestart(); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return VncPasswordDecrypt(config.Password), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// VncInit 初始化 VNC 服务对象,VncInstall/VncStart 等操作前必须调用
|
// VncInit 初始化 VNC 服务对象,VncInstall/VncStart 等操作前必须调用
|
||||||
|
|||||||
@@ -280,6 +280,10 @@ func WriteVncServerConfig(config VncServerConfig) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
err = key.SetDWordValue("LogLevel", config.LogLevel)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
err = key.SetDWordValue("EnableFileTransfers", config.EnableFileTransfers)
|
err = key.SetDWordValue("EnableFileTransfers", config.EnableFileTransfers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
Reference in New Issue
Block a user