Skip to content

Commit 263adc7

Browse files
committed
Merge pull request #6 from shazow/cleanup
Cleanup
2 parents 46e94bd + 74d4b90 commit 263adc7

File tree

3 files changed

+260
-199
lines changed

3 files changed

+260
-199
lines changed

README.md

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,12 @@ Usage: ./execd [options] <auth-handler> <exec-handler>
99
-d=false: debug mode displays handler output
1010
-e=false: pass environment to handlers
1111
-k="": pem file of private keys (read from SSH_PRIVATE_KEYS by default)
12+
-h="": host ip to listen on
1213
-p="22": port to listen on
1314
-s=false: run exec handler via SHELL
1415
```
16+
17+
1518
#### auth-handler $user $key
1619

1720
* `$user` argument is the name of the user being used to attempt the connection
@@ -21,12 +24,47 @@ auth-handler is the path to an executable that's used for authenticating incomin
2124

2225
Although auth-handler is required, you can still achieve no-auth open access by providing `/usr/bin/true` as auth-handler.
2326

27+
2428
#### exec-handler $command...
2529

2630
* `$command...` arguments is the command line that was specified to run by the SSH client
2731

2832
exec-handler is the path to an executable that's used to execute the command provided by the client. The meaning of that is quite flexible. All of the stdout and stderr is returned to the client, including the exit status. If the client provides stdin, that's passed to the exec-handler. Any environment variables provided by the auth-handler output will be available to exec-handler, as well as `$USER` and `$SSH_ORIGINAL_COMMAND` environment variables.
2933

34+
35+
## Examples
36+
37+
**These examples bypass all authentication and allow remote execution, *do not* run this in production.**
38+
39+
Echo server (with accept-all auth):
40+
41+
```
42+
server$ execd $(which true) $(which echo)
43+
client$ ssh $SERVER "hello world"
44+
hello world
45+
```
46+
47+
Echo host's environment to clients (with accept-all auth):
48+
49+
```
50+
server$ execd -e $(which true) $(env)
51+
client$ ssh $SERVER
52+
USER=root
53+
HOME=/root
54+
LANG=en_US.UTF-8
55+
...
56+
```
57+
58+
Bash server (with accept-all auth):
59+
60+
```
61+
server$ execd $(which true) $(which bash)
62+
client$ ssh $SERVER
63+
bash-4.3$ echo "this is a bash instance running on the server"
64+
this is a bash instance running on the server
65+
```
66+
67+
3068
## Credit / History
3169

3270
It started with [gitreceive](https://github.com/progrium/gitreceive), which was then used in [Dokku](https://github.com/progrium/dokku). Then I made a more generalized version of gitreceive, more similar to execd, called [sshcommand](https://github.com/progrium/sshcommand), which eventually replaced gitreceive in Dokku. When I started work on Flynn, the first projects included [gitreceived](https://github.com/flynn/gitreceived) (a standalone daemon version of gitreceive). This was refined by the Flynn community, namely Jonathan Rudenberg.
@@ -39,4 +77,4 @@ This project was made possible thanks to [DigitalOcean](http://digitalocean.com)
3977

4078
## License
4179

42-
BSD
80+
BSD

execd.go

Lines changed: 6 additions & 198 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,17 @@ import (
77
"errors"
88
"flag"
99
"fmt"
10-
"io"
1110
"io/ioutil"
1211
"log"
1312
"net"
1413
"os"
1514
"os/exec"
1615
"path/filepath"
1716
"strings"
18-
"sync"
1917
"syscall"
2018

21-
"code.google.com/p/go.crypto/ssh"
2219
"github.com/flynn/go-shlex"
23-
"github.com/kr/pty"
20+
"golang.org/x/crypto/ssh"
2421
)
2522

2623
var host = flag.String("h", "", "host ip to listen on")
@@ -50,64 +47,6 @@ func exitStatus(err error) (exitStatusMsg, error) {
5047
return exitStatusMsg{0}, nil
5148
}
5249

53-
func attachCmd(cmd *exec.Cmd, stdout io.Writer, stderr io.Writer, stdin io.Reader) (*sync.WaitGroup, error) {
54-
var wg sync.WaitGroup
55-
wg.Add(2)
56-
57-
if stdin != nil {
58-
stdinIn, err := cmd.StdinPipe()
59-
if err != nil {
60-
return nil, err
61-
}
62-
go func() {
63-
io.Copy(stdinIn, stdin)
64-
stdinIn.Close()
65-
// FIXME: Do we care that this is not part of the WaitGroup?
66-
}()
67-
}
68-
69-
stdoutOut, err := cmd.StdoutPipe()
70-
if err != nil {
71-
return nil, err
72-
}
73-
go func() {
74-
io.Copy(stdout, stdoutOut)
75-
wg.Done()
76-
}()
77-
78-
stderrOut, err := cmd.StderrPipe()
79-
if err != nil {
80-
return nil, err
81-
}
82-
go func() {
83-
io.Copy(stderr, stderrOut)
84-
wg.Done()
85-
}()
86-
87-
return &wg, nil
88-
}
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-
11150
func addKey(conf *ssh.ServerConfig, block *pem.Block) (err error) {
11251
var key interface{}
11352
switch block.Type {
@@ -150,16 +89,13 @@ func parseKeys(conf *ssh.ServerConfig, pemData []byte) error {
15089
}
15190

15291
func handleAuth(handler []string, conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
92+
var output bytes.Buffer
93+
15394
keydata := string(bytes.TrimSpace(ssh.MarshalAuthorizedKey(key)))
15495
cmd := exec.Command(handler[0], append(handler[1:], conn.User(), keydata)...)
155-
var output bytes.Buffer
156-
done, err := attachCmd(cmd, &output, &output, nil)
157-
if err != nil {
158-
return nil, err
159-
}
160-
err = cmd.Run()
161-
done.Wait()
162-
status, err := exitStatus(err)
96+
cmd.Stdout = &output
97+
cmd.Stderr = &output
98+
status, err := exitStatus(cmd.Run())
16399
if err != nil {
164100
return nil, err
165101
}
@@ -263,131 +199,3 @@ func handleConn(conn net.Conn, conf *ssh.ServerConfig, execHandler []string) {
263199
go handleChannel(sshConn, ch, execHandler)
264200
}
265201
}
266-
267-
func handleChannel(conn *ssh.ServerConn, newChan ssh.NewChannel, execHandler []string) {
268-
ch, reqs, err := newChan.Accept()
269-
if err != nil {
270-
log.Println("newChan.Accept failed:", err)
271-
return
272-
}
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-
294-
for req := range reqs {
295-
switch req.Type {
296-
case "exec":
297-
defer ch.Close()
298-
299-
if req.WantReply {
300-
req.Reply(true, nil)
301-
}
302-
303-
cmdline := string(req.Payload[4:])
304-
var cmd *exec.Cmd
305-
if *shell {
306-
shellcmd := flag.Arg(1) + " " + cmdline
307-
cmd = exec.Command(os.Getenv("SHELL"), "-c", shellcmd)
308-
} else {
309-
cmdargs, err := shlex.Split(cmdline)
310-
if assert("shlex.Split", err) {
311-
return
312-
}
313-
cmd = exec.Command(execHandler[0], append(execHandler[1:], cmdargs...)...)
314-
}
315-
if *env {
316-
cmd.Env = os.Environ()
317-
} else {
318-
cmd.Env = []string{}
319-
}
320-
if conn.Permissions != nil {
321-
// Using Permissions.Extensions as a way to get state from PublicKeyCallback
322-
if conn.Permissions.Extensions["environ"] != "" {
323-
cmd.Env = append(cmd.Env, strings.Split(conn.Permissions.Extensions["environ"], "\n")...)
324-
}
325-
cmd.Env = append(cmd.Env, "USER="+conn.Permissions.Extensions["user"])
326-
}
327-
cmd.Env = append(cmd.Env, "SSH_ORIGINAL_COMMAND="+cmdline)
328-
done, err := attachCmd(cmd, stdout, stderr, ch)
329-
if assert("attachCmd", err) {
330-
return
331-
}
332-
if assert("cmd.Start", cmd.Start()) {
333-
return
334-
}
335-
done.Wait()
336-
status, err := exitStatus(cmd.Wait())
337-
if assert("exitStatus", err) {
338-
return
339-
}
340-
_, err = ch.SendRequest("exit-status", false, ssh.Marshal(&status))
341-
assert("sendExit", err)
342-
return
343-
case "pty-req":
344-
width, height, okSize := parsePtyRequest(req.Payload)
345-
346-
var cmd *exec.Cmd
347-
if *shell {
348-
cmd = exec.Command(os.Getenv("SHELL"))
349-
} else {
350-
cmd = exec.Command(execHandler[0], execHandler[1:]...)
351-
}
352-
if *env {
353-
cmd.Env = os.Environ()
354-
} else {
355-
cmd.Env = []string{}
356-
}
357-
if conn.Permissions != nil {
358-
// Using Permissions.Extensions as a way to get state from PublicKeyCallback
359-
if conn.Permissions.Extensions["environ"] != "" {
360-
cmd.Env = append(cmd.Env, strings.Split(conn.Permissions.Extensions["environ"], "\n")...)
361-
}
362-
cmd.Env = append(cmd.Env, "USER="+conn.Permissions.Extensions["user"])
363-
}
364-
ptyShell, _, err := attachShell(cmd, stdout, ch)
365-
if assert("attachShell", err) {
366-
ch.Close()
367-
return
368-
}
369-
if okSize {
370-
setWinsize(ptyShell.Fd(), width, height)
371-
req.Reply(true, nil)
372-
}
373-
374-
go func() {
375-
status, err := exitStatus(cmd.Wait())
376-
if !assert("exitStatus", err) {
377-
_, err := ch.SendRequest("exit-status", false, ssh.Marshal(&status))
378-
assert("sendExit", err)
379-
}
380-
ch.Close()
381-
}()
382-
case "window-change":
383-
width, height, okSize := parsePtyRequest(req.Payload)
384-
if okSize {
385-
setWinsize(ptyShell.Fd(), width, height)
386-
}
387-
}
388-
389-
if req.WantReply {
390-
req.Reply(true, nil)
391-
}
392-
}
393-
}

0 commit comments

Comments
 (0)