summaryrefslogtreecommitdiffstats
path: root/vendor/github.com/gliderlabs/ssh/session.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/gliderlabs/ssh/session.go')
-rw-r--r--vendor/github.com/gliderlabs/ssh/session.go308
1 files changed, 308 insertions, 0 deletions
diff --git a/vendor/github.com/gliderlabs/ssh/session.go b/vendor/github.com/gliderlabs/ssh/session.go
new file mode 100644
index 0000000000..6c77d6c8a7
--- /dev/null
+++ b/vendor/github.com/gliderlabs/ssh/session.go
@@ -0,0 +1,308 @@
+package ssh
+
+import (
+ "bytes"
+ "context"
+ "errors"
+ "fmt"
+ "net"
+ "sync"
+
+ "github.com/anmitsu/go-shlex"
+ gossh "golang.org/x/crypto/ssh"
+)
+
+// Session provides access to information about an SSH session and methods
+// to read and write to the SSH channel with an embedded Channel interface from
+// cypto/ssh.
+//
+// When Command() returns an empty slice, the user requested a shell. Otherwise
+// the user is performing an exec with those command arguments.
+//
+// TODO: Signals
+type Session interface {
+ gossh.Channel
+
+ // User returns the username used when establishing the SSH connection.
+ User() string
+
+ // RemoteAddr returns the net.Addr of the client side of the connection.
+ RemoteAddr() net.Addr
+
+ // LocalAddr returns the net.Addr of the server side of the connection.
+ LocalAddr() net.Addr
+
+ // Environ returns a copy of strings representing the environment set by the
+ // user for this session, in the form "key=value".
+ Environ() []string
+
+ // Exit sends an exit status and then closes the session.
+ Exit(code int) error
+
+ // Command returns a shell parsed slice of arguments that were provided by the
+ // user. Shell parsing splits the command string according to POSIX shell rules,
+ // which considers quoting not just whitespace.
+ Command() []string
+
+ // RawCommand returns the exact command that was provided by the user.
+ RawCommand() string
+
+ // PublicKey returns the PublicKey used to authenticate. If a public key was not
+ // used it will return nil.
+ PublicKey() PublicKey
+
+ // Context returns the connection's context. The returned context is always
+ // non-nil and holds the same data as the Context passed into auth
+ // handlers and callbacks.
+ //
+ // The context is canceled when the client's connection closes or I/O
+ // operation fails.
+ Context() context.Context
+
+ // Permissions returns a copy of the Permissions object that was available for
+ // setup in the auth handlers via the Context.
+ Permissions() Permissions
+
+ // Pty returns PTY information, a channel of window size changes, and a boolean
+ // of whether or not a PTY was accepted for this session.
+ Pty() (Pty, <-chan Window, bool)
+
+ // Signals registers a channel to receive signals sent from the client. The
+ // channel must handle signal sends or it will block the SSH request loop.
+ // Registering nil will unregister the channel from signal sends. During the
+ // time no channel is registered signals are buffered up to a reasonable amount.
+ // If there are buffered signals when a channel is registered, they will be
+ // sent in order on the channel immediately after registering.
+ Signals(c chan<- Signal)
+}
+
+// maxSigBufSize is how many signals will be buffered
+// when there is no signal channel specified
+const maxSigBufSize = 128
+
+func DefaultSessionHandler(srv *Server, conn *gossh.ServerConn, newChan gossh.NewChannel, ctx Context) {
+ ch, reqs, err := newChan.Accept()
+ if err != nil {
+ // TODO: trigger event callback
+ return
+ }
+ sess := &session{
+ Channel: ch,
+ conn: conn,
+ handler: srv.Handler,
+ ptyCb: srv.PtyCallback,
+ sessReqCb: srv.SessionRequestCallback,
+ ctx: ctx,
+ }
+ sess.handleRequests(reqs)
+}
+
+type session struct {
+ sync.Mutex
+ gossh.Channel
+ conn *gossh.ServerConn
+ handler Handler
+ handled bool
+ exited bool
+ pty *Pty
+ winch chan Window
+ env []string
+ ptyCb PtyCallback
+ sessReqCb SessionRequestCallback
+ rawCmd string
+ ctx Context
+ sigCh chan<- Signal
+ sigBuf []Signal
+}
+
+func (sess *session) Write(p []byte) (n int, err error) {
+ if sess.pty != nil {
+ m := len(p)
+ // normalize \n to \r\n when pty is accepted.
+ // this is a hardcoded shortcut since we don't support terminal modes.
+ p = bytes.Replace(p, []byte{'\n'}, []byte{'\r', '\n'}, -1)
+ p = bytes.Replace(p, []byte{'\r', '\r', '\n'}, []byte{'\r', '\n'}, -1)
+ n, err = sess.Channel.Write(p)
+ if n > m {
+ n = m
+ }
+ return
+ }
+ return sess.Channel.Write(p)
+}
+
+func (sess *session) PublicKey() PublicKey {
+ sessionkey := sess.ctx.Value(ContextKeyPublicKey)
+ if sessionkey == nil {
+ return nil
+ }
+ return sessionkey.(PublicKey)
+}
+
+func (sess *session) Permissions() Permissions {
+ // use context permissions because its properly
+ // wrapped and easier to dereference
+ perms := sess.ctx.Value(ContextKeyPermissions).(*Permissions)
+ return *perms
+}
+
+func (sess *session) Context() context.Context {
+ return sess.ctx
+}
+
+func (sess *session) Exit(code int) error {
+ sess.Lock()
+ defer sess.Unlock()
+ if sess.exited {
+ return errors.New("Session.Exit called multiple times")
+ }
+ sess.exited = true
+
+ status := struct{ Status uint32 }{uint32(code)}
+ _, err := sess.SendRequest("exit-status", false, gossh.Marshal(&status))
+ if err != nil {
+ return err
+ }
+ return sess.Close()
+}
+
+func (sess *session) User() string {
+ return sess.conn.User()
+}
+
+func (sess *session) RemoteAddr() net.Addr {
+ return sess.conn.RemoteAddr()
+}
+
+func (sess *session) LocalAddr() net.Addr {
+ return sess.conn.LocalAddr()
+}
+
+func (sess *session) Environ() []string {
+ return append([]string(nil), sess.env...)
+}
+
+func (sess *session) RawCommand() string {
+ return sess.rawCmd
+}
+
+func (sess *session) Command() []string {
+ cmd, _ := shlex.Split(sess.rawCmd, true)
+ return append([]string(nil), cmd...)
+}
+
+func (sess *session) Pty() (Pty, <-chan Window, bool) {
+ if sess.pty != nil {
+ return *sess.pty, sess.winch, true
+ }
+ return Pty{}, sess.winch, false
+}
+
+func (sess *session) Signals(c chan<- Signal) {
+ sess.Lock()
+ defer sess.Unlock()
+ sess.sigCh = c
+ if len(sess.sigBuf) > 0 {
+ go func() {
+ for _, sig := range sess.sigBuf {
+ sess.sigCh <- sig
+ }
+ }()
+ }
+}
+
+func (sess *session) handleRequests(reqs <-chan *gossh.Request) {
+ for req := range reqs {
+ switch req.Type {
+ case "shell", "exec":
+ if sess.handled {
+ req.Reply(false, nil)
+ continue
+ }
+
+ var payload = struct{ Value string }{}
+ gossh.Unmarshal(req.Payload, &payload)
+ sess.rawCmd = payload.Value
+
+ // If there's a session policy callback, we need to confirm before
+ // accepting the session.
+ if sess.sessReqCb != nil && !sess.sessReqCb(sess, req.Type) {
+ sess.rawCmd = ""
+ req.Reply(false, nil)
+ continue
+ }
+
+ sess.handled = true
+ req.Reply(true, nil)
+
+ go func() {
+ sess.handler(sess)
+ sess.Exit(0)
+ }()
+ case "env":
+ if sess.handled {
+ req.Reply(false, nil)
+ continue
+ }
+ var kv struct{ Key, Value string }
+ gossh.Unmarshal(req.Payload, &kv)
+ sess.env = append(sess.env, fmt.Sprintf("%s=%s", kv.Key, kv.Value))
+ req.Reply(true, nil)
+ case "signal":
+ var payload struct{ Signal string }
+ gossh.Unmarshal(req.Payload, &payload)
+ sess.Lock()
+ if sess.sigCh != nil {
+ sess.sigCh <- Signal(payload.Signal)
+ } else {
+ if len(sess.sigBuf) < maxSigBufSize {
+ sess.sigBuf = append(sess.sigBuf, Signal(payload.Signal))
+ }
+ }
+ sess.Unlock()
+ case "pty-req":
+ if sess.handled || sess.pty != nil {
+ req.Reply(false, nil)
+ continue
+ }
+ ptyReq, ok := parsePtyRequest(req.Payload)
+ if !ok {
+ req.Reply(false, nil)
+ continue
+ }
+ if sess.ptyCb != nil {
+ ok := sess.ptyCb(sess.ctx, ptyReq)
+ if !ok {
+ req.Reply(false, nil)
+ continue
+ }
+ }
+ sess.pty = &ptyReq
+ sess.winch = make(chan Window, 1)
+ sess.winch <- ptyReq.Window
+ defer func() {
+ // when reqs is closed
+ close(sess.winch)
+ }()
+ req.Reply(ok, nil)
+ case "window-change":
+ if sess.pty == nil {
+ req.Reply(false, nil)
+ continue
+ }
+ win, ok := parseWinchRequest(req.Payload)
+ if ok {
+ sess.pty.Window = win
+ sess.winch <- win
+ }
+ req.Reply(ok, nil)
+ case agentRequestType:
+ // TODO: option/callback to allow agent forwarding
+ SetAgentRequested(sess.ctx)
+ req.Reply(true, nil)
+ default:
+ // TODO: debug log
+ req.Reply(false, nil)
+ }
+ }
+}