Compare commits
2 Commits
master
...
ssh-intern
Author | SHA1 | Date | |
---|---|---|---|
![]() |
4703ad4dc1 | ||
![]() |
cd0f9291c6 |
2
.gitignore
vendored
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
|
||||
|
||||
[BSD](LICENSE)
|
||||
BSD
|
||||
|
@ -6,17 +6,17 @@ import (
|
||||
"io"
|
||||
"log"
|
||||
|
||||
"git.tcp.direct/bfu/glider-ssh"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/docker/docker/pkg/stdcopy"
|
||||
"github.com/gliderlabs/ssh"
|
||||
)
|
||||
|
||||
func main() {
|
||||
ssh.Handle(func(sess ssh.Session) {
|
||||
_, _, isTty := sess.Pty()
|
||||
var cfg = &container.Config{
|
||||
cfg := &container.Config{
|
||||
Image: sess.User(),
|
||||
Cmd: sess.Command(),
|
||||
Env: sess.Environ(),
|
||||
|
@ -5,7 +5,7 @@ import (
|
||||
"log"
|
||||
"os/exec"
|
||||
|
||||
"git.tcp.direct/bfu/glider-ssh"
|
||||
"github.com/gliderlabs/ssh"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
@ -9,8 +9,8 @@ import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"git.tcp.direct/bfu/glider-ssh"
|
||||
"github.com/creack/pty"
|
||||
"github.com/gliderlabs/ssh"
|
||||
"github.com/kr/pty"
|
||||
)
|
||||
|
||||
func setWinsize(f *os.File, w, h int) {
|
||||
@ -37,7 +37,6 @@ func main() {
|
||||
io.Copy(f, s) // stdin
|
||||
}()
|
||||
io.Copy(s, f) // stdout
|
||||
cmd.Wait()
|
||||
} else {
|
||||
io.WriteString(s, "No PTY requested.\n")
|
||||
s.Exit(1)
|
||||
|
@ -5,7 +5,7 @@ import (
|
||||
"io"
|
||||
"log"
|
||||
|
||||
"git.tcp.direct/bfu/glider-ssh"
|
||||
"github.com/gliderlabs/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"
|
||||
"log"
|
||||
|
||||
"git.tcp.direct/bfu/glider-ssh"
|
||||
"github.com/gliderlabs/ssh"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
@ -4,7 +4,7 @@ import (
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"git.tcp.direct/bfu/glider-ssh"
|
||||
"github.com/gliderlabs/ssh"
|
||||
)
|
||||
|
||||
var (
|
||||
|
2
agent.go
2
agent.go
@ -1,4 +1,4 @@
|
||||
package glider_ssh
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
26
circle.yml
Normal file
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
|
4
conn.go
4
conn.go
@ -1,4 +1,4 @@
|
||||
package glider_ssh
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"context"
|
||||
@ -44,7 +44,7 @@ func (c *serverConn) updateDeadline() {
|
||||
switch {
|
||||
case c.idleTimeout > 0:
|
||||
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)
|
||||
return
|
||||
}
|
||||
|
17
context.go
17
context.go
@ -1,10 +1,9 @@
|
||||
package glider_ssh
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
gossh "golang.org/x/crypto/ssh"
|
||||
)
|
||||
@ -49,7 +48,7 @@ var (
|
||||
ContextKeyServer = &contextKey{"ssh-server"}
|
||||
|
||||
// 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"}
|
||||
|
||||
// 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
|
||||
// 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
|
||||
// exposed on Session in the session Handler. A connection-scoped lock is also
|
||||
// embedded in the context to make it easier to limit operations per-connection.
|
||||
// exposed on Session in the session Handler.
|
||||
type Context interface {
|
||||
context.Context
|
||||
sync.Locker
|
||||
|
||||
// User returns the username used when establishing the SSH connection.
|
||||
User() string
|
||||
@ -93,12 +90,11 @@ type Context interface {
|
||||
|
||||
type sshContext struct {
|
||||
context.Context
|
||||
*sync.Mutex
|
||||
}
|
||||
|
||||
func newContext(srv *Server) (*sshContext, context.CancelFunc) {
|
||||
innerCtx, cancel := context.WithCancel(context.Background())
|
||||
ctx := &sshContext{innerCtx, &sync.Mutex{}}
|
||||
ctx := &sshContext{innerCtx}
|
||||
ctx.SetValue(ContextKeyServer, srv)
|
||||
perms := &Permissions{&gossh.Permissions{}}
|
||||
ctx.SetValue(ContextKeyPermissions, perms)
|
||||
@ -140,10 +136,7 @@ func (ctx *sshContext) ServerVersion() string {
|
||||
}
|
||||
|
||||
func (ctx *sshContext) RemoteAddr() net.Addr {
|
||||
if addr, ok := ctx.Value(ContextKeyRemoteAddr).(net.Addr); ok {
|
||||
return addr
|
||||
}
|
||||
return nil
|
||||
return ctx.Value(ContextKeyRemoteAddr).(net.Addr)
|
||||
}
|
||||
|
||||
func (ctx *sshContext) LocalAddr() net.Addr {
|
||||
|
@ -1,4 +1,4 @@
|
||||
package glider_ssh
|
||||
package ssh
|
||||
|
||||
import "testing"
|
||||
|
||||
|
4
doc.go
4
doc.go
@ -1,4 +1,5 @@
|
||||
/*
|
||||
|
||||
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
|
||||
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
|
||||
started, but not completed. Pull Requests welcome!
|
||||
|
||||
*/
|
||||
package glider_ssh
|
||||
package ssh
|
||||
|
@ -1,10 +1,10 @@
|
||||
package glider_ssh_test
|
||||
package ssh_test
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
||||
"git.tcp.direct/bfu/glider-ssh"
|
||||
"github.com/gliderlabs/ssh"
|
||||
)
|
||||
|
||||
func ExampleListenAndServe() {
|
||||
|
8
go.mod
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
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 (
|
||||
"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
|
||||
// from a PEM file as bytes.
|
||||
func HostKeyPEM(bytes []byte) Option {
|
||||
|
@ -1,4 +1,4 @@
|
||||
package glider_ssh
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"net"
|
||||
@ -95,7 +95,7 @@ func TestConnWrapping(t *testing.T) {
|
||||
HostKeyCallback: gossh.InsecureIgnoreHostKey(),
|
||||
}, PasswordAuth(func(ctx Context, password string) bool {
|
||||
return true
|
||||
}), WrapConn(func(ctx Context, conn net.Conn) net.Conn {
|
||||
}), WrapConn(func(conn net.Conn) net.Conn {
|
||||
wrapped = &wrappedConn{conn, 0}
|
||||
return wrapped
|
||||
}))
|
||||
|
181
server.go
181
server.go
@ -1,4 +1,4 @@
|
||||
package glider_ssh
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"context"
|
||||
@ -15,20 +15,6 @@ 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{}
|
||||
|
||||
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 is a valid configuration. When both PasswordHandler and
|
||||
// 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
|
||||
Version string // server version to be sent before the initial handshake
|
||||
|
||||
KeyboardInteractiveHandler KeyboardInteractiveHandler // keyboard-interactive authentication handler
|
||||
PasswordHandler PasswordHandler // password authentication handler
|
||||
PublicKeyHandler PublicKeyHandler // public key authentication handler
|
||||
PtyCallback PtyCallback // callback for allowing PTY sessions, allows all if nil
|
||||
ConnCallback ConnCallback // optional callback for wrapping net.Conn before handling
|
||||
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
|
||||
PasswordHandler PasswordHandler // password authentication handler
|
||||
PublicKeyHandler PublicKeyHandler // public key authentication handler
|
||||
PtyCallback PtyCallback // callback for allowing PTY sessions, allows all if nil
|
||||
ConnCallback ConnCallback // optional callback for wrapping net.Conn before handling
|
||||
LocalPortForwardingCallback LocalPortForwardingCallback // callback for allowing local port forwarding, denies all if nil
|
||||
|
||||
IdleTimeout time.Duration // connection timeout when no activity, none if empty
|
||||
MaxTimeout time.Duration // absolute connection timeout, none if empty
|
||||
|
||||
// ChannelHandlers allow overriding the built-in session handlers or provide
|
||||
// extensions to the protocol, such as tcpip forwarding. By default only the
|
||||
// "session" handler is enabled.
|
||||
ChannelHandlers map[string]ChannelHandler
|
||||
// Internal x/crypto/ssh config. Note that a number of values in this struct
|
||||
// are overwritten every time a connection starts, so only use this if you
|
||||
// know what you're doing and absolutely need to change the internal config
|
||||
// values.
|
||||
BaseConfig *gossh.ServerConfig
|
||||
|
||||
// RequestHandlers allow overriding the server-level request handlers or
|
||||
// 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
|
||||
channelHandlers map[string]channelHandler
|
||||
|
||||
listenerWg sync.WaitGroup
|
||||
mu sync.RWMutex
|
||||
mu sync.Mutex
|
||||
listeners map[net.Listener]struct{}
|
||||
conns map[*gossh.ServerConn]struct{}
|
||||
connWg sync.WaitGroup
|
||||
doneChan chan struct{}
|
||||
}
|
||||
|
||||
func (srv *Server) ensureHostSigner() error {
|
||||
srv.mu.Lock()
|
||||
defer srv.mu.Unlock()
|
||||
// internal for now
|
||||
type channelHandler func(srv *Server, conn *gossh.ServerConn, newChan gossh.NewChannel, ctx Context)
|
||||
|
||||
func (srv *Server) ensureHostSigner() error {
|
||||
if len(srv.HostSigners) == 0 {
|
||||
signer, err := generateSigner()
|
||||
if err != nil {
|
||||
@ -89,44 +63,18 @@ func (srv *Server) ensureHostSigner() error {
|
||||
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 {
|
||||
srv.mu.RLock()
|
||||
defer srv.mu.RUnlock()
|
||||
|
||||
var config *gossh.ServerConfig
|
||||
if srv.ServerConfigCallback == nil {
|
||||
// Use the provided base config if set, otherwise default to an empty
|
||||
// config.
|
||||
config := srv.BaseConfig
|
||||
if config == nil {
|
||||
config = &gossh.ServerConfig{}
|
||||
} else {
|
||||
config = srv.ServerConfigCallback(ctx)
|
||||
}
|
||||
|
||||
for _, signer := range srv.HostSigners {
|
||||
config.AddHostKey(signer)
|
||||
}
|
||||
if srv.PasswordHandler == nil && srv.PublicKeyHandler == nil && srv.KeyboardInteractiveHandler == nil {
|
||||
if srv.PasswordHandler == nil && srv.PublicKeyHandler == nil {
|
||||
config.NoClientAuth = true
|
||||
}
|
||||
if srv.Version != "" {
|
||||
@ -151,23 +99,12 @@ func (srv *Server) config(ctx Context) *gossh.ServerConfig {
|
||||
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
|
||||
}
|
||||
|
||||
// Handle sets the Handler for the server.
|
||||
func (srv *Server) Handle(fn Handler) {
|
||||
srv.mu.Lock()
|
||||
defer srv.mu.Unlock()
|
||||
|
||||
srv.Handler = fn
|
||||
}
|
||||
|
||||
@ -179,7 +116,6 @@ func (srv *Server) Handle(fn Handler) {
|
||||
func (srv *Server) Close() error {
|
||||
srv.mu.Lock()
|
||||
defer srv.mu.Unlock()
|
||||
|
||||
srv.closeDoneChanLocked()
|
||||
err := srv.closeListenersLocked()
|
||||
for c := range srv.conns {
|
||||
@ -221,7 +157,6 @@ func (srv *Server) Shutdown(ctx context.Context) error {
|
||||
//
|
||||
// Serve always returns a non-nil error.
|
||||
func (srv *Server) Serve(l net.Listener) error {
|
||||
srv.ensureHandlers()
|
||||
defer l.Close()
|
||||
if err := srv.ensureHostSigner(); err != nil {
|
||||
return err
|
||||
@ -229,6 +164,12 @@ func (srv *Server) Serve(l net.Listener) error {
|
||||
if srv.Handler == nil {
|
||||
srv.Handler = DefaultHandler
|
||||
}
|
||||
if srv.channelHandlers == nil {
|
||||
srv.channelHandlers = map[string]channelHandler{
|
||||
"session": sessionHandler,
|
||||
"direct-tcpip": directTcpipHandler,
|
||||
}
|
||||
}
|
||||
var tempDelay time.Duration
|
||||
|
||||
srv.trackListener(l, true)
|
||||
@ -255,20 +196,20 @@ func (srv *Server) Serve(l net.Listener) error {
|
||||
}
|
||||
return e
|
||||
}
|
||||
go srv.HandleConn(conn)
|
||||
go srv.handleConn(conn)
|
||||
}
|
||||
}
|
||||
|
||||
func (srv *Server) HandleConn(newConn net.Conn) {
|
||||
ctx, cancel := newContext(srv)
|
||||
func (srv *Server) handleConn(newConn net.Conn) {
|
||||
if srv.ConnCallback != nil {
|
||||
cbConn := srv.ConnCallback(ctx, newConn)
|
||||
cbConn := srv.ConnCallback(newConn)
|
||||
if cbConn == nil {
|
||||
newConn.Close()
|
||||
return
|
||||
}
|
||||
newConn = cbConn
|
||||
}
|
||||
ctx, cancel := newContext(srv)
|
||||
conn := &serverConn{
|
||||
Conn: newConn,
|
||||
idleTimeout: srv.IdleTimeout,
|
||||
@ -280,9 +221,7 @@ func (srv *Server) HandleConn(newConn net.Conn) {
|
||||
defer conn.Close()
|
||||
sshConn, chans, reqs, err := gossh.NewServerConn(conn, srv.config(ctx))
|
||||
if err != nil {
|
||||
if srv.ConnectionFailedCallback != nil {
|
||||
srv.ConnectionFailedCallback(conn, err)
|
||||
}
|
||||
// TODO: trigger event callback
|
||||
return
|
||||
}
|
||||
|
||||
@ -291,14 +230,10 @@ func (srv *Server) HandleConn(newConn net.Conn) {
|
||||
|
||||
ctx.SetValue(ContextKeyConn, sshConn)
|
||||
applyConnMetadata(ctx, sshConn)
|
||||
//go gossh.DiscardRequests(reqs)
|
||||
go srv.handleRequests(ctx, reqs)
|
||||
go gossh.DiscardRequests(reqs)
|
||||
for ch := range chans {
|
||||
handler := srv.ChannelHandlers[ch.ChannelType()]
|
||||
if handler == nil {
|
||||
handler = srv.ChannelHandlers["default"]
|
||||
}
|
||||
if handler == nil {
|
||||
handler, found := srv.channelHandlers[ch.ChannelType()]
|
||||
if !found {
|
||||
ch.Reject(gossh.UnknownChannelType, "unsupported channel type")
|
||||
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
|
||||
// Serve to handle incoming connections. If srv.Addr is blank, ":22" is used.
|
||||
// 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
|
||||
// least one host key.
|
||||
func (srv *Server) AddHostKey(key Signer) {
|
||||
srv.mu.Lock()
|
||||
defer srv.mu.Unlock()
|
||||
|
||||
// these are later added via AddHostKey on ServerConfig, which performs the
|
||||
// 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)
|
||||
}
|
||||
|
||||
// SetOption runs a functional option against the server.
|
||||
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)
|
||||
}
|
||||
|
||||
func (srv *Server) getDoneChan() <-chan struct{} {
|
||||
srv.mu.Lock()
|
||||
defer srv.mu.Unlock()
|
||||
|
||||
return srv.getDoneChanLocked()
|
||||
}
|
||||
|
||||
@ -414,7 +309,6 @@ func (srv *Server) closeListenersLocked() error {
|
||||
func (srv *Server) trackListener(ln net.Listener, add bool) {
|
||||
srv.mu.Lock()
|
||||
defer srv.mu.Unlock()
|
||||
|
||||
if srv.listeners == nil {
|
||||
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) {
|
||||
srv.mu.Lock()
|
||||
defer srv.mu.Unlock()
|
||||
|
||||
if srv.conns == nil {
|
||||
srv.conns = make(map[*gossh.ServerConn]struct{})
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
package glider_ssh
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@ -8,26 +8,6 @@ import (
|
||||
"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) {
|
||||
l := newLocalListener()
|
||||
testBytes := []byte("Hello world\n")
|
||||
|
135
session.go
135
session.go
@ -1,4 +1,4 @@
|
||||
package glider_ssh
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@ -14,7 +14,7 @@ import (
|
||||
|
||||
// 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
|
||||
// crypto/ssh.
|
||||
// cypto/ssh.
|
||||
//
|
||||
// When Command() returns an empty slice, the user requested a shell. Otherwise
|
||||
// the user is performing an exec with those command arguments.
|
||||
@ -44,12 +44,6 @@ type Session interface {
|
||||
// which considers quoting not just whitespace.
|
||||
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
|
||||
// used it will return nil.
|
||||
PublicKey() PublicKey
|
||||
@ -77,32 +71,24 @@ type Session interface {
|
||||
// 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)
|
||||
|
||||
// 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
|
||||
// when there is no signal channel specified
|
||||
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()
|
||||
if err != nil {
|
||||
// TODO: trigger event callback
|
||||
return
|
||||
}
|
||||
sess := &session{
|
||||
Channel: ch,
|
||||
conn: conn,
|
||||
handler: srv.Handler,
|
||||
ptyCb: srv.PtyCallback,
|
||||
sessReqCb: srv.SessionRequestCallback,
|
||||
subsystemHandlers: srv.SubsystemHandlers,
|
||||
ctx: ctx,
|
||||
Channel: ch,
|
||||
conn: conn,
|
||||
handler: srv.Handler,
|
||||
ptyCb: srv.PtyCallback,
|
||||
ctx: ctx,
|
||||
}
|
||||
sess.handleRequests(reqs)
|
||||
}
|
||||
@ -110,22 +96,18 @@ func DefaultSessionHandler(srv *Server, conn *gossh.ServerConn, newChan gossh.Ne
|
||||
type session struct {
|
||||
sync.Mutex
|
||||
gossh.Channel
|
||||
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
|
||||
subsystem string
|
||||
ctx Context
|
||||
sigCh chan<- Signal
|
||||
sigBuf []Signal
|
||||
breakCh chan<- bool
|
||||
conn *gossh.ServerConn
|
||||
handler Handler
|
||||
handled bool
|
||||
exited bool
|
||||
pty *Pty
|
||||
winch chan Window
|
||||
env []string
|
||||
ptyCb PtyCallback
|
||||
cmd []string
|
||||
ctx Context
|
||||
sigCh chan<- Signal
|
||||
sigBuf []Signal
|
||||
}
|
||||
|
||||
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...)
|
||||
}
|
||||
|
||||
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) Subsystem() string {
|
||||
return sess.subsystem
|
||||
return append([]string(nil), sess.cmd...)
|
||||
}
|
||||
|
||||
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) {
|
||||
for req := range reqs {
|
||||
switch req.Type {
|
||||
@ -242,60 +209,16 @@ func (sess *session) handleRequests(reqs <-chan *gossh.Request) {
|
||||
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)
|
||||
|
||||
var payload = struct{ Value string }{}
|
||||
gossh.Unmarshal(req.Payload, &payload)
|
||||
sess.cmd, _ = shlex.Split(payload.Value, true)
|
||||
go func() {
|
||||
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.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":
|
||||
if sess.handled {
|
||||
req.Reply(false, nil)
|
||||
@ -357,18 +280,8 @@ func (sess *session) handleRequests(reqs <-chan *gossh.Request) {
|
||||
// TODO: option/callback to allow agent forwarding
|
||||
SetAgentRequested(sess.ctx)
|
||||
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:
|
||||
// TODO: debug log
|
||||
req.Reply(false, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
150
session_test.go
150
session_test.go
@ -1,4 +1,4 @@
|
||||
package glider_ssh
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@ -11,7 +11,6 @@ import (
|
||||
)
|
||||
|
||||
func (srv *Server) serveOnce(l net.Listener) error {
|
||||
srv.ensureHandlers()
|
||||
if err := srv.ensureHostSigner(); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -19,11 +18,11 @@ func (srv *Server) serveOnce(l net.Listener) error {
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
srv.ChannelHandlers = map[string]ChannelHandler{
|
||||
"session": DefaultSessionHandler,
|
||||
"direct-tcpip": DirectTCPIPHandler,
|
||||
srv.channelHandlers = map[string]channelHandler{
|
||||
"session": sessionHandler,
|
||||
"direct-tcpip": directTcpipHandler,
|
||||
}
|
||||
srv.HandleConn(conn)
|
||||
srv.handleConn(conn)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -289,40 +288,20 @@ func TestPtyResize(t *testing.T) {
|
||||
func TestSignals(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{})
|
||||
|
||||
session, _, cleanup := newTestSession(t, &Server{
|
||||
Handler: func(s Session) {
|
||||
// We need to use a buffered channel here, otherwise it's possible for the
|
||||
// second call to Signal to get discarded.
|
||||
signals := make(chan Signal, 2)
|
||||
signals := make(chan Signal)
|
||||
s.Signals(signals)
|
||||
|
||||
select {
|
||||
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
|
||||
if sig := <-signals; sig != SIGINT {
|
||||
t.Fatalf("expected signal %v but got %v", SIGINT, sig)
|
||||
}
|
||||
|
||||
select {
|
||||
case sig := <-signals:
|
||||
if sig != SIGKILL {
|
||||
errChan <- fmt.Errorf("expected signal %v but got %v", SIGKILL, sig)
|
||||
return
|
||||
exiter := make(chan bool)
|
||||
go func() {
|
||||
if sig := <-signals; sig == SIGKILL {
|
||||
close(exiter)
|
||||
}
|
||||
case <-doneChan:
|
||||
errChan <- fmt.Errorf("Unexpected done")
|
||||
return
|
||||
}
|
||||
}()
|
||||
<-exiter
|
||||
},
|
||||
}, nil)
|
||||
defer cleanup()
|
||||
@ -332,106 +311,7 @@ func TestSignals(t *testing.T) {
|
||||
session.Signal(gossh.SIGKILL)
|
||||
}()
|
||||
|
||||
go func() {
|
||||
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)
|
||||
err := session.Run("")
|
||||
if err != nil {
|
||||
t.Fatalf("expected nil but got %v", err)
|
||||
}
|
||||
|
34
ssh.go
34
ssh.go
@ -1,10 +1,8 @@
|
||||
package glider_ssh
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"crypto/subtle"
|
||||
"net"
|
||||
|
||||
gossh "golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
type Signal string
|
||||
@ -41,33 +39,17 @@ type PublicKeyHandler func(ctx Context, key PublicKey) bool
|
||||
// PasswordHandler is a callback for performing password authentication.
|
||||
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.
|
||||
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.
|
||||
// It allows wrapping for timeouts and limiting by returning
|
||||
// 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
|
||||
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.
|
||||
type Window struct {
|
||||
Width int
|
||||
@ -85,27 +67,27 @@ type Pty struct {
|
||||
// connection goroutine for each. The connection goroutines read requests and
|
||||
// then calls handler to handle sessions. Handler is typically nil, in which
|
||||
// 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}
|
||||
for _, option := range options {
|
||||
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
|
||||
// with handler to handle sessions on incoming connections. Handler is typically
|
||||
// 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}
|
||||
for _, option := range options {
|
||||
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.
|
||||
|
@ -1,4 +1,4 @@
|
||||
package glider_ssh
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
157
tcpip.go
157
tcpip.go
@ -1,44 +1,36 @@
|
||||
package glider_ssh
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
gossh "golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
const (
|
||||
forwardedTCPChannelType = "forwarded-tcpip"
|
||||
)
|
||||
|
||||
// direct-tcpip data struct as specified in RFC4254, Section 7.2
|
||||
type localForwardChannelData struct {
|
||||
DestAddr string
|
||||
DestPort uint32
|
||||
type forwardData struct {
|
||||
DestinationHost string
|
||||
DestinationPort uint32
|
||||
|
||||
OriginAddr string
|
||||
OriginPort uint32
|
||||
OriginatorHost string
|
||||
OriginatorPort uint32
|
||||
}
|
||||
|
||||
// DirectTCPIPHandler can be enabled by adding it to the server's
|
||||
// ChannelHandlers under direct-tcpip.
|
||||
func DirectTCPIPHandler(srv *Server, conn *gossh.ServerConn, newChan gossh.NewChannel, ctx Context) {
|
||||
d := localForwardChannelData{}
|
||||
func directTcpipHandler(srv *Server, conn *gossh.ServerConn, newChan gossh.NewChannel, ctx Context) {
|
||||
d := forwardData{}
|
||||
if err := gossh.Unmarshal(newChan.ExtraData(), &d); err != nil {
|
||||
newChan.Reject(gossh.ConnectionFailed, "error parsing forward data: "+err.Error())
|
||||
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")
|
||||
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
|
||||
dconn, err := dialer.DialContext(ctx, "tcp", dest)
|
||||
if err != nil {
|
||||
@ -64,130 +56,3 @@ func DirectTCPIPHandler(srv *Server, conn *gossh.ServerConn, newChan gossh.NewCh
|
||||
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 (
|
||||
"bytes"
|
||||
|
8
util.go
8
util.go
@ -1,4 +1,4 @@
|
||||
package glider_ssh
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
@ -22,10 +22,16 @@ func parsePtyRequest(s []byte) (pty Pty, ok bool) {
|
||||
return
|
||||
}
|
||||
width32, s, ok := parseUint32(s)
|
||||
if width32 < 1 {
|
||||
ok = false
|
||||
}
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
height32, _, ok := parseUint32(s)
|
||||
if height32 < 1 {
|
||||
ok = false
|
||||
}
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
2
wrap.go
2
wrap.go
@ -1,4 +1,4 @@
|
||||
package glider_ssh
|
||||
package ssh
|
||||
|
||||
import gossh "golang.org/x/crypto/ssh"
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user