@@ -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 , stdout io.Writer , stdin io.Reader ) (* os.File , * 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 , nil , err
98
+ }
99
+ go func () {
100
+ io .Copy (stdout , cmdPty )
101
+ wg .Done ()
102
+ }()
103
+ go func () {
104
+ io .Copy (cmdPty , stdin )
105
+ wg .Done ()
106
+ }()
107
+
108
+ return cmdPty , & 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 {
@@ -247,18 +270,31 @@ func handleChannel(conn *ssh.ServerConn, newChan ssh.NewChannel, execHandler []s
247
270
log .Println ("newChan.Accept failed:" , err )
248
271
return
249
272
}
250
- defer ch .Close ()
273
+
274
+ assert := func (at string , err error ) bool {
275
+ if err != nil {
276
+ log .Printf ("%s failed: %s" , at , err )
277
+ ch .Stderr ().Write ([]byte ("Internal error.\n " ))
278
+ return true
279
+ }
280
+ return false
281
+ }
282
+
283
+ var stdout , stderr io.Writer
284
+ if * debug {
285
+ stdout = io .MultiWriter (ch , os .Stdout )
286
+ stderr = io .MultiWriter (ch .Stderr (), os .Stdout )
287
+ } else {
288
+ stdout = ch
289
+ stderr = ch .Stderr ()
290
+ }
291
+
292
+ var ptyShell * os.File
293
+
251
294
for req := range reqs {
252
295
switch req .Type {
253
296
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
- }
297
+ defer ch .Close ()
262
298
263
299
if req .WantReply {
264
300
req .Reply (true , nil )
@@ -287,14 +323,6 @@ func handleChannel(conn *ssh.ServerConn, newChan ssh.NewChannel, execHandler []s
287
323
cmd .Env = append (cmd .Env , "USER=" + conn .Permissions .Extensions ["user" ])
288
324
}
289
325
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
326
done , err := attachCmd (cmd , stdout , stderr , ch )
299
327
if assert ("attachCmd" , err ) {
300
328
return
@@ -310,12 +338,52 @@ func handleChannel(conn *ssh.ServerConn, newChan ssh.NewChannel, execHandler []s
310
338
_ , err = ch .SendRequest ("exit-status" , false , ssh .Marshal (& status ))
311
339
assert ("sendExit" , err )
312
340
return
313
- case "env" :
314
- if req .WantReply {
341
+ case "pty-req" :
342
+ width , height , okSize := parsePtyRequest (req .Payload )
343
+
344
+ var cmd * exec.Cmd
345
+ if * shell {
346
+ cmd = exec .Command (os .Getenv ("SHELL" ))
347
+ } else {
348
+ cmd = exec .Command (execHandler [0 ], execHandler [1 :]... )
349
+ }
350
+ if ! * env {
351
+ cmd .Env = []string {}
352
+ }
353
+ if conn .Permissions != nil {
354
+ // Using Permissions.Extensions as a way to get state from PublicKeyCallback
355
+ if conn .Permissions .Extensions ["environ" ] != "" {
356
+ cmd .Env = append (cmd .Env , strings .Split (conn .Permissions .Extensions ["environ" ], "\n " )... )
357
+ }
358
+ cmd .Env = append (cmd .Env , "USER=" + conn .Permissions .Extensions ["user" ])
359
+ }
360
+ ptyShell , _ , err := attachShell (cmd , stdout , ch )
361
+ if assert ("attachShell" , err ) {
362
+ ch .Close ()
363
+ return
364
+ }
365
+ if okSize {
366
+ setWinsize (ptyShell .Fd (), width , height )
315
367
req .Reply (true , nil )
316
368
}
317
- default :
318
- return
369
+
370
+ go func () {
371
+ status , err := exitStatus (cmd .Wait ())
372
+ if ! assert ("exitStatus" , err ) {
373
+ _ , err := ch .SendRequest ("exit-status" , false , ssh .Marshal (& status ))
374
+ assert ("sendExit" , err )
375
+ }
376
+ ch .Close ()
377
+ }()
378
+ case "window-change" :
379
+ width , height , okSize := parsePtyRequest (req .Payload )
380
+ if okSize {
381
+ setWinsize (ptyShell .Fd (), width , height )
382
+ }
383
+ }
384
+
385
+ if req .WantReply {
386
+ req .Reply (true , nil )
319
387
}
320
388
}
321
389
}
0 commit comments