Skip to content

Commit cf4d88d

Browse files
committed
Add shell support. Very hacky, sorta-working prototype.
1 parent fcf92ec commit cf4d88d

File tree

1 file changed

+75
-22
lines changed

1 file changed

+75
-22
lines changed

execd.go

Lines changed: 75 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020

2121
"code.google.com/p/go.crypto/ssh"
2222
"github.com/flynn/go-shlex"
23+
"github.com/kr/pty"
2324
)
2425

2526
var host = flag.String("h", "", "host ip to listen on")
@@ -49,7 +50,7 @@ func exitStatus(err error) (exitStatusMsg, error) {
4950
return exitStatusMsg{0}, nil
5051
}
5152

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) {
5354
var wg sync.WaitGroup
5455
wg.Add(2)
5556

@@ -61,6 +62,7 @@ func attachCmd(cmd *exec.Cmd, stdout, stderr io.Writer, stdin io.Reader) (*sync.
6162
go func() {
6263
io.Copy(stdinIn, stdin)
6364
stdinIn.Close()
65+
// FIXME: Do we care that this is not part of the WaitGroup?
6466
}()
6567
}
6668

@@ -85,6 +87,27 @@ func attachCmd(cmd *exec.Cmd, stdout, stderr io.Writer, stdin io.Reader) (*sync.
8587
return &wg, nil
8688
}
8789

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+
88111
func addKey(conf *ssh.ServerConfig, block *pem.Block) (err error) {
89112
var key interface{}
90113
switch block.Type {
@@ -248,18 +271,30 @@ func handleChannel(conn *ssh.ServerConn, newChan ssh.NewChannel, execHandler []s
248271
return
249272
}
250273
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?
251295
for req := range reqs {
252296
switch req.Type {
253297
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-
263298
if req.WantReply {
264299
req.Reply(true, nil)
265300
}
@@ -287,14 +322,6 @@ func handleChannel(conn *ssh.ServerConn, newChan ssh.NewChannel, execHandler []s
287322
cmd.Env = append(cmd.Env, "USER="+conn.Permissions.Extensions["user"])
288323
}
289324
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-
}
298325
done, err := attachCmd(cmd, stdout, stderr, ch)
299326
if assert("attachCmd", err) {
300327
return
@@ -310,12 +337,38 @@ func handleChannel(conn *ssh.ServerConn, newChan ssh.NewChannel, execHandler []s
310337
_, err = ch.SendRequest("exit-status", false, ssh.Marshal(&status))
311338
assert("sendExit", err)
312339
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
316360
}
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?
318367
return
319368
}
369+
370+
if req.WantReply {
371+
req.Reply(true, nil)
372+
}
320373
}
321374
}

0 commit comments

Comments
 (0)