- 拆分原有单体 ssh.go,按职责重组为 types、utils、transport、login、keepalive、session、exec、pool、shell、terminal、forward、hostkey、state 等模块,并补充平台相关实现 - 重做登录与连接运行时,补齐基于 context 的建连、jump/proxy 链路、可配置认证顺序,以及 Unix/Windows 下的 ssh-agent 支持 - 新增正式非交互执行模型 ExecRequest/ExecResult,支持流式输出、溢出统计、超时控制,以及 posix/powershell/cmd/raw 多方言执行 - 保留旧 shell 风格兼容接口,同时让路径/用户探测等 helper 具备跨 shell fallback,避免 Windows 目标继续硬依赖 POSIX 命令 - 新增 TerminalSession 作为原始交互终端基座,提供 IO attach、resize、signal/control、退出状态与关闭原因管理 - 重构端口转发语义,默认复用当前 SSH 连接,并显式提供 detached 的本地/动态转发模式承载隔离场景 - 梳理 keepalive 与取消语义,区分仅取消本次操作和关闭整条连接,并统一连接状态与传输关闭路径 - 围绕新的 session/连接生命周期重做执行池与运行时支撑 - 大幅增强 SFTP 传输链路,补齐更安全的原子替换、校验、进度回调、重试隔离、可复用 client 生命周期与失败语义 - 新增取消语义、keepalive、SFTP、forward、terminal input 等关键回归测试,提升核心链路稳定性
71 lines
1.8 KiB
Go
71 lines
1.8 KiB
Go
//go:build windows
|
|
|
|
package starssh
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/Microsoft/go-winio"
|
|
"golang.org/x/sys/windows"
|
|
)
|
|
|
|
const defaultWindowsSSHAgentPipe = `\\.\pipe\openssh-ssh-agent`
|
|
|
|
func dialSSHAgent(timeout time.Duration) (net.Conn, error) {
|
|
agentSock := strings.TrimSpace(os.Getenv("SSH_AUTH_SOCK"))
|
|
if agentSock != "" {
|
|
return dialWindowsSSHAgentEndpoint(agentSock, timeout)
|
|
}
|
|
return dialWindowsNamedPipe(defaultWindowsSSHAgentPipe, timeout, true)
|
|
}
|
|
|
|
func dialWindowsSSHAgentEndpoint(endpoint string, timeout time.Duration) (net.Conn, error) {
|
|
if pipePath, ok := normalizeWindowsSSHAgentPipe(endpoint); ok {
|
|
return dialWindowsNamedPipe(pipePath, timeout, false)
|
|
}
|
|
if timeout > 0 {
|
|
return net.DialTimeout("unix", endpoint, timeout)
|
|
}
|
|
return net.Dial("unix", endpoint)
|
|
}
|
|
|
|
func dialWindowsNamedPipe(path string, timeout time.Duration, unavailableOnNotFound bool) (net.Conn, error) {
|
|
ctx := context.Background()
|
|
cancel := func() {}
|
|
if timeout > 0 {
|
|
ctx, cancel = context.WithTimeout(ctx, timeout)
|
|
}
|
|
defer cancel()
|
|
|
|
conn, err := winio.DialPipeContext(ctx, path)
|
|
if err != nil && unavailableOnNotFound && isWindowsPipeUnavailable(err) {
|
|
return nil, errSSHAgentUnavailable
|
|
}
|
|
return conn, err
|
|
}
|
|
|
|
func normalizeWindowsSSHAgentPipe(endpoint string) (string, bool) {
|
|
trimmed := strings.TrimSpace(endpoint)
|
|
if trimmed == "" {
|
|
return "", false
|
|
}
|
|
|
|
normalized := trimmed
|
|
if strings.HasPrefix(normalized, "//./pipe/") {
|
|
normalized = `\\.\pipe\` + strings.TrimPrefix(normalized, "//./pipe/")
|
|
}
|
|
if strings.HasPrefix(normalized, `\\.\pipe\`) {
|
|
return normalized, true
|
|
}
|
|
return "", false
|
|
}
|
|
|
|
func isWindowsPipeUnavailable(err error) bool {
|
|
return errors.Is(err, windows.ERROR_FILE_NOT_FOUND) || errors.Is(err, windows.ERROR_PATH_NOT_FOUND)
|
|
}
|