Compare commits

..

2 Commits

Author SHA1 Message Date
Kaleb Elwert
4703ad4dc1 Expose the gossh.ServerConfig rather than specific values 2018-11-02 17:22:07 -07:00
Kaleb Elwert
cd0f9291c6 Directly expose the SSH server KEXT, MAC and Cipher algorithms 2018-11-02 11:42:13 -07:00
30 changed files with 158 additions and 686 deletions

2
.gitignore vendored

@ -1,2 +0,0 @@
.idea/
vendor/

@ -93,4 +93,4 @@ Become a sponsor and get your logo on our README on Github with a link to your s
## License ## License
[BSD](LICENSE) BSD

@ -6,17 +6,17 @@ import (
"io" "io"
"log" "log"
"git.tcp.direct/bfu/glider-ssh"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/container"
"github.com/docker/docker/client" "github.com/docker/docker/client"
"github.com/docker/docker/pkg/stdcopy" "github.com/docker/docker/pkg/stdcopy"
"github.com/gliderlabs/ssh"
) )
func main() { func main() {
ssh.Handle(func(sess ssh.Session) { ssh.Handle(func(sess ssh.Session) {
_, _, isTty := sess.Pty() _, _, isTty := sess.Pty()
var cfg = &container.Config{ cfg := &container.Config{
Image: sess.User(), Image: sess.User(),
Cmd: sess.Command(), Cmd: sess.Command(),
Env: sess.Environ(), Env: sess.Environ(),

@ -5,7 +5,7 @@ import (
"log" "log"
"os/exec" "os/exec"
"git.tcp.direct/bfu/glider-ssh" "github.com/gliderlabs/ssh"
) )
func main() { func main() {

@ -9,8 +9,8 @@ import (
"syscall" "syscall"
"unsafe" "unsafe"
"git.tcp.direct/bfu/glider-ssh" "github.com/gliderlabs/ssh"
"github.com/creack/pty" "github.com/kr/pty"
) )
func setWinsize(f *os.File, w, h int) { func setWinsize(f *os.File, w, h int) {
@ -37,7 +37,6 @@ func main() {
io.Copy(f, s) // stdin io.Copy(f, s) // stdin
}() }()
io.Copy(s, f) // stdout io.Copy(s, f) // stdout
cmd.Wait()
} else { } else {
io.WriteString(s, "No PTY requested.\n") io.WriteString(s, "No PTY requested.\n")
s.Exit(1) s.Exit(1)

@ -5,7 +5,7 @@ import (
"io" "io"
"log" "log"
"git.tcp.direct/bfu/glider-ssh" "github.com/gliderlabs/ssh"
gossh "golang.org/x/crypto/ssh" gossh "golang.org/x/crypto/ssh"
) )

@ -1,37 +0,0 @@
package main
import (
"io"
"log"
"github.com/gliderlabs/ssh"
)
func main() {
log.Println("starting ssh server on port 2222...")
forwardHandler := &ssh.ForwardedTCPHandler{}
server := ssh.Server{
LocalPortForwardingCallback: ssh.LocalPortForwardingCallback(func(ctx ssh.Context, dhost string, dport uint32) bool {
log.Println("Accepted forward", dhost, dport)
return true
}),
Addr: ":2222",
Handler: ssh.Handler(func(s ssh.Session) {
io.WriteString(s, "Remote forwarding available...\n")
select {}
}),
ReversePortForwardingCallback: ssh.ReversePortForwardingCallback(func(ctx ssh.Context, host string, port uint32) bool {
log.Println("attempt to bind", host, port, "granted")
return true
}),
RequestHandlers: map[string]ssh.RequestHandler{
"tcpip-forward": forwardHandler.HandleSSHRequest,
"cancel-tcpip-forward": forwardHandler.HandleSSHRequest,
},
}
log.Fatal(server.ListenAndServe())
}

@ -5,7 +5,7 @@ import (
"io" "io"
"log" "log"
"git.tcp.direct/bfu/glider-ssh" "github.com/gliderlabs/ssh"
) )
func main() { func main() {

@ -4,7 +4,7 @@ import (
"log" "log"
"time" "time"
"git.tcp.direct/bfu/glider-ssh" "github.com/gliderlabs/ssh"
) )
var ( var (

@ -1,4 +1,4 @@
package glider_ssh package ssh
import ( import (
"io" "io"

26
circle.yml Normal file

@ -0,0 +1,26 @@
version: 2
jobs:
build-go-latest:
docker:
- image: golang:latest
working_directory: /go/src/github.com/gliderlabs/ssh
steps:
- checkout
- run: go get
- run: go test -v -race
build-go-1.9:
docker:
- image: golang:1.9
working_directory: /go/src/github.com/gliderlabs/ssh
steps:
- checkout
- run: go get
- run: go test -v -race
workflows:
version: 2
build:
jobs:
- build-go-latest
- build-go-1.9

@ -1,4 +1,4 @@
package glider_ssh package ssh
import ( import (
"context" "context"
@ -44,7 +44,7 @@ func (c *serverConn) updateDeadline() {
switch { switch {
case c.idleTimeout > 0: case c.idleTimeout > 0:
idleDeadline := time.Now().Add(c.idleTimeout) idleDeadline := time.Now().Add(c.idleTimeout)
if idleDeadline.Unix() < c.maxDeadline.Unix() || c.maxDeadline.IsZero() { if idleDeadline.Unix() < c.maxDeadline.Unix() {
c.Conn.SetDeadline(idleDeadline) c.Conn.SetDeadline(idleDeadline)
return return
} }

@ -1,10 +1,9 @@
package glider_ssh package ssh
import ( import (
"context" "context"
"encoding/hex" "encoding/hex"
"net" "net"
"sync"
gossh "golang.org/x/crypto/ssh" gossh "golang.org/x/crypto/ssh"
) )
@ -49,7 +48,7 @@ var (
ContextKeyServer = &contextKey{"ssh-server"} ContextKeyServer = &contextKey{"ssh-server"}
// ContextKeyConn is a context key for use with Contexts in this package. // ContextKeyConn is a context key for use with Contexts in this package.
// The associated value will be of type gossh.ServerConn. // The associated value will be of type gossh.Conn.
ContextKeyConn = &contextKey{"ssh-conn"} ContextKeyConn = &contextKey{"ssh-conn"}
// ContextKeyPublicKey is a context key for use with Contexts in this package. // ContextKeyPublicKey is a context key for use with Contexts in this package.
@ -60,11 +59,9 @@ var (
// Context is a package specific context interface. It exposes connection // Context is a package specific context interface. It exposes connection
// metadata and allows new values to be easily written to it. It's used in // metadata and allows new values to be easily written to it. It's used in
// authentication handlers and callbacks, and its underlying context.Context is // authentication handlers and callbacks, and its underlying context.Context is
// exposed on Session in the session Handler. A connection-scoped lock is also // exposed on Session in the session Handler.
// embedded in the context to make it easier to limit operations per-connection.
type Context interface { type Context interface {
context.Context context.Context
sync.Locker
// User returns the username used when establishing the SSH connection. // User returns the username used when establishing the SSH connection.
User() string User() string
@ -93,12 +90,11 @@ type Context interface {
type sshContext struct { type sshContext struct {
context.Context context.Context
*sync.Mutex
} }
func newContext(srv *Server) (*sshContext, context.CancelFunc) { func newContext(srv *Server) (*sshContext, context.CancelFunc) {
innerCtx, cancel := context.WithCancel(context.Background()) innerCtx, cancel := context.WithCancel(context.Background())
ctx := &sshContext{innerCtx, &sync.Mutex{}} ctx := &sshContext{innerCtx}
ctx.SetValue(ContextKeyServer, srv) ctx.SetValue(ContextKeyServer, srv)
perms := &Permissions{&gossh.Permissions{}} perms := &Permissions{&gossh.Permissions{}}
ctx.SetValue(ContextKeyPermissions, perms) ctx.SetValue(ContextKeyPermissions, perms)
@ -140,10 +136,7 @@ func (ctx *sshContext) ServerVersion() string {
} }
func (ctx *sshContext) RemoteAddr() net.Addr { func (ctx *sshContext) RemoteAddr() net.Addr {
if addr, ok := ctx.Value(ContextKeyRemoteAddr).(net.Addr); ok { return ctx.Value(ContextKeyRemoteAddr).(net.Addr)
return addr
}
return nil
} }
func (ctx *sshContext) LocalAddr() net.Addr { func (ctx *sshContext) LocalAddr() net.Addr {

@ -1,4 +1,4 @@
package glider_ssh package ssh
import "testing" import "testing"

4
doc.go

@ -1,4 +1,5 @@
/* /*
Package ssh wraps the crypto/ssh package with a higher-level API for building Package ssh wraps the crypto/ssh package with a higher-level API for building
SSH servers. The goal of the API was to make it as simple as using net/http, so SSH servers. The goal of the API was to make it as simple as using net/http, so
the API is very similar. the API is very similar.
@ -41,5 +42,6 @@ exposed to you via the Session interface.
The one big feature missing from the Session abstraction is signals. This was The one big feature missing from the Session abstraction is signals. This was
started, but not completed. Pull Requests welcome! started, but not completed. Pull Requests welcome!
*/ */
package glider_ssh package ssh

@ -1,10 +1,10 @@
package glider_ssh_test package ssh_test
import ( import (
"io" "io"
"io/ioutil" "io/ioutil"
"git.tcp.direct/bfu/glider-ssh" "github.com/gliderlabs/ssh"
) )
func ExampleListenAndServe() { func ExampleListenAndServe() {

8
go.mod

@ -1,8 +0,0 @@
module git.tcp.direct/bfu/glider-ssh
go 1.13
require (
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be
golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871
)

13
go.sum

@ -1,13 +0,0 @@
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 h1:/pEO3GD/ABYAjuakUS6xSEmmlyVS4kxBNkeA9tLJiTI=
golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

@ -1,4 +1,4 @@
package glider_ssh package ssh
import ( import (
"io/ioutil" "io/ioutil"
@ -42,13 +42,6 @@ func HostKeyFile(filepath string) Option {
} }
} }
func KeyboardInteractiveAuth(fn KeyboardInteractiveHandler) Option {
return func(srv *Server) error {
srv.KeyboardInteractiveHandler = fn
return nil
}
}
// HostKeyPEM returns a functional option that adds HostSigners to the server // HostKeyPEM returns a functional option that adds HostSigners to the server
// from a PEM file as bytes. // from a PEM file as bytes.
func HostKeyPEM(bytes []byte) Option { func HostKeyPEM(bytes []byte) Option {

@ -1,4 +1,4 @@
package glider_ssh package ssh
import ( import (
"net" "net"
@ -95,7 +95,7 @@ func TestConnWrapping(t *testing.T) {
HostKeyCallback: gossh.InsecureIgnoreHostKey(), HostKeyCallback: gossh.InsecureIgnoreHostKey(),
}, PasswordAuth(func(ctx Context, password string) bool { }, PasswordAuth(func(ctx Context, password string) bool {
return true return true
}), WrapConn(func(ctx Context, conn net.Conn) net.Conn { }), WrapConn(func(conn net.Conn) net.Conn {
wrapped = &wrappedConn{conn, 0} wrapped = &wrappedConn{conn, 0}
return wrapped return wrapped
})) }))

181
server.go

@ -1,4 +1,4 @@
package glider_ssh package ssh
import ( import (
"context" "context"
@ -15,20 +15,6 @@ 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)
var DefaultRequestHandlers = map[string]RequestHandler{}
type ChannelHandler func(srv *Server, conn *gossh.ServerConn, newChan gossh.NewChannel, ctx Context)
var DefaultChannelHandlers = map[string]ChannelHandler{
"session": DefaultSessionHandler,
}
// Server defines parameters for running an SSH server. The zero value for // Server defines parameters for running an SSH server. The zero value for
// Server is a valid configuration. When both PasswordHandler and // Server is a valid configuration. When both PasswordHandler and
// PublicKeyHandler are nil, no client authentication is performed. // PublicKeyHandler are nil, no client authentication is performed.
@ -38,47 +24,35 @@ type Server struct {
HostSigners []Signer // private keys for the host key, must have at least one HostSigners []Signer // private keys for the host key, must have at least one
Version string // server version to be sent before the initial handshake Version string // server version to be sent before the initial handshake
KeyboardInteractiveHandler KeyboardInteractiveHandler // keyboard-interactive authentication handler PasswordHandler PasswordHandler // password authentication handler
PasswordHandler PasswordHandler // password authentication handler PublicKeyHandler PublicKeyHandler // public key authentication handler
PublicKeyHandler PublicKeyHandler // public key authentication handler PtyCallback PtyCallback // callback for allowing PTY sessions, allows all if nil
PtyCallback PtyCallback // callback for allowing PTY sessions, allows all if nil ConnCallback ConnCallback // optional callback for wrapping net.Conn before handling
ConnCallback ConnCallback // optional callback for wrapping net.Conn before handling LocalPortForwardingCallback LocalPortForwardingCallback // callback for allowing local port forwarding, denies all if nil
LocalPortForwardingCallback LocalPortForwardingCallback // callback for allowing local port forwarding, denies all if nil
ReversePortForwardingCallback ReversePortForwardingCallback // callback for allowing reverse port forwarding, denies all if nil
ServerConfigCallback ServerConfigCallback // callback for configuring detailed SSH options
SessionRequestCallback SessionRequestCallback // callback for allowing or denying SSH sessions
ConnectionFailedCallback ConnectionFailedCallback // callback to report connection failures
IdleTimeout time.Duration // connection timeout when no activity, none if empty IdleTimeout time.Duration // connection timeout when no activity, none if empty
MaxTimeout time.Duration // absolute connection timeout, none if empty MaxTimeout time.Duration // absolute connection timeout, none if empty
// ChannelHandlers allow overriding the built-in session handlers or provide // Internal x/crypto/ssh config. Note that a number of values in this struct
// extensions to the protocol, such as tcpip forwarding. By default only the // are overwritten every time a connection starts, so only use this if you
// "session" handler is enabled. // know what you're doing and absolutely need to change the internal config
ChannelHandlers map[string]ChannelHandler // values.
BaseConfig *gossh.ServerConfig
// RequestHandlers allow overriding the server-level request handlers or channelHandlers map[string]channelHandler
// provide extensions to the protocol, such as tcpip forwarding. By default
// 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 listenerWg sync.WaitGroup
mu sync.RWMutex mu sync.Mutex
listeners map[net.Listener]struct{} listeners map[net.Listener]struct{}
conns map[*gossh.ServerConn]struct{} conns map[*gossh.ServerConn]struct{}
connWg sync.WaitGroup connWg sync.WaitGroup
doneChan chan struct{} doneChan chan struct{}
} }
func (srv *Server) ensureHostSigner() error { // internal for now
srv.mu.Lock() type channelHandler func(srv *Server, conn *gossh.ServerConn, newChan gossh.NewChannel, ctx Context)
defer srv.mu.Unlock()
func (srv *Server) ensureHostSigner() error {
if len(srv.HostSigners) == 0 { if len(srv.HostSigners) == 0 {
signer, err := generateSigner() signer, err := generateSigner()
if err != nil { if err != nil {
@ -89,44 +63,18 @@ func (srv *Server) ensureHostSigner() error {
return nil return nil
} }
func (srv *Server) ensureHandlers() {
srv.mu.Lock()
defer srv.mu.Unlock()
if srv.RequestHandlers == nil {
srv.RequestHandlers = map[string]RequestHandler{}
for k, v := range DefaultRequestHandlers {
srv.RequestHandlers[k] = v
}
}
if srv.ChannelHandlers == nil {
srv.ChannelHandlers = map[string]ChannelHandler{}
for k, v := range DefaultChannelHandlers {
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 {
srv.mu.RLock() // Use the provided base config if set, otherwise default to an empty
defer srv.mu.RUnlock() // config.
config := srv.BaseConfig
var config *gossh.ServerConfig if config == nil {
if srv.ServerConfigCallback == nil {
config = &gossh.ServerConfig{} config = &gossh.ServerConfig{}
} else {
config = srv.ServerConfigCallback(ctx)
} }
for _, signer := range srv.HostSigners { for _, signer := range srv.HostSigners {
config.AddHostKey(signer) config.AddHostKey(signer)
} }
if srv.PasswordHandler == nil && srv.PublicKeyHandler == nil && srv.KeyboardInteractiveHandler == nil { if srv.PasswordHandler == nil && srv.PublicKeyHandler == nil {
config.NoClientAuth = true config.NoClientAuth = true
} }
if srv.Version != "" { if srv.Version != "" {
@ -151,23 +99,12 @@ func (srv *Server) config(ctx Context) *gossh.ServerConfig {
return ctx.Permissions().Permissions, nil return ctx.Permissions().Permissions, nil
} }
} }
if srv.KeyboardInteractiveHandler != nil {
config.KeyboardInteractiveCallback = func(conn gossh.ConnMetadata, challenger gossh.KeyboardInteractiveChallenge) (*gossh.Permissions, error) {
applyConnMetadata(ctx, conn)
if ok := srv.KeyboardInteractiveHandler(ctx, challenger); !ok {
return ctx.Permissions().Permissions, fmt.Errorf("permission denied")
}
return ctx.Permissions().Permissions, nil
}
}
return config return config
} }
// Handle sets the Handler for the server. // Handle sets the Handler for the server.
func (srv *Server) Handle(fn Handler) { func (srv *Server) Handle(fn Handler) {
srv.mu.Lock()
defer srv.mu.Unlock()
srv.Handler = fn srv.Handler = fn
} }
@ -179,7 +116,6 @@ func (srv *Server) Handle(fn Handler) {
func (srv *Server) Close() error { func (srv *Server) Close() error {
srv.mu.Lock() srv.mu.Lock()
defer srv.mu.Unlock() defer srv.mu.Unlock()
srv.closeDoneChanLocked() srv.closeDoneChanLocked()
err := srv.closeListenersLocked() err := srv.closeListenersLocked()
for c := range srv.conns { for c := range srv.conns {
@ -221,7 +157,6 @@ func (srv *Server) Shutdown(ctx context.Context) error {
// //
// Serve always returns a non-nil error. // Serve always returns a non-nil error.
func (srv *Server) Serve(l net.Listener) error { func (srv *Server) Serve(l net.Listener) error {
srv.ensureHandlers()
defer l.Close() defer l.Close()
if err := srv.ensureHostSigner(); err != nil { if err := srv.ensureHostSigner(); err != nil {
return err return err
@ -229,6 +164,12 @@ func (srv *Server) Serve(l net.Listener) error {
if srv.Handler == nil { if srv.Handler == nil {
srv.Handler = DefaultHandler srv.Handler = DefaultHandler
} }
if srv.channelHandlers == nil {
srv.channelHandlers = map[string]channelHandler{
"session": sessionHandler,
"direct-tcpip": directTcpipHandler,
}
}
var tempDelay time.Duration var tempDelay time.Duration
srv.trackListener(l, true) srv.trackListener(l, true)
@ -255,20 +196,20 @@ func (srv *Server) Serve(l net.Listener) error {
} }
return e return e
} }
go srv.HandleConn(conn) go srv.handleConn(conn)
} }
} }
func (srv *Server) HandleConn(newConn net.Conn) { func (srv *Server) handleConn(newConn net.Conn) {
ctx, cancel := newContext(srv)
if srv.ConnCallback != nil { if srv.ConnCallback != nil {
cbConn := srv.ConnCallback(ctx, newConn) cbConn := srv.ConnCallback(newConn)
if cbConn == nil { if cbConn == nil {
newConn.Close() newConn.Close()
return return
} }
newConn = cbConn newConn = cbConn
} }
ctx, cancel := newContext(srv)
conn := &serverConn{ conn := &serverConn{
Conn: newConn, Conn: newConn,
idleTimeout: srv.IdleTimeout, idleTimeout: srv.IdleTimeout,
@ -280,9 +221,7 @@ func (srv *Server) HandleConn(newConn net.Conn) {
defer conn.Close() defer conn.Close()
sshConn, chans, reqs, err := gossh.NewServerConn(conn, srv.config(ctx)) sshConn, chans, reqs, err := gossh.NewServerConn(conn, srv.config(ctx))
if err != nil { if err != nil {
if srv.ConnectionFailedCallback != nil { // TODO: trigger event callback
srv.ConnectionFailedCallback(conn, err)
}
return return
} }
@ -291,14 +230,10 @@ func (srv *Server) HandleConn(newConn net.Conn) {
ctx.SetValue(ContextKeyConn, sshConn) ctx.SetValue(ContextKeyConn, sshConn)
applyConnMetadata(ctx, sshConn) applyConnMetadata(ctx, sshConn)
//go gossh.DiscardRequests(reqs) go gossh.DiscardRequests(reqs)
go srv.handleRequests(ctx, reqs)
for ch := range chans { for ch := range chans {
handler := srv.ChannelHandlers[ch.ChannelType()] handler, found := srv.channelHandlers[ch.ChannelType()]
if handler == nil { if !found {
handler = srv.ChannelHandlers["default"]
}
if handler == nil {
ch.Reject(gossh.UnknownChannelType, "unsupported channel type") ch.Reject(gossh.UnknownChannelType, "unsupported channel type")
continue continue
} }
@ -306,23 +241,6 @@ func (srv *Server) HandleConn(newConn net.Conn) {
} }
} }
func (srv *Server) handleRequests(ctx Context, in <-chan *gossh.Request) {
for req := range in {
handler := srv.RequestHandlers[req.Type]
if handler == nil {
handler = srv.RequestHandlers["default"]
}
if handler == nil {
req.Reply(false, nil)
continue
}
/*reqCtx, cancel := context.WithCancel(ctx)
defer cancel() */
ret, payload := handler(ctx, srv, req)
req.Reply(ret, payload)
}
}
// ListenAndServe listens on the TCP network address srv.Addr and then calls // ListenAndServe listens on the TCP network address srv.Addr and then calls
// Serve to handle incoming connections. If srv.Addr is blank, ":22" is used. // Serve to handle incoming connections. If srv.Addr is blank, ":22" is used.
// ListenAndServe always returns a non-nil error. // ListenAndServe always returns a non-nil error.
@ -342,42 +260,19 @@ func (srv *Server) ListenAndServe() error {
// with the same algorithm, it is overwritten. Each server config must have at // with the same algorithm, it is overwritten. Each server config must have at
// least one host key. // least one host key.
func (srv *Server) AddHostKey(key Signer) { func (srv *Server) AddHostKey(key Signer) {
srv.mu.Lock()
defer srv.mu.Unlock()
// these are later added via AddHostKey on ServerConfig, which performs the // these are later added via AddHostKey on ServerConfig, which performs the
// check for one of every algorithm. // check for one of every algorithm.
// This check is based on the AddHostKey method from the x/crypto/ssh
// library. This allows us to only keep one active key for each type on a
// server at once. So, if you're dynamically updating keys at runtime, this
// list will not keep growing.
for i, k := range srv.HostSigners {
if k.PublicKey().Type() == key.PublicKey().Type() {
srv.HostSigners[i] = key
return
}
}
srv.HostSigners = append(srv.HostSigners, key) srv.HostSigners = append(srv.HostSigners, key)
} }
// SetOption runs a functional option against the server. // SetOption runs a functional option against the server.
func (srv *Server) SetOption(option Option) error { func (srv *Server) SetOption(option Option) error {
// NOTE: there is a potential race here for any option that doesn't call an
// internal method. We can't actually lock here because if something calls
// (as an example) AddHostKey, it will deadlock.
//srv.mu.Lock()
//defer srv.mu.Unlock()
return option(srv) return option(srv)
} }
func (srv *Server) getDoneChan() <-chan struct{} { func (srv *Server) getDoneChan() <-chan struct{} {
srv.mu.Lock() srv.mu.Lock()
defer srv.mu.Unlock() defer srv.mu.Unlock()
return srv.getDoneChanLocked() return srv.getDoneChanLocked()
} }
@ -414,7 +309,6 @@ func (srv *Server) closeListenersLocked() error {
func (srv *Server) trackListener(ln net.Listener, add bool) { func (srv *Server) trackListener(ln net.Listener, add bool) {
srv.mu.Lock() srv.mu.Lock()
defer srv.mu.Unlock() defer srv.mu.Unlock()
if srv.listeners == nil { if srv.listeners == nil {
srv.listeners = make(map[net.Listener]struct{}) srv.listeners = make(map[net.Listener]struct{})
} }
@ -435,7 +329,6 @@ func (srv *Server) trackListener(ln net.Listener, add bool) {
func (srv *Server) trackConn(c *gossh.ServerConn, add bool) { func (srv *Server) trackConn(c *gossh.ServerConn, add bool) {
srv.mu.Lock() srv.mu.Lock()
defer srv.mu.Unlock() defer srv.mu.Unlock()
if srv.conns == nil { if srv.conns == nil {
srv.conns = make(map[*gossh.ServerConn]struct{}) srv.conns = make(map[*gossh.ServerConn]struct{})
} }

@ -1,4 +1,4 @@
package glider_ssh package ssh
import ( import (
"bytes" "bytes"
@ -8,26 +8,6 @@ import (
"time" "time"
) )
func TestAddHostKey(t *testing.T) {
s := Server{}
signer, err := generateSigner()
if err != nil {
t.Fatal(err)
}
s.AddHostKey(signer)
if len(s.HostSigners) != 1 {
t.Fatal("Key was not properly added")
}
signer, err = generateSigner()
if err != nil {
t.Fatal(err)
}
s.AddHostKey(signer)
if len(s.HostSigners) != 1 {
t.Fatal("Key was not properly replaced")
}
}
func TestServerShutdown(t *testing.T) { func TestServerShutdown(t *testing.T) {
l := newLocalListener() l := newLocalListener()
testBytes := []byte("Hello world\n") testBytes := []byte("Hello world\n")

@ -1,4 +1,4 @@
package glider_ssh package ssh
import ( import (
"bytes" "bytes"
@ -14,7 +14,7 @@ import (
// Session provides access to information about an SSH session and methods // 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 // to read and write to the SSH channel with an embedded Channel interface from
// crypto/ssh. // cypto/ssh.
// //
// When Command() returns an empty slice, the user requested a shell. Otherwise // When Command() returns an empty slice, the user requested a shell. Otherwise
// the user is performing an exec with those command arguments. // the user is performing an exec with those command arguments.
@ -44,12 +44,6 @@ type Session interface {
// which considers quoting not just whitespace. // which considers quoting not just whitespace.
Command() []string Command() []string
// 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 // 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
@ -77,32 +71,24 @@ type Session interface {
// If there are buffered signals when a channel is registered, they will be // If there are buffered signals when a channel is registered, they will be
// sent in order on the channel immediately after registering. // sent in order on the channel immediately after registering.
Signals(c chan<- Signal) Signals(c chan<- Signal)
// Break regisers a channel to receive notifications of break requests sent
// from the client. The channel must handle break requests, or it will block
// the request handling loop. Registering nil will unregister the channel.
// During the time that no channel is registered, breaks are ignored.
Break(c chan<- bool)
} }
// maxSigBufSize is how many signals will be buffered // maxSigBufSize is how many signals will be buffered
// when there is no signal channel specified // when there is no signal channel specified
const maxSigBufSize = 128 const maxSigBufSize = 128
func DefaultSessionHandler(srv *Server, conn *gossh.ServerConn, newChan gossh.NewChannel, ctx Context) { func sessionHandler(srv *Server, conn *gossh.ServerConn, newChan gossh.NewChannel, ctx Context) {
ch, reqs, err := newChan.Accept() ch, reqs, err := newChan.Accept()
if err != nil { if err != nil {
// TODO: trigger event callback // TODO: trigger event callback
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, ctx: ctx,
subsystemHandlers: srv.SubsystemHandlers,
ctx: ctx,
} }
sess.handleRequests(reqs) sess.handleRequests(reqs)
} }
@ -110,22 +96,18 @@ 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
subsystemHandlers map[string]SubsystemHandler handled bool
handled bool exited bool
exited bool pty *Pty
pty *Pty winch chan Window
winch chan Window env []string
env []string ptyCb PtyCallback
ptyCb PtyCallback cmd []string
sessReqCb SessionRequestCallback ctx Context
rawCmd string sigCh chan<- Signal
subsystem string sigBuf []Signal
ctx Context
sigCh chan<- Signal
sigBuf []Signal
breakCh chan<- bool
} }
func (sess *session) Write(p []byte) (n int, err error) { func (sess *session) Write(p []byte) (n int, err error) {
@ -195,17 +177,8 @@ func (sess *session) Environ() []string {
return append([]string(nil), sess.env...) return append([]string(nil), sess.env...)
} }
func (sess *session) RawCommand() string {
return sess.rawCmd
}
func (sess *session) Command() []string { func (sess *session) Command() []string {
cmd, _ := shlex.Split(sess.rawCmd, true) return append([]string(nil), sess.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) {
@ -228,12 +201,6 @@ func (sess *session) Signals(c chan<- Signal) {
} }
} }
func (sess *session) Break(c chan<- bool) {
sess.Lock()
defer sess.Unlock()
sess.breakCh = c
}
func (sess *session) handleRequests(reqs <-chan *gossh.Request) { func (sess *session) handleRequests(reqs <-chan *gossh.Request) {
for req := range reqs { for req := range reqs {
switch req.Type { switch req.Type {
@ -242,60 +209,16 @@ func (sess *session) handleRequests(reqs <-chan *gossh.Request) {
req.Reply(false, nil) req.Reply(false, nil)
continue 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 sess.handled = true
req.Reply(true, nil) req.Reply(true, nil)
var payload = struct{ Value string }{}
gossh.Unmarshal(req.Payload, &payload)
sess.cmd, _ = shlex.Split(payload.Value, true)
go func() { go func() {
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)
@ -357,18 +280,8 @@ func (sess *session) handleRequests(reqs <-chan *gossh.Request) {
// TODO: option/callback to allow agent forwarding // TODO: option/callback to allow agent forwarding
SetAgentRequested(sess.ctx) SetAgentRequested(sess.ctx)
req.Reply(true, nil) req.Reply(true, nil)
case "break":
ok := false
sess.Lock()
if sess.breakCh != nil {
sess.breakCh <- true
ok = true
}
req.Reply(ok, nil)
sess.Unlock()
default: default:
// TODO: debug log // TODO: debug log
req.Reply(false, nil)
} }
} }
} }

@ -1,4 +1,4 @@
package glider_ssh package ssh
import ( import (
"bytes" "bytes"
@ -11,7 +11,6 @@ import (
) )
func (srv *Server) serveOnce(l net.Listener) error { func (srv *Server) serveOnce(l net.Listener) error {
srv.ensureHandlers()
if err := srv.ensureHostSigner(); err != nil { if err := srv.ensureHostSigner(); err != nil {
return err return err
} }
@ -19,11 +18,11 @@ func (srv *Server) serveOnce(l net.Listener) error {
if e != nil { if e != nil {
return e return e
} }
srv.ChannelHandlers = map[string]ChannelHandler{ srv.channelHandlers = map[string]channelHandler{
"session": DefaultSessionHandler, "session": sessionHandler,
"direct-tcpip": DirectTCPIPHandler, "direct-tcpip": directTcpipHandler,
} }
srv.HandleConn(conn) srv.handleConn(conn)
return nil return nil
} }
@ -289,40 +288,20 @@ func TestPtyResize(t *testing.T) {
func TestSignals(t *testing.T) { func TestSignals(t *testing.T) {
t.Parallel() t.Parallel()
// errChan lets us get errors back from the session
errChan := make(chan error, 5)
// doneChan lets us specify that we should exit.
doneChan := make(chan interface{})
session, _, cleanup := newTestSession(t, &Server{ session, _, cleanup := newTestSession(t, &Server{
Handler: func(s Session) { Handler: func(s Session) {
// We need to use a buffered channel here, otherwise it's possible for the signals := make(chan Signal)
// second call to Signal to get discarded.
signals := make(chan Signal, 2)
s.Signals(signals) s.Signals(signals)
if sig := <-signals; sig != SIGINT {
select { t.Fatalf("expected signal %v but got %v", SIGINT, sig)
case sig := <-signals:
if sig != SIGINT {
errChan <- fmt.Errorf("expected signal %v but got %v", SIGINT, sig)
return
}
case <-doneChan:
errChan <- fmt.Errorf("Unexpected done")
return
} }
exiter := make(chan bool)
select { go func() {
case sig := <-signals: if sig := <-signals; sig == SIGKILL {
if sig != SIGKILL { close(exiter)
errChan <- fmt.Errorf("expected signal %v but got %v", SIGKILL, sig)
return
} }
case <-doneChan: }()
errChan <- fmt.Errorf("Unexpected done") <-exiter
return
}
}, },
}, nil) }, nil)
defer cleanup() defer cleanup()
@ -332,106 +311,7 @@ func TestSignals(t *testing.T) {
session.Signal(gossh.SIGKILL) session.Signal(gossh.SIGKILL)
}() }()
go func() { err := session.Run("")
errChan <- session.Run("")
}()
err := <-errChan
close(doneChan)
if err != nil {
t.Fatalf("expected nil but got %v", err)
}
}
func TestBreakWithChanRegistered(t *testing.T) {
t.Parallel()
// errChan lets us get errors back from the session
errChan := make(chan error, 5)
// doneChan lets us specify that we should exit.
doneChan := make(chan interface{})
breakChan := make(chan bool)
readyToReceiveBreak := make(chan bool)
session, _, cleanup := newTestSession(t, &Server{
Handler: func(s Session) {
s.Break(breakChan) // register a break channel with the session
readyToReceiveBreak <- true
select {
case <-breakChan:
io.WriteString(s, "break")
case <-doneChan:
errChan <- fmt.Errorf("Unexpected done")
return
}
},
}, nil)
defer cleanup()
var stdout bytes.Buffer
session.Stdout = &stdout
go func() {
errChan <- session.Run("")
}()
<-readyToReceiveBreak
ok, err := session.SendRequest("break", true, nil)
if err != nil {
t.Fatalf("expected nil but got %v", err)
}
if ok != true {
t.Fatalf("expected true but got %v", ok)
}
err = <-errChan
close(doneChan)
if err != nil {
t.Fatalf("expected nil but got %v", err)
}
if !bytes.Equal(stdout.Bytes(), []byte("break")) {
t.Fatalf("stdout = %#v, expected 'break'", stdout.Bytes())
}
}
func TestBreakWithoutChanRegistered(t *testing.T) {
t.Parallel()
// errChan lets us get errors back from the session
errChan := make(chan error, 5)
// doneChan lets us specify that we should exit.
doneChan := make(chan interface{})
waitUntilAfterBreakSent := make(chan bool)
session, _, cleanup := newTestSession(t, &Server{
Handler: func(s Session) {
<-waitUntilAfterBreakSent
},
}, nil)
defer cleanup()
var stdout bytes.Buffer
session.Stdout = &stdout
go func() {
errChan <- session.Run("")
}()
ok, err := session.SendRequest("break", true, nil)
if err != nil {
t.Fatalf("expected nil but got %v", err)
}
if ok != false {
t.Fatalf("expected false but got %v", ok)
}
waitUntilAfterBreakSent <- true
err = <-errChan
close(doneChan)
if err != nil { if err != nil {
t.Fatalf("expected nil but got %v", err) t.Fatalf("expected nil but got %v", err)
} }

34
ssh.go

@ -1,10 +1,8 @@
package glider_ssh package ssh
import ( import (
"crypto/subtle" "crypto/subtle"
"net" "net"
gossh "golang.org/x/crypto/ssh"
) )
type Signal string type Signal string
@ -41,33 +39,17 @@ type PublicKeyHandler func(ctx Context, key PublicKey) bool
// PasswordHandler is a callback for performing password authentication. // PasswordHandler is a callback for performing password authentication.
type PasswordHandler func(ctx Context, password string) bool type PasswordHandler func(ctx Context, password string) bool
// KeyboardInteractiveHandler is a callback for performing keyboard-interactive authentication.
type KeyboardInteractiveHandler func(ctx Context, challenger gossh.KeyboardInteractiveChallenge) bool
// PtyCallback is a hook for allowing PTY sessions. // PtyCallback is a hook for allowing PTY sessions.
type PtyCallback func(ctx Context, pty Pty) bool type PtyCallback func(ctx Context, pty Pty) bool
// SessionRequestCallback is a callback for allowing or denying SSH sessions.
type SessionRequestCallback func(sess Session, requestType string) bool
// ConnCallback is a hook for new connections before handling. // ConnCallback is a hook for new connections before handling.
// It allows wrapping for timeouts and limiting by returning // It allows wrapping for timeouts and limiting by returning
// the net.Conn that will be used as the underlying connection. // the net.Conn that will be used as the underlying connection.
type ConnCallback func(ctx Context, conn net.Conn) net.Conn type ConnCallback func(conn net.Conn) net.Conn
// LocalPortForwardingCallback is a hook for allowing port forwarding // LocalPortForwardingCallback is a hook for allowing port forwarding
type LocalPortForwardingCallback func(ctx Context, destinationHost string, destinationPort uint32) bool type LocalPortForwardingCallback func(ctx Context, destinationHost string, destinationPort uint32) bool
// ReversePortForwardingCallback is a hook for allowing reverse port forwarding
type ReversePortForwardingCallback func(ctx Context, bindHost string, bindPort uint32) bool
// ServerConfigCallback is a hook for creating custom default server configs
type ServerConfigCallback func(ctx Context) *gossh.ServerConfig
// ConnectionFailedCallback is a hook for reporting failed connections
// Please note: the net.Conn is likely to be closed at this point
type ConnectionFailedCallback func(conn net.Conn, err error)
// Window represents the size of a PTY window. // Window represents the size of a PTY window.
type Window struct { type Window struct {
Width int Width int
@ -85,27 +67,27 @@ type Pty struct {
// connection goroutine for each. The connection goroutines read requests and // connection goroutine for each. The connection goroutines read requests and
// then calls handler to handle sessions. Handler is typically nil, in which // then calls handler to handle sessions. Handler is typically nil, in which
// case the DefaultHandler is used. // case the DefaultHandler is used.
func Serve(l net.Listener, handler Handler, options ...Option) (*Server, error) { func Serve(l net.Listener, handler Handler, options ...Option) error {
srv := &Server{Handler: handler} srv := &Server{Handler: handler}
for _, option := range options { for _, option := range options {
if err := srv.SetOption(option); err != nil { if err := srv.SetOption(option); err != nil {
return nil, err return err
} }
} }
return srv, srv.Serve(l) return srv.Serve(l)
} }
// ListenAndServe listens on the TCP network address addr and then calls Serve // ListenAndServe listens on the TCP network address addr and then calls Serve
// with handler to handle sessions on incoming connections. Handler is typically // with handler to handle sessions on incoming connections. Handler is typically
// nil, in which case the DefaultHandler is used. // nil, in which case the DefaultHandler is used.
func ListenAndServe(addr string, handler Handler, options ...Option) (*Server, error) { func ListenAndServe(addr string, handler Handler, options ...Option) error {
srv := &Server{Addr: addr, Handler: handler} srv := &Server{Addr: addr, Handler: handler}
for _, option := range options { for _, option := range options {
if err := srv.SetOption(option); err != nil { if err := srv.SetOption(option); err != nil {
return nil, err return err
} }
} }
return srv, srv.ListenAndServe() return srv.ListenAndServe()
} }
// Handle registers the handler as the DefaultHandler. // Handle registers the handler as the DefaultHandler.

@ -1,4 +1,4 @@
package glider_ssh package ssh
import ( import (
"testing" "testing"

157
tcpip.go

@ -1,44 +1,36 @@
package glider_ssh package ssh
import ( import (
"io" "io"
"log"
"net" "net"
"strconv" "strconv"
"sync"
gossh "golang.org/x/crypto/ssh" gossh "golang.org/x/crypto/ssh"
) )
const (
forwardedTCPChannelType = "forwarded-tcpip"
)
// direct-tcpip data struct as specified in RFC4254, Section 7.2 // direct-tcpip data struct as specified in RFC4254, Section 7.2
type localForwardChannelData struct { type forwardData struct {
DestAddr string DestinationHost string
DestPort uint32 DestinationPort uint32
OriginAddr string OriginatorHost string
OriginPort uint32 OriginatorPort uint32
} }
// DirectTCPIPHandler can be enabled by adding it to the server's func directTcpipHandler(srv *Server, conn *gossh.ServerConn, newChan gossh.NewChannel, ctx Context) {
// ChannelHandlers under direct-tcpip. d := forwardData{}
func DirectTCPIPHandler(srv *Server, conn *gossh.ServerConn, newChan gossh.NewChannel, ctx Context) {
d := localForwardChannelData{}
if err := gossh.Unmarshal(newChan.ExtraData(), &d); err != nil { if err := gossh.Unmarshal(newChan.ExtraData(), &d); err != nil {
newChan.Reject(gossh.ConnectionFailed, "error parsing forward data: "+err.Error()) newChan.Reject(gossh.ConnectionFailed, "error parsing forward data: "+err.Error())
return return
} }
if srv.LocalPortForwardingCallback == nil || !srv.LocalPortForwardingCallback(ctx, d.DestAddr, d.DestPort) { if srv.LocalPortForwardingCallback == nil || !srv.LocalPortForwardingCallback(ctx, d.DestinationHost, d.DestinationPort) {
newChan.Reject(gossh.Prohibited, "port forwarding is disabled") newChan.Reject(gossh.Prohibited, "port forwarding is disabled")
return return
} }
dest := net.JoinHostPort(d.DestAddr, strconv.FormatInt(int64(d.DestPort), 10)) dest := net.JoinHostPort(d.DestinationHost, strconv.FormatInt(int64(d.DestinationPort), 10))
var dialer net.Dialer var dialer net.Dialer
dconn, err := dialer.DialContext(ctx, "tcp", dest) dconn, err := dialer.DialContext(ctx, "tcp", dest)
if err != nil { if err != nil {
@ -64,130 +56,3 @@ func DirectTCPIPHandler(srv *Server, conn *gossh.ServerConn, newChan gossh.NewCh
io.Copy(dconn, ch) io.Copy(dconn, ch)
}() }()
} }
type remoteForwardRequest struct {
BindAddr string
BindPort uint32
}
type remoteForwardSuccess struct {
BindPort uint32
}
type remoteForwardCancelRequest struct {
BindAddr string
BindPort uint32
}
type remoteForwardChannelData struct {
DestAddr string
DestPort uint32
OriginAddr string
OriginPort uint32
}
// ForwardedTCPHandler can be enabled by creating a ForwardedTCPHandler and
// adding the HandleSSHRequest callback to the server's RequestHandlers under
// tcpip-forward and cancel-tcpip-forward.
type ForwardedTCPHandler struct {
forwards map[string]net.Listener
sync.Mutex
}
func (h *ForwardedTCPHandler) HandleSSHRequest(ctx Context, srv *Server, req *gossh.Request) (bool, []byte) {
h.Lock()
if h.forwards == nil {
h.forwards = make(map[string]net.Listener)
}
h.Unlock()
conn := ctx.Value(ContextKeyConn).(*gossh.ServerConn)
switch req.Type {
case "tcpip-forward":
var reqPayload remoteForwardRequest
if err := gossh.Unmarshal(req.Payload, &reqPayload); err != nil {
// TODO: log parse failure
return false, []byte{}
}
if srv.ReversePortForwardingCallback == nil || !srv.ReversePortForwardingCallback(ctx, reqPayload.BindAddr, reqPayload.BindPort) {
return false, []byte("port forwarding is disabled")
}
addr := net.JoinHostPort(reqPayload.BindAddr, strconv.Itoa(int(reqPayload.BindPort)))
ln, err := net.Listen("tcp", addr)
if err != nil {
// TODO: log listen failure
return false, []byte{}
}
_, destPortStr, _ := net.SplitHostPort(ln.Addr().String())
destPort, _ := strconv.Atoi(destPortStr)
h.Lock()
h.forwards[addr] = ln
h.Unlock()
go func() {
<-ctx.Done()
h.Lock()
ln, ok := h.forwards[addr]
h.Unlock()
if ok {
ln.Close()
}
}()
go func() {
for {
c, err := ln.Accept()
if err != nil {
// TODO: log accept failure
break
}
originAddr, orignPortStr, _ := net.SplitHostPort(c.RemoteAddr().String())
originPort, _ := strconv.Atoi(orignPortStr)
payload := gossh.Marshal(&remoteForwardChannelData{
DestAddr: reqPayload.BindAddr,
DestPort: uint32(destPort),
OriginAddr: originAddr,
OriginPort: uint32(originPort),
})
go func() {
ch, reqs, err := conn.OpenChannel(forwardedTCPChannelType, payload)
if err != nil {
// TODO: log failure to open channel
log.Println(err)
c.Close()
return
}
go gossh.DiscardRequests(reqs)
go func() {
defer ch.Close()
defer c.Close()
io.Copy(ch, c)
}()
go func() {
defer ch.Close()
defer c.Close()
io.Copy(c, ch)
}()
}()
}
h.Lock()
delete(h.forwards, addr)
h.Unlock()
}()
return true, gossh.Marshal(&remoteForwardSuccess{uint32(destPort)})
case "cancel-tcpip-forward":
var reqPayload remoteForwardCancelRequest
if err := gossh.Unmarshal(req.Payload, &reqPayload); err != nil {
// TODO: log parse failure
return false, []byte{}
}
addr := net.JoinHostPort(reqPayload.BindAddr, strconv.Itoa(int(reqPayload.BindPort)))
h.Lock()
ln, ok := h.forwards[addr]
h.Unlock()
if ok {
ln.Close()
}
return true, nil
default:
return false, nil
}
}

@ -1,4 +1,4 @@
package glider_ssh package ssh
import ( import (
"bytes" "bytes"

@ -1,4 +1,4 @@
package glider_ssh package ssh
import ( import (
"crypto/rand" "crypto/rand"
@ -22,10 +22,16 @@ func parsePtyRequest(s []byte) (pty Pty, ok bool) {
return return
} }
width32, s, ok := parseUint32(s) width32, s, ok := parseUint32(s)
if width32 < 1 {
ok = false
}
if !ok { if !ok {
return return
} }
height32, _, ok := parseUint32(s) height32, _, ok := parseUint32(s)
if height32 < 1 {
ok = false
}
if !ok { if !ok {
return return
} }

@ -1,4 +1,4 @@
package glider_ssh package ssh
import gossh "golang.org/x/crypto/ssh" import gossh "golang.org/x/crypto/ssh"