@@ -10,22 +10,34 @@ import (
10
10
"os/exec"
11
11
"path/filepath"
12
12
"strconv"
13
+ "strings"
13
14
14
15
"github.com/lima-vm/sshocker/pkg/ssh"
15
16
"github.com/lima-vm/sshocker/pkg/util"
16
17
"github.com/pkg/sftp"
17
18
"github.com/sirupsen/logrus"
18
19
)
19
20
21
+ type Driver = string
22
+
23
+ const (
24
+ DriverAuto = "auto" // Default
25
+ DriverBuiltin = Driver ("builtin" ) // Legacy. Unrecommended.
26
+ DriverOpensshSftpServer = Driver ("openssh-sftp-server" ) // More robust and secure. Recommended.
27
+ )
28
+
20
29
type ReverseSSHFS struct {
21
30
* ssh.SSHConfig
22
- LocalPath string
23
- Host string
24
- Port int
25
- RemotePath string
26
- Readonly bool
27
- sshCmd * exec.Cmd
28
- SSHFSAdditionalArgs []string
31
+ Driver Driver
32
+ OpensshSftpServerBinary string // used only when Driver == DriverOpensshSftpServer
33
+ LocalPath string
34
+ Host string
35
+ Port int
36
+ RemotePath string
37
+ Readonly bool
38
+ sshCmd * exec.Cmd
39
+ opensshSftpServerCmd * exec.Cmd
40
+ SSHFSAdditionalArgs []string
29
41
}
30
42
31
43
func (rsf * ReverseSSHFS ) Prepare () error {
@@ -48,6 +60,55 @@ func (rsf *ReverseSSHFS) Prepare() error {
48
60
return nil
49
61
}
50
62
63
+ func DetectOpensshSftpServerBinary () string {
64
+ homebrewSSHD := []string {
65
+ "/usr/local/sbin/sshd" ,
66
+ "/opt/homebrew/sbin/sshd" ,
67
+ }
68
+ for _ , f := range homebrewSSHD {
69
+ // sshd is like "/usr/local/Cellar/openssh/8.9p1/sbin/sshd"
70
+ sshd , err := filepath .EvalSymlinks (f )
71
+ if err != nil {
72
+ continue
73
+ }
74
+ // local is like "/usr/local/Cellar/openssh"
75
+ local := filepath .Dir (filepath .Dir (sshd ))
76
+ // sftpServer is like "/usr/local/Cellar/openssh/8.9p1/libexec/sftp-server"
77
+ sftpServer := filepath .Join (local , "libexec" , "sftp-server" )
78
+ if exe , err := exec .LookPath (sftpServer ); err == nil {
79
+ return exe
80
+ }
81
+ }
82
+ candidates := []string {
83
+ "/usr/libexec/sftp-server" , // macOS, OpenWrt
84
+ "/usr/libexec/openssh/sftp-server" , // Fedora
85
+ "/usr/lib/sftp-server" , // Debian (symlink to openssh/sftp-server)
86
+ "/usr/lib/openssh/sftp-server" , // Debian
87
+ "/usr/lib/ssh/sftp-server" , // Alpine
88
+ }
89
+ for _ , cand := range candidates {
90
+ if exe , err := exec .LookPath (cand ); err == nil {
91
+ return exe
92
+ }
93
+ }
94
+ return ""
95
+ }
96
+
97
+ func DetectDriver (explicitOpensshSftpServerBinary string ) (Driver , string , error ) {
98
+ if explicitOpensshSftpServerBinary != "" {
99
+ exe , err := exec .LookPath (explicitOpensshSftpServerBinary )
100
+ if err != nil {
101
+ return "" , "" , err
102
+ }
103
+ return DriverOpensshSftpServer , exe , nil
104
+ }
105
+ exe := DetectOpensshSftpServerBinary ()
106
+ if exe != "" {
107
+ return DriverOpensshSftpServer , exe , nil
108
+ }
109
+ return DriverBuiltin , "" , nil
110
+ }
111
+
51
112
func (rsf * ReverseSSHFS ) Start () error {
52
113
sshBinary := rsf .SSHConfig .Binary ()
53
114
sshArgs := rsf .SSHConfig .Args ()
@@ -68,44 +129,101 @@ func (rsf *ReverseSSHFS) Start() error {
68
129
sshArgs = append (sshArgs , rsf .SSHFSAdditionalArgs ... )
69
130
rsf .sshCmd = exec .Command (sshBinary , sshArgs ... )
70
131
rsf .sshCmd .Stderr = os .Stderr
71
- stdinPipe , err := rsf .sshCmd .StdinPipe ()
72
- if err != nil {
73
- return err
74
- }
75
- stdoutPipe , err := rsf .sshCmd .StdoutPipe ()
76
- if err != nil {
77
- return err
78
- }
79
- stdio := & util.RWC {
80
- ReadCloser : stdoutPipe ,
81
- WriteCloser : stdinPipe ,
82
- }
83
- var sftpOpts []sftp.ServerOption
84
- if rsf .Readonly {
85
- sftpOpts = append (sftpOpts , sftp .ReadOnly ())
132
+ driver := rsf .Driver
133
+ opensshSftpServerBinary := rsf .OpensshSftpServerBinary
134
+ switch driver {
135
+ case DriverBuiltin , DriverOpensshSftpServer :
136
+ // NOP
137
+ case "" , DriverAuto :
138
+ var err error
139
+ driver , opensshSftpServerBinary , err = DetectDriver (opensshSftpServerBinary )
140
+ if err != nil {
141
+ return fmt .Errorf ("failed to choose driver automatically: %w" , err )
142
+ }
143
+ logrus .Debugf ("Chosen driver %q" , driver )
144
+ default :
145
+ return fmt .Errorf ("unknown driver %q" , driver )
86
146
}
87
- // NOTE: sftp.NewServer doesn't support specifying the root.
88
- // https://github.com/pkg/sftp/pull/238
89
- //
90
- // TODO: use sftp.NewRequestServer with custom handlers to mitigate potential vulnerabilities.
91
- server , err := sftp .NewServer (stdio , sftpOpts ... )
92
- if err != nil {
93
- return err
147
+ var builtinSftpServer * sftp.Server
148
+ switch driver {
149
+ case DriverBuiltin :
150
+ stdinPipe , err := rsf .sshCmd .StdinPipe ()
151
+ if err != nil {
152
+ return err
153
+ }
154
+ stdoutPipe , err := rsf .sshCmd .StdoutPipe ()
155
+ if err != nil {
156
+ return err
157
+ }
158
+ stdio := & util.RWC {
159
+ ReadCloser : stdoutPipe ,
160
+ WriteCloser : stdinPipe ,
161
+ }
162
+ var sftpOpts []sftp.ServerOption
163
+ if rsf .Readonly {
164
+ sftpOpts = append (sftpOpts , sftp .ReadOnly ())
165
+ }
166
+ // NOTE: sftp.NewServer doesn't support specifying the root.
167
+ // https://github.com/pkg/sftp/pull/238
168
+ //
169
+ // TODO: use sftp.NewRequestServer with custom handlers to mitigate potential vulnerabilities.
170
+ builtinSftpServer , err = sftp .NewServer (stdio , sftpOpts ... )
171
+ if err != nil {
172
+ return err
173
+ }
174
+ case DriverOpensshSftpServer :
175
+ if opensshSftpServerBinary == "" {
176
+ opensshSftpServerBinary = DetectOpensshSftpServerBinary ()
177
+ if opensshSftpServerBinary == "" {
178
+ return errors .New ("no openssh sftp-server found" )
179
+ }
180
+ }
181
+ logrus .Debugf ("Using OpenSSH SFTP Server %q" , opensshSftpServerBinary )
182
+ sftpServerArgs := []string {
183
+ // `-e` available since OpenSSH 5.4p1 (2010) https://github.com/openssh/openssh-portable/commit/7bee06ab
184
+ "-e" ,
185
+ // `-d` available since OpenSSH 6.2p1 (2013) https://github.com/openssh/openssh-portable/commit/502ab0ef
186
+ "-d" , strings .ReplaceAll (rsf .LocalPath , "%" , "%%" ),
187
+ }
188
+ if rsf .Readonly {
189
+ // `-R` available since OpenSSH 5.4p1 (2010) https://github.com/openssh/openssh-portable/commit/db7bf825
190
+ sftpServerArgs = append (sftpServerArgs , "-R" )
191
+ }
192
+ rsf .opensshSftpServerCmd = exec .Command (opensshSftpServerBinary , sftpServerArgs ... )
193
+ rsf .opensshSftpServerCmd .Stderr = os .Stderr
194
+ var err error
195
+ rsf .opensshSftpServerCmd .Stdin , err = rsf .sshCmd .StdoutPipe ()
196
+ if err != nil {
197
+ return err
198
+ }
199
+ rsf .sshCmd .Stdin , err = rsf .opensshSftpServerCmd .StdoutPipe ()
200
+ if err != nil {
201
+ return err
202
+ }
94
203
}
95
204
logrus .Debugf ("executing ssh for remote sshfs: %s %v" , rsf .sshCmd .Path , rsf .sshCmd .Args )
96
205
if err := rsf .sshCmd .Start (); err != nil {
97
206
return err
98
207
}
99
208
logrus .Debugf ("starting sftp server for %v" , rsf .LocalPath )
100
- go func () {
101
- if srvErr := server .Serve (); srvErr != nil {
102
- if errors .Is (srvErr , io .EOF ) {
103
- logrus .WithError (srvErr ).Debugf ("sftp server for %v exited with EOF (negligible)" , rsf .LocalPath )
104
- } else {
105
- logrus .WithError (srvErr ).Errorf ("sftp server for %v exited" , rsf .LocalPath )
209
+ switch driver {
210
+ case DriverBuiltin :
211
+ go func () {
212
+ if srvErr := builtinSftpServer .Serve (); srvErr != nil {
213
+ if errors .Is (srvErr , io .EOF ) {
214
+ logrus .WithError (srvErr ).Debugf ("sftp server for %v exited with EOF (negligible)" , rsf .LocalPath )
215
+ } else {
216
+ logrus .WithError (srvErr ).Errorf ("sftp server for %v exited" , rsf .LocalPath )
217
+ }
106
218
}
219
+ }()
220
+ case DriverOpensshSftpServer :
221
+ logrus .Debugf ("executing OpenSSH SFTP Server: %s %v" , rsf .opensshSftpServerCmd .Path , rsf .opensshSftpServerCmd .Args )
222
+ if err := rsf .opensshSftpServerCmd .Start (); err != nil {
223
+ return err
107
224
}
108
- }()
225
+ }
226
+ logrus .Debugf ("waiting for remote ready" )
109
227
if err := rsf .waitForRemoteReady (); err != nil {
110
228
// not a fatal error
111
229
logrus .WithError (err ).Warnf ("failed to confirm whether %v [remote] is successfully mounted" , rsf .RemotePath )
@@ -158,9 +276,20 @@ done
158
276
}
159
277
160
278
func (rsf * ReverseSSHFS ) Close () error {
161
- logrus .Debugf ("killing ssh server for remote sshfs: %s %v" , rsf .sshCmd .Path , rsf .sshCmd .Args )
162
- if err := rsf .sshCmd .Process .Kill (); err != nil {
163
- return err
279
+ logrus .Debugf ("killing processes for remote sshfs: %s %v" , rsf .sshCmd .Path , rsf .sshCmd .Args )
280
+ var errors []error
281
+ if rsf .sshCmd != nil && rsf .sshCmd .Process != nil {
282
+ if err := rsf .sshCmd .Process .Kill (); err != nil {
283
+ errors = append (errors , err )
284
+ }
285
+ }
286
+ if rsf .opensshSftpServerCmd != nil && rsf .opensshSftpServerCmd .Process != nil {
287
+ if err := rsf .opensshSftpServerCmd .Process .Kill (); err != nil {
288
+ errors = append (errors , err )
289
+ }
290
+ }
291
+ if len (errors ) > 0 {
292
+ return fmt .Errorf ("%v" , errors )
164
293
}
165
294
return nil
166
295
}
0 commit comments