@@ -20,6 +20,7 @@ import (
20
20
21
21
"code.google.com/p/go.crypto/ssh"
22
22
"github.com/flynn/go-shlex"
23
+ "github.com/kr/pty"
23
24
)
24
25
25
26
var host = flag .String ("h" , "" , "host ip to listen on" )
@@ -49,7 +50,7 @@ func exitStatus(err error) (exitStatusMsg, error) {
49
50
return exitStatusMsg {0 }, nil
50
51
}
51
52
52
- func attachCmd (cmd * exec.Cmd , stdout , stderr io.Writer , stdin io.Reader ) (* sync.WaitGroup , error ) {
53
+ func attachCmd (cmd * exec.Cmd , stdout io. Writer , stderr io.Writer , stdin io.Reader ) (* sync.WaitGroup , error ) {
53
54
var wg sync.WaitGroup
54
55
wg .Add (2 )
55
56
@@ -61,6 +62,7 @@ func attachCmd(cmd *exec.Cmd, stdout, stderr io.Writer, stdin io.Reader) (*sync.
61
62
go func () {
62
63
io .Copy (stdinIn , stdin )
63
64
stdinIn .Close ()
65
+ // FIXME: Do we care that this is not part of the WaitGroup?
64
66
}()
65
67
}
66
68
@@ -85,6 +87,27 @@ func attachCmd(cmd *exec.Cmd, stdout, stderr io.Writer, stdin io.Reader) (*sync.
85
87
return & wg , nil
86
88
}
87
89
90
+ func attachShell (cmd * exec.Cmd , ch ssh.Channel ) (* sync.WaitGroup , error ) {
91
+ var wg sync.WaitGroup
92
+ wg .Add (2 )
93
+
94
+ // Note that pty merges stdout and stderr.
95
+ cmdPty , err := pty .Start (cmd )
96
+ if err != nil {
97
+ return nil , err
98
+ }
99
+ go func () {
100
+ io .Copy (ch , cmdPty )
101
+ wg .Done ()
102
+ }()
103
+ go func () {
104
+ io .Copy (cmdPty , ch )
105
+ wg .Done ()
106
+ }()
107
+
108
+ return & wg , nil
109
+ }
110
+
88
111
func addKey (conf * ssh.ServerConfig , block * pem.Block ) (err error ) {
89
112
var key interface {}
90
113
switch block .Type {
@@ -248,18 +271,30 @@ func handleChannel(conn *ssh.ServerConn, newChan ssh.NewChannel, execHandler []s
248
271
return
249
272
}
250
273
defer ch .Close ()
274
+
275
+ assert := func (at string , err error ) bool {
276
+ if err != nil {
277
+ log .Printf ("%s failed: %s" , at , err )
278
+ ch .Stderr ().Write ([]byte ("Internal error.\n " ))
279
+ return true
280
+ }
281
+ return false
282
+ }
283
+
284
+ var stdout , stderr io.Writer
285
+ if * debug {
286
+ stdout = io .MultiWriter (ch , os .Stdout )
287
+ stderr = io .MultiWriter (ch .Stderr (), os .Stdout )
288
+ } else {
289
+ stdout = ch
290
+ stderr = ch .Stderr ()
291
+ }
292
+
293
+ // FIXME: We currently bail out as soon as a req is matched. Technically ssh
294
+ // can send multiple requests out of band, which we might want to handle?
251
295
for req := range reqs {
252
296
switch req .Type {
253
297
case "exec" :
254
- assert := func (at string , err error ) bool {
255
- if err != nil {
256
- log .Printf ("%s failed: %s" , at , err )
257
- ch .Stderr ().Write ([]byte ("Internal error.\n " ))
258
- return true
259
- }
260
- return false
261
- }
262
-
263
298
if req .WantReply {
264
299
req .Reply (true , nil )
265
300
}
@@ -287,14 +322,6 @@ func handleChannel(conn *ssh.ServerConn, newChan ssh.NewChannel, execHandler []s
287
322
cmd .Env = append (cmd .Env , "USER=" + conn .Permissions .Extensions ["user" ])
288
323
}
289
324
cmd .Env = append (cmd .Env , "SSH_ORIGINAL_COMMAND=" + cmdline )
290
- var stdout , stderr io.Writer
291
- if * debug {
292
- stdout = io .MultiWriter (ch , os .Stdout )
293
- stderr = io .MultiWriter (ch .Stderr (), os .Stdout )
294
- } else {
295
- stdout = ch
296
- stderr = ch .Stderr ()
297
- }
298
325
done , err := attachCmd (cmd , stdout , stderr , ch )
299
326
if assert ("attachCmd" , err ) {
300
327
return
@@ -310,12 +337,38 @@ func handleChannel(conn *ssh.ServerConn, newChan ssh.NewChannel, execHandler []s
310
337
_ , err = ch .SendRequest ("exit-status" , false , ssh .Marshal (& status ))
311
338
assert ("sendExit" , err )
312
339
return
313
- case "env" :
314
- if req .WantReply {
315
- req .Reply (true , nil )
340
+ case "pty-req" :
341
+ // TODO: Handle "shell" which technically comes after "pty-req".
342
+ // TODO: Handle "window-change" for pty
343
+ // TODO: Parse payload to set initial window dimensions
344
+ // XXX: Obey handler
345
+ cmd := exec .Command ("/usr/local/bin/bash" )
346
+ if ! * env {
347
+ cmd .Env = []string {}
348
+ }
349
+ if conn .Permissions != nil {
350
+ // Using Permissions.Extensions as a way to get state from PublicKeyCallback
351
+ if conn .Permissions .Extensions ["environ" ] != "" {
352
+ cmd .Env = append (cmd .Env , strings .Split (conn .Permissions .Extensions ["environ" ], "\n " )... )
353
+ }
354
+ cmd .Env = append (cmd .Env , "USER=" + conn .Permissions .Extensions ["user" ])
355
+ }
356
+ // TODO: Pass in stdout/stdin to support our mitm'ing above
357
+ done , err := attachShell (cmd , ch )
358
+ if assert ("attachShell" , err ) {
359
+ return
316
360
}
317
- default :
361
+ done .Wait ()
362
+ _ , err = exitStatus (cmd .Wait ())
363
+ if assert ("exitStatus" , err ) {
364
+ return
365
+ }
366
+ // FIXME: Do we want to send anything back before closing?
318
367
return
319
368
}
369
+
370
+ if req .WantReply {
371
+ req .Reply (true , nil )
372
+ }
320
373
}
321
374
}
0 commit comments