Merge pull request #111 from gliderlabs/subsystem-support

Add subsystem support
This commit is contained in:
Kaleb Elwert 2020-07-17 15:22:14 -07:00 committed by GitHub
commit 76cadaa318
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 77 additions and 19 deletions

@ -15,6 +15,10 @@ import (
// and ListenAndServeTLS methods after a call to Shutdown or Close. // and ListenAndServeTLS methods after a call to Shutdown or Close.
var ErrServerClosed = errors.New("ssh: Server closed") var ErrServerClosed = errors.New("ssh: Server closed")
type SubsystemHandler func(s Session)
var DefaultSubsystemHandlers = map[string]SubsystemHandler{}
type RequestHandler func(ctx Context, srv *Server, req *gossh.Request) (ok bool, payload []byte) type RequestHandler func(ctx Context, srv *Server, req *gossh.Request) (ok bool, payload []byte)
var DefaultRequestHandlers = map[string]RequestHandler{} var DefaultRequestHandlers = map[string]RequestHandler{}
@ -57,6 +61,10 @@ type Server struct {
// no handlers are enabled. // no handlers are enabled.
RequestHandlers map[string]RequestHandler RequestHandlers map[string]RequestHandler
// SubsystemHandlers are handlers which are similar to the usual SSH command
// handlers, but handle named subsystems.
SubsystemHandlers map[string]SubsystemHandler
listenerWg sync.WaitGroup listenerWg sync.WaitGroup
mu sync.RWMutex mu sync.RWMutex
listeners map[net.Listener]struct{} listeners map[net.Listener]struct{}
@ -95,6 +103,12 @@ func (srv *Server) ensureHandlers() {
srv.ChannelHandlers[k] = v srv.ChannelHandlers[k] = v
} }
} }
if srv.SubsystemHandlers == nil {
srv.SubsystemHandlers = map[string]SubsystemHandler{}
for k, v := range DefaultSubsystemHandlers {
srv.SubsystemHandlers[k] = v
}
}
} }
func (srv *Server) config(ctx Context) *gossh.ServerConfig { func (srv *Server) config(ctx Context) *gossh.ServerConfig {

@ -47,6 +47,9 @@ type Session interface {
// RawCommand returns the exact command that was provided by the user. // RawCommand returns the exact command that was provided by the user.
RawCommand() string RawCommand() string
// Subsystem returns the subsystem requested by the user.
Subsystem() string
// PublicKey returns the PublicKey used to authenticate. If a public key was not // PublicKey returns the PublicKey used to authenticate. If a public key was not
// used it will return nil. // used it will return nil.
PublicKey() PublicKey PublicKey() PublicKey
@ -87,12 +90,13 @@ func DefaultSessionHandler(srv *Server, conn *gossh.ServerConn, newChan gossh.Ne
return return
} }
sess := &session{ sess := &session{
Channel: ch, Channel: ch,
conn: conn, conn: conn,
handler: srv.Handler, handler: srv.Handler,
ptyCb: srv.PtyCallback, ptyCb: srv.PtyCallback,
sessReqCb: srv.SessionRequestCallback, sessReqCb: srv.SessionRequestCallback,
ctx: ctx, subsystemHandlers: srv.SubsystemHandlers,
ctx: ctx,
} }
sess.handleRequests(reqs) sess.handleRequests(reqs)
} }
@ -100,19 +104,21 @@ func DefaultSessionHandler(srv *Server, conn *gossh.ServerConn, newChan gossh.Ne
type session struct { type session struct {
sync.Mutex sync.Mutex
gossh.Channel gossh.Channel
conn *gossh.ServerConn conn *gossh.ServerConn
handler Handler handler Handler
handled bool subsystemHandlers map[string]SubsystemHandler
exited bool handled bool
pty *Pty exited bool
winch chan Window pty *Pty
env []string winch chan Window
ptyCb PtyCallback env []string
sessReqCb SessionRequestCallback ptyCb PtyCallback
rawCmd string sessReqCb SessionRequestCallback
ctx Context rawCmd string
sigCh chan<- Signal subsystem string
sigBuf []Signal ctx Context
sigCh chan<- Signal
sigBuf []Signal
} }
func (sess *session) Write(p []byte) (n int, err error) { func (sess *session) Write(p []byte) (n int, err error) {
@ -191,6 +197,10 @@ func (sess *session) Command() []string {
return append([]string(nil), cmd...) return append([]string(nil), cmd...)
} }
func (sess *session) Subsystem() string {
return sess.subsystem
}
func (sess *session) Pty() (Pty, <-chan Window, bool) { func (sess *session) Pty() (Pty, <-chan Window, bool) {
if sess.pty != nil { if sess.pty != nil {
return *sess.pty, sess.winch, true return *sess.pty, sess.winch, true
@ -239,6 +249,40 @@ func (sess *session) handleRequests(reqs <-chan *gossh.Request) {
sess.handler(sess) sess.handler(sess)
sess.Exit(0) sess.Exit(0)
}() }()
case "subsystem":
if sess.handled {
req.Reply(false, nil)
continue
}
var payload = struct{ Value string }{}
gossh.Unmarshal(req.Payload, &payload)
sess.subsystem = 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
}
handler := sess.subsystemHandlers[payload.Value]
if handler == nil {
handler = sess.subsystemHandlers["default"]
}
if handler == nil {
req.Reply(false, nil)
continue
}
sess.handled = true
req.Reply(true, nil)
go func() {
handler(sess)
sess.Exit(0)
}()
case "env": case "env":
if sess.handled { if sess.handled {
req.Reply(false, nil) req.Reply(false, nil)