Skip to content

Commit bc99aca

Browse files
committed
sshutil: detect Win32-OpenSSH version, export PathForSSH
Two related changes used together by callers that talk to the ssh family of binaries on Windows: ParseOpenSSHVersion: also match "OpenSSH_for_Windows_X.YpZ" (the version banner emitted by native Windows OpenSSH). Previously the regex required a digit immediately after "OpenSSH_", so Win32-OpenSSH was misdetected as version 0.0.0 and Lima then treated it as pre-8.0 legacy ssh in code paths that branch on the version (e.g. scp URL form). PathForSSH: rename from the previously unexported pathForSSH and export it. copytool.parseCopyPaths needs the same path-translation logic, and duplicating the cygpath-vs-native decision in two packages would invite drift. Add a test for the Win32-OpenSSH version banner. Signed-off-by: Jan Dubois <jan.dubois@suse.com>
1 parent ce3a069 commit bc99aca

2 files changed

Lines changed: 15 additions & 9 deletions

File tree

pkg/sshutil/sshutil.go

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -90,14 +90,14 @@ func IsSSHCygwin(sshExe SSHExe) bool {
9090
return err == nil
9191
}
9292

93-
// pathForSSH converts orig to the path form expected by the ssh and ssh-keygen
94-
// binaries that Lima will invoke. On non-Windows the path is returned unchanged.
95-
// On Windows:
93+
// PathForSSH converts orig to the path form expected by the ssh family of
94+
// binaries (ssh, ssh-keygen, scp, sftp, rsync-over-ssh) that Lima will invoke.
95+
// On non-Windows the path is returned unchanged. On Windows:
9696
// - For Cygwin-based ssh (Git for Windows, MSYS2): runs cygpath to produce a
9797
// Cygwin-style path like /c/Users/...
9898
// - For native Windows OpenSSH: returns the path with forward slashes
99-
// (e.g. C:/Users/...), which native ssh-keygen and ssh accept.
100-
func pathForSSH(ctx context.Context, sshExe SSHExe, orig string) (string, error) {
99+
// (e.g. C:/Users/...), which native ssh, ssh-keygen, and scp accept.
100+
func PathForSSH(ctx context.Context, sshExe SSHExe, orig string) (string, error) {
101101
if runtime.GOOS != "windows" {
102102
return orig, nil
103103
}
@@ -152,7 +152,7 @@ func DefaultPubKeys(ctx context.Context, loadDotSSH bool) ([]PubKey, error) {
152152
if sshErr != nil {
153153
return sshErr
154154
}
155-
privPath, err = pathForSSH(ctx, sshExe, privPath)
155+
privPath, err = PathForSSH(ctx, sshExe, privPath)
156156
if err != nil {
157157
return err
158158
}
@@ -328,7 +328,7 @@ func CommonOpts(ctx context.Context, sshExe SSHExe, useDotSSH bool) ([]string, e
328328

329329
func identityFileEntry(ctx context.Context, sshExe SSHExe, privateKeyPath string) (string, error) {
330330
if runtime.GOOS == "windows" {
331-
privateKeyPath, err := pathForSSH(ctx, sshExe, privateKeyPath)
331+
privateKeyPath, err := PathForSSH(ctx, sshExe, privateKeyPath)
332332
if err != nil {
333333
return "", err
334334
}
@@ -386,7 +386,7 @@ func SSHOpts(ctx context.Context, sshExe SSHExe, instDir, username string, useDo
386386
}
387387
controlPath := fmt.Sprintf(`ControlPath="%s"`, controlSock)
388388
if runtime.GOOS == "windows" {
389-
controlSock, err = pathForSSH(ctx, sshExe, controlSock)
389+
controlSock, err = PathForSSH(ctx, sshExe, controlSock)
390390
if err != nil {
391391
return nil, err
392392
}
@@ -430,7 +430,10 @@ func SSHOptsRemovingControlPath(opts []string) []string {
430430
}
431431

432432
func ParseOpenSSHVersion(version []byte) *semver.Version {
433-
regex := regexp.MustCompile(`(?m)^OpenSSH_(\d+\.\d+)(?:p(\d+))?\b`)
433+
// Matches "OpenSSH_8.4p1 ..." (upstream) and "OpenSSH_for_Windows_9.5p2 ..."
434+
// (Win32-OpenSSH). The optional "_for_Windows" prefix is recognized so native
435+
// Windows OpenSSH is not misdetected as version 0.0.0.
436+
regex := regexp.MustCompile(`(?m)^OpenSSH(?:_for_Windows)?_(\d+\.\d+)(?:p(\d+))?\b`)
434437
matches := regex.FindSubmatch(version)
435438
if len(matches) == 3 {
436439
if len(matches[2]) == 0 {

pkg/sshutil/sshutil_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ func TestParseOpenSSHVersion(t *testing.T) {
3333
// NixOS 25.05
3434
assert.Check(t, ParseOpenSSHVersion([]byte(`command-line line 0: Unsupported option "gssapiauthentication"
3535
OpenSSH_10.0p2, OpenSSL 3.4.1 11 Feb 2025`)).Equal(*semver.New("10.0.2")))
36+
37+
// Native Windows OpenSSH (Win32-OpenSSH)
38+
assert.Check(t, ParseOpenSSHVersion([]byte("OpenSSH_for_Windows_9.5p2, LibreSSL 3.8.2")).Equal(*semver.New("9.5.2")))
3639
}
3740

3841
func TestParseOpenSSHGSSAPISupported(t *testing.T) {

0 commit comments

Comments
 (0)