diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..3e9de3d --- /dev/null +++ b/go.mod @@ -0,0 +1,10 @@ +module github.com/vanbroup/ssh + +go 1.12 + +require ( + github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 + github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect + github.com/gliderlabs/ssh v0.2.2 + golang.org/x/crypto v0.0.0-20190909091759-094676da4a83 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..e67efd5 --- /dev/null +++ b/go.sum @@ -0,0 +1,14 @@ +github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA= +github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= +github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ= +github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= +github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0= +github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190909091759-094676da4a83 h1:mgAKeshyNqWKdENOnQsg+8dRTwZFIwFaO3HNl52sweA= +golang.org/x/crypto v0.0.0-20190909091759-094676da4a83/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/server.go b/server.go index 28ed017..fb33a2d 100644 --- a/server.go +++ b/server.go @@ -15,6 +15,10 @@ import ( // and ListenAndServeTLS methods after a call to Shutdown or Close. 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) var DefaultRequestHandlers = map[string]RequestHandler{} @@ -57,6 +61,10 @@ type Server struct { // no handlers are enabled. 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 mu sync.Mutex listeners map[net.Listener]struct{} @@ -91,6 +99,12 @@ func (srv *Server) ensureHandlers() { 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 { diff --git a/session.go b/session.go index 6c77d6c..0211861 100644 --- a/session.go +++ b/session.go @@ -47,6 +47,9 @@ type Session interface { // RawCommand returns the exact command that was provided by the user. 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 // used it will return nil. PublicKey() PublicKey @@ -87,12 +90,13 @@ func DefaultSessionHandler(srv *Server, conn *gossh.ServerConn, newChan gossh.Ne return } sess := &session{ - Channel: ch, - conn: conn, - handler: srv.Handler, - ptyCb: srv.PtyCallback, - sessReqCb: srv.SessionRequestCallback, - ctx: ctx, + Channel: ch, + conn: conn, + handler: srv.Handler, + ptyCb: srv.PtyCallback, + sessReqCb: srv.SessionRequestCallback, + subsystemHandlers: srv.SubsystemHandlers, + ctx: ctx, } sess.handleRequests(reqs) } @@ -100,19 +104,22 @@ func DefaultSessionHandler(srv *Server, conn *gossh.ServerConn, newChan gossh.Ne 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 + conn *gossh.ServerConn + handler Handler + subsystemHandlers map[string]SubsystemHandler + handled bool + exited bool + pty *Pty + winch chan Window + env []string + ptyCb PtyCallback + sessReqCb SessionRequestCallback + rawCmd string + cmd []string + subsystem string + ctx Context + sigCh chan<- Signal + sigBuf []Signal } func (sess *session) Write(p []byte) (n int, err error) { @@ -191,6 +198,10 @@ func (sess *session) Command() []string { return append([]string(nil), cmd...) } +func (sess *session) Subsystem() string { + return sess.subsystem +} + func (sess *session) Pty() (Pty, <-chan Window, bool) { if sess.pty != nil { return *sess.pty, sess.winch, true @@ -239,6 +250,40 @@ func (sess *session) handleRequests(reqs <-chan *gossh.Request) { sess.handler(sess) 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.cmd = nil + 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": if sess.handled { req.Reply(false, nil)