//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) }