diff options
Diffstat (limited to 'vendor/github.com/gliderlabs/ssh/session.go')
-rw-r--r-- | vendor/github.com/gliderlabs/ssh/session.go | 308 |
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) + } + } +} |