@@ -35,29 +35,36 @@ import (
3535// in place of the 'ssh' executable.
3636const EnvShellSSH = "SSH"
3737
38- func SSHArguments () (arg0 string , arg0Args []string , err error ) {
38+ type SSHExe struct {
39+ Exe string
40+ Args []string
41+ }
42+
43+ func NewSSHExe () (SSHExe , error ) {
44+ var sshExe SSHExe
45+
3946 if sshShell := os .Getenv (EnvShellSSH ); sshShell != "" {
4047 sshShellFields , err := shellwords .Parse (sshShell )
4148 switch {
4249 case err != nil :
4350 logrus .WithError (err ).Warnf ("Failed to split %s variable into shell tokens. " +
4451 "Falling back to 'ssh' command" , EnvShellSSH )
4552 case len (sshShellFields ) > 0 :
46- arg0 = sshShellFields [0 ]
53+ sshExe . Exe = sshShellFields [0 ]
4754 if len (sshShellFields ) > 1 {
48- arg0Args = sshShellFields [1 :]
55+ sshExe . Args = sshShellFields [1 :]
4956 }
57+ return sshExe , nil
5058 }
5159 }
5260
53- if arg0 == "" {
54- arg0 , err = exec .LookPath ("ssh" )
55- if err != nil {
56- return "" , []string {"" }, err
57- }
61+ executable , err := exec .LookPath ("ssh" )
62+ if err != nil {
63+ return SSHExe {}, err
5864 }
65+ sshExe .Exe = executable
5966
60- return arg0 , arg0Args , nil
67+ return sshExe , nil
6168}
6269
6370type PubKey struct {
@@ -177,7 +184,7 @@ var sshInfo struct {
177184//
178185// The result always contains the IdentityFile option.
179186// The result never contains the Port option.
180- func CommonOpts (sshPath string , useDotSSH bool ) ([]string , error ) {
187+ func CommonOpts (sshExe SSHExe , useDotSSH bool ) ([]string , error ) {
181188 configDir , err := dirnames .LimaConfigDir ()
182189 if err != nil {
183190 return nil , err
@@ -243,7 +250,7 @@ func CommonOpts(sshPath string, useDotSSH bool) ([]string, error) {
243250
244251 sshInfo .Do (func () {
245252 sshInfo .aesAccelerated = detectAESAcceleration ()
246- sshInfo .openSSH = detectOpenSSHInfo (sshPath )
253+ sshInfo .openSSH = detectOpenSSHInfo (sshExe )
247254 })
248255
249256 if sshInfo .openSSH .GSSAPISupported {
@@ -287,12 +294,12 @@ func identityFileEntry(privateKeyPath string) (string, error) {
287294}
288295
289296// SSHOpts adds the following options to CommonOptions: User, ControlMaster, ControlPath, ControlPersist.
290- func SSHOpts (sshPath , instDir , username string , useDotSSH , forwardAgent , forwardX11 , forwardX11Trusted bool ) ([]string , error ) {
297+ func SSHOpts (sshExe SSHExe , instDir , username string , useDotSSH , forwardAgent , forwardX11 , forwardX11Trusted bool ) ([]string , error ) {
291298 controlSock := filepath .Join (instDir , filenames .SSHSock )
292299 if len (controlSock ) >= osutil .UnixPathMax {
293300 return nil , fmt .Errorf ("socket path %q is too long: >= UNIX_PATH_MAX=%d" , controlSock , osutil .UnixPathMax )
294301 }
295- opts , err := CommonOpts (sshPath , useDotSSH )
302+ opts , err := CommonOpts (sshExe , useDotSSH )
296303 if err != nil {
297304 return nil , err
298305 }
@@ -361,27 +368,29 @@ var (
361368 openSSHInfosRW sync.RWMutex
362369)
363370
364- func detectOpenSSHInfo (ssh string ) openSSHInfo {
371+ func detectOpenSSHInfo (sshExe SSHExe ) openSSHInfo {
365372 var (
366373 info openSSHInfo
367374 exe sshExecutable
368375 stderr bytes.Buffer
369376 )
370- path , err := exec . LookPath ( ssh )
371- if err != nil {
372- logrus . Warnf ( "failed to find ssh executable: %v" , err )
373- } else {
374- st , _ := os .Stat (path )
375- exe = sshExecutable {Path : path , Size : st .Size (), ModTime : st .ModTime ()}
377+ // Note: For SSH wrappers like "kitten ssh", os.Stat will check the wrapper
378+ // executable (kitten) instead of the underlying ssh binary. This means
379+ // cache invalidation won't work properly - ssh upgrades won't be detected
380+ // since kitten's size/mtime won't change. This is probably acceptable.
381+ if st , err := os .Stat (sshExe . Exe ); err == nil {
382+ exe = sshExecutable {Path : sshExe . Exe , Size : st .Size (), ModTime : st .ModTime ()}
376383 openSSHInfosRW .RLock ()
377384 info := openSSHInfos [exe ]
378385 openSSHInfosRW .RUnlock ()
379386 if info != nil {
380387 return * info
381388 }
382389 }
390+ sshArgs := append ([]string {}, sshExe .Args ... )
383391 // -V should be last
384- cmd := exec .Command (path , "-o" , "GSSAPIAuthentication=no" , "-V" )
392+ sshArgs = append (sshArgs , "-o" , "GSSAPIAuthentication=no" , "-V" )
393+ cmd := exec .Command (sshExe .Exe , sshArgs ... )
385394 cmd .Stderr = & stderr
386395 if err := cmd .Run (); err != nil {
387396 logrus .Warnf ("failed to run %v: stderr=%q" , cmd .Args , stderr .String ())
@@ -398,8 +407,8 @@ func detectOpenSSHInfo(ssh string) openSSHInfo {
398407 return info
399408}
400409
401- func DetectOpenSSHVersion (ssh string ) semver.Version {
402- return detectOpenSSHInfo (ssh ).Version
410+ func DetectOpenSSHVersion (sshExe SSHExe ) semver.Version {
411+ return detectOpenSSHInfo (sshExe ).Version
403412}
404413
405414// detectValidPublicKey returns whether content represent a public key.
0 commit comments