summaryrefslogtreecommitdiffstats
path: root/vendor/github.com/shazow
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/shazow')
-rw-r--r--vendor/github.com/shazow/rateio/LICENSE21
-rw-r--r--vendor/github.com/shazow/rateio/doc.go29
-rw-r--r--vendor/github.com/shazow/rateio/limiter.go62
-rw-r--r--vendor/github.com/shazow/rateio/reader.go25
-rw-r--r--vendor/github.com/shazow/rateio/writer.go25
-rw-r--r--vendor/github.com/shazow/ssh-chat/sshd/LICENSE21
-rw-r--r--vendor/github.com/shazow/ssh-chat/sshd/auth.go72
-rw-r--r--vendor/github.com/shazow/ssh-chat/sshd/client.go76
-rw-r--r--vendor/github.com/shazow/ssh-chat/sshd/doc.go34
-rw-r--r--vendor/github.com/shazow/ssh-chat/sshd/logger.go22
-rw-r--r--vendor/github.com/shazow/ssh-chat/sshd/net.go67
-rw-r--r--vendor/github.com/shazow/ssh-chat/sshd/pty.go70
-rw-r--r--vendor/github.com/shazow/ssh-chat/sshd/ratelimit.go71
-rw-r--r--vendor/github.com/shazow/ssh-chat/sshd/terminal.go167
14 files changed, 762 insertions, 0 deletions
diff --git a/vendor/github.com/shazow/rateio/LICENSE b/vendor/github.com/shazow/rateio/LICENSE
new file mode 100644
index 00000000..19011793
--- /dev/null
+++ b/vendor/github.com/shazow/rateio/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Andrey Petrov
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor/github.com/shazow/rateio/doc.go b/vendor/github.com/shazow/rateio/doc.go
new file mode 100644
index 00000000..1c5e0851
--- /dev/null
+++ b/vendor/github.com/shazow/rateio/doc.go
@@ -0,0 +1,29 @@
+/*
+Package rateio provides an io interfaces for rate-limiting.
+
+This can be used to apply rate limiting to any type that implements an io-style interface.
+
+For example, we can use it to restrict the reading rate of a net.Conn:
+
+ type limitedConn struct {
+ net.Conn
+ io.Reader // Our rate-limited io.Reader for net.Conn
+ }
+
+ func (r *limitedConn) Read(p []byte) (n int, err error) {
+ return r.Reader.Read(p)
+ }
+
+ // ReadLimitConn returns a net.Conn whose io.Reader interface is rate-limited by limiter.
+ func ReadLimitConn(conn net.Conn, limiter rateio.Limiter) net.Conn {
+ return &limitedConn{
+ Conn: conn,
+ Reader: rateio.NewReader(conn, limiter),
+ }
+ }
+
+Then we can use ReadLimitConn to wrap our existing net.Conn and continue using
+the wrapped version in its place.
+
+*/
+package rateio
diff --git a/vendor/github.com/shazow/rateio/limiter.go b/vendor/github.com/shazow/rateio/limiter.go
new file mode 100644
index 00000000..db5ae76c
--- /dev/null
+++ b/vendor/github.com/shazow/rateio/limiter.go
@@ -0,0 +1,62 @@
+package rateio
+
+import (
+ "errors"
+ "time"
+)
+
+const minInt = -int(^uint(0)>>1) - 1
+
+// The error returned when the read rate exceeds our specification.
+var ErrRateExceeded = errors.New("Read rate exceeded.")
+
+// Limiter is an interface for a rate limiter.
+// There are a few example limiters included in the package, but feel free to go wild with your own.
+type Limiter interface {
+ // Apply this many bytes to the limiter, return ErrRateExceeded if the defined rate is exceeded.
+ Count(int) error
+}
+
+// simpleLimiter is a rate limiter that restricts Amount bytes in Frequency duration.
+type simpleLimiter struct {
+ Amount int
+ Frequency time.Duration
+
+ numRead int
+ timeRead time.Time
+}
+
+// NewSimpleLimiter creates a Limiter that restricts a given number of bytes per frequency.
+func NewSimpleLimiter(amount int, frequency time.Duration) Limiter {
+ return &simpleLimiter{
+ Amount: amount,
+ Frequency: frequency,
+ }
+}
+
+// NewGracefulLimiter returns a Limiter that is the same as a
+// SimpleLimiter but adds a grace period at the start of the rate
+// limiting where it allows unlimited bytes to be read during that
+// period.
+func NewGracefulLimiter(amount int, frequency time.Duration, grace time.Duration) Limiter {
+ return &simpleLimiter{
+ Amount: amount,
+ Frequency: frequency,
+ numRead: minInt,
+ timeRead: time.Now().Add(grace),
+ }
+}
+
+// Count applies n bytes to the limiter.
+func (limit *simpleLimiter) Count(n int) error {
+ now := time.Now()
+ if now.After(limit.timeRead) {
+ limit.numRead = 0
+ limit.timeRead = now.Add(limit.Frequency)
+ }
+ limit.numRead += n
+ if limit.numRead > limit.Amount {
+ return ErrRateExceeded
+ }
+ return nil
+}
diff --git a/vendor/github.com/shazow/rateio/reader.go b/vendor/github.com/shazow/rateio/reader.go
new file mode 100644
index 00000000..56bd8ffa
--- /dev/null
+++ b/vendor/github.com/shazow/rateio/reader.go
@@ -0,0 +1,25 @@
+package rateio
+
+import "io"
+
+type reader struct {
+ io.Reader
+ Limiter
+}
+
+// Read reads data into p.
+// Returns ErrRateExceeded error if our specified read is exceeded.
+func (r *reader) Read(p []byte) (n int, err error) {
+ n, err = r.Reader.Read(p)
+ if err != nil {
+ return
+ }
+
+ err = r.Limiter.Count(n)
+ return
+}
+
+// NewReader proxies an io.Reader but keeps track of bytes read based on our Limiter.
+func NewReader(r io.Reader, limiter Limiter) io.Reader {
+ return &reader{r, limiter}
+}
diff --git a/vendor/github.com/shazow/rateio/writer.go b/vendor/github.com/shazow/rateio/writer.go
new file mode 100644
index 00000000..d4feea49
--- /dev/null
+++ b/vendor/github.com/shazow/rateio/writer.go
@@ -0,0 +1,25 @@
+package rateio
+
+import "io"
+
+type writer struct {
+ io.Writer
+ Limiter
+}
+
+// Write writes the contents of p into the buffer.
+// Returns ErrRateExceeded error if our specified read is exceeded.
+func (w *writer) Write(p []byte) (n int, err error) {
+ n, err = w.Writer.Write(p)
+ if err != nil {
+ return
+ }
+
+ err = w.Limiter.Count(n)
+ return
+}
+
+// NewWriter proxies an io.Writer but keeps track of bytes read based on our Limiter.
+func NewWriter(w io.Writer, limiter Limiter) io.Writer {
+ return &writer{w, limiter}
+}
diff --git a/vendor/github.com/shazow/ssh-chat/sshd/LICENSE b/vendor/github.com/shazow/ssh-chat/sshd/LICENSE
new file mode 100644
index 00000000..325b43a6
--- /dev/null
+++ b/vendor/github.com/shazow/ssh-chat/sshd/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2014 Andrey Petrov
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor/github.com/shazow/ssh-chat/sshd/auth.go b/vendor/github.com/shazow/ssh-chat/sshd/auth.go
new file mode 100644
index 00000000..2fc86fa8
--- /dev/null
+++ b/vendor/github.com/shazow/ssh-chat/sshd/auth.go
@@ -0,0 +1,72 @@
+package sshd
+
+import (
+ "crypto/sha256"
+ "encoding/base64"
+ "errors"
+ "net"
+
+ "golang.org/x/crypto/ssh"
+)
+
+// Auth is used to authenticate connections based on public keys.
+type Auth interface {
+ // Whether to allow connections without a public key.
+ AllowAnonymous() bool
+ // Given address and public key, return if the connection should be permitted.
+ Check(net.Addr, ssh.PublicKey) (bool, error)
+}
+
+// MakeAuth makes an ssh.ServerConfig which performs authentication against an Auth implementation.
+func MakeAuth(auth Auth) *ssh.ServerConfig {
+ config := ssh.ServerConfig{
+ NoClientAuth: false,
+ // Auth-related things should be constant-time to avoid timing attacks.
+ PublicKeyCallback: func(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
+ ok, err := auth.Check(conn.RemoteAddr(), key)
+ if !ok {
+ return nil, err
+ }
+ perm := &ssh.Permissions{Extensions: map[string]string{
+ "pubkey": string(key.Marshal()),
+ }}
+ return perm, nil
+ },
+ KeyboardInteractiveCallback: func(conn ssh.ConnMetadata, challenge ssh.KeyboardInteractiveChallenge) (*ssh.Permissions, error) {
+ if !auth.AllowAnonymous() {
+ return nil, errors.New("public key authentication required")
+ }
+ _, err := auth.Check(conn.RemoteAddr(), nil)
+ return nil, err
+ },
+ }
+
+ return &config
+}
+
+// MakeNoAuth makes a simple ssh.ServerConfig which allows all connections.
+// Primarily used for testing.
+func MakeNoAuth() *ssh.ServerConfig {
+ config := ssh.ServerConfig{
+ NoClientAuth: false,
+ // Auth-related things should be constant-time to avoid timing attacks.
+ PublicKeyCallback: func(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
+ perm := &ssh.Permissions{Extensions: map[string]string{
+ "pubkey": string(key.Marshal()),
+ }}
+ return perm, nil
+ },
+ KeyboardInteractiveCallback: func(conn ssh.ConnMetadata, challenge ssh.KeyboardInteractiveChallenge) (*ssh.Permissions, error) {
+ return nil, nil
+ },
+ }
+
+ return &config
+}
+
+// Fingerprint performs a SHA256 BASE64 fingerprint of the PublicKey, similar to OpenSSH.
+// See: https://anongit.mindrot.org/openssh.git/commit/?id=56d1c83cdd1ac
+func Fingerprint(k ssh.PublicKey) string {
+ hash := sha256.Sum256(k.Marshal())
+ return "SHA256:" + base64.StdEncoding.EncodeToString(hash[:])
+}
diff --git a/vendor/github.com/shazow/ssh-chat/sshd/client.go b/vendor/github.com/shazow/ssh-chat/sshd/client.go
new file mode 100644
index 00000000..004aa473
--- /dev/null
+++ b/vendor/github.com/shazow/ssh-chat/sshd/client.go
@@ -0,0 +1,76 @@
+package sshd
+
+import (
+ "crypto/rand"
+ "crypto/rsa"
+ "io"
+
+ "golang.org/x/crypto/ssh"
+)
+
+// NewRandomSigner generates a random key of a desired bit length.
+func NewRandomSigner(bits int) (ssh.Signer, error) {
+ key, err := rsa.GenerateKey(rand.Reader, bits)
+ if err != nil {
+ return nil, err
+ }
+ return ssh.NewSignerFromKey(key)
+}
+
+// NewClientConfig creates a barebones ssh.ClientConfig to be used with ssh.Dial.
+func NewClientConfig(name string) *ssh.ClientConfig {
+ return &ssh.ClientConfig{
+ User: name,
+ Auth: []ssh.AuthMethod{
+ ssh.KeyboardInteractive(func(user, instruction string, questions []string, echos []bool) (answers []string, err error) {
+ return
+ }),
+ },
+ HostKeyCallback: ssh.InsecureIgnoreHostKey(),
+ }
+}
+
+// ConnectShell makes a barebones SSH client session, used for testing.
+func ConnectShell(host string, name string, handler func(r io.Reader, w io.WriteCloser) error) error {
+ config := NewClientConfig(name)
+ conn, err := ssh.Dial("tcp", host, config)
+ if err != nil {
+ return err
+ }
+ defer conn.Close()
+
+ session, err := conn.NewSession()
+ if err != nil {
+ return err
+ }
+ defer session.Close()
+
+ in, err := session.StdinPipe()
+ if err != nil {
+ return err
+ }
+
+ out, err := session.StdoutPipe()
+ if err != nil {
+ return err
+ }
+
+ /*
+ err = session.RequestPty("xterm", 80, 40, ssh.TerminalModes{})
+ if err != nil {
+ return err
+ }
+ */
+
+ err = session.Shell()
+ if err != nil {
+ return err
+ }
+
+ _, err = session.SendRequest("ping", true, nil)
+ if err != nil {
+ return err
+ }
+
+ return handler(out, in)
+}
diff --git a/vendor/github.com/shazow/ssh-chat/sshd/doc.go b/vendor/github.com/shazow/ssh-chat/sshd/doc.go
new file mode 100644
index 00000000..21cd9142
--- /dev/null
+++ b/vendor/github.com/shazow/ssh-chat/sshd/doc.go
@@ -0,0 +1,34 @@
+package sshd
+
+/*
+
+ signer, err := ssh.ParsePrivateKey(privateKey)
+
+ config := MakeNoAuth()
+ config.AddHostKey(signer)
+
+ s, err := ListenSSH("0.0.0.0:2022", config)
+ if err != nil {
+ // Handle opening socket error
+ }
+ defer s.Close()
+
+ terminals := s.ServeTerminal()
+
+ for term := range terminals {
+ go func() {
+ defer term.Close()
+ term.SetPrompt("...")
+ term.AutoCompleteCallback = nil // ...
+
+ for {
+ line, err := term.ReadLine()
+ if err != nil {
+ break
+ }
+ term.Write(...)
+ }
+
+ }()
+ }
+*/
diff --git a/vendor/github.com/shazow/ssh-chat/sshd/logger.go b/vendor/github.com/shazow/ssh-chat/sshd/logger.go
new file mode 100644
index 00000000..9f6998f0
--- /dev/null
+++ b/vendor/github.com/shazow/ssh-chat/sshd/logger.go
@@ -0,0 +1,22 @@
+package sshd
+
+import "io"
+import stdlog "log"
+
+var logger *stdlog.Logger
+
+func SetLogger(w io.Writer) {
+ flags := stdlog.Flags()
+ prefix := "[sshd] "
+ logger = stdlog.New(w, prefix, flags)
+}
+
+type nullWriter struct{}
+
+func (nullWriter) Write(data []byte) (int, error) {
+ return len(data), nil
+}
+
+func init() {
+ SetLogger(nullWriter{})
+}
diff --git a/vendor/github.com/shazow/ssh-chat/sshd/net.go b/vendor/github.com/shazow/ssh-chat/sshd/net.go
new file mode 100644
index 00000000..1b3f4028
--- /dev/null
+++ b/vendor/github.com/shazow/ssh-chat/sshd/net.go
@@ -0,0 +1,67 @@
+package sshd
+
+import (
+ "net"
+
+ "github.com/shazow/rateio"
+ "golang.org/x/crypto/ssh"
+)
+
+// Container for the connection and ssh-related configuration
+type SSHListener struct {
+ net.Listener
+ config *ssh.ServerConfig
+
+ RateLimit func() rateio.Limiter
+ HandlerFunc func(term *Terminal)
+}
+
+// Make an SSH listener socket
+func ListenSSH(laddr string, config *ssh.ServerConfig) (*SSHListener, error) {
+ socket, err := net.Listen("tcp", laddr)
+ if err != nil {
+ return nil, err
+ }
+ l := SSHListener{Listener: socket, config: config}
+ return &l, nil
+}
+
+func (l *SSHListener) handleConn(conn net.Conn) (*Terminal, error) {
+ if l.RateLimit != nil {
+ // TODO: Configurable Limiter?
+ conn = ReadLimitConn(conn, l.RateLimit())
+ }
+
+ // Upgrade TCP connection to SSH connection
+ sshConn, channels, requests, err := ssh.NewServerConn(conn, l.config)
+ if err != nil {
+ return nil, err
+ }
+
+ // FIXME: Disconnect if too many faulty requests? (Avoid DoS.)
+ go ssh.DiscardRequests(requests)
+ return NewSession(sshConn, channels)
+}
+
+// Accept incoming connections as terminal requests and yield them
+func (l *SSHListener) Serve() {
+ defer l.Close()
+ for {
+ conn, err := l.Accept()
+
+ if err != nil {
+ logger.Printf("Failed to accept connection: %s", err)
+ break
+ }
+
+ // Goroutineify to resume accepting sockets early
+ go func() {
+ term, err := l.handleConn(conn)
+ if err != nil {
+ logger.Printf("[%s] Failed to handshake: %s", conn.RemoteAddr(), err)
+ return
+ }
+ l.HandlerFunc(term)
+ }()
+ }
+}
diff --git a/vendor/github.com/shazow/ssh-chat/sshd/pty.go b/vendor/github.com/shazow/ssh-chat/sshd/pty.go
new file mode 100644
index 00000000..4443fd33
--- /dev/null
+++ b/vendor/github.com/shazow/ssh-chat/sshd/pty.go
@@ -0,0 +1,70 @@
+package sshd
+
+import "encoding/binary"
+
+// Helpers below are borrowed from go.crypto circa 2011:
+
+// parsePtyRequest parses the payload of the pty-req message and extracts the
+// dimensions of the terminal. See RFC 4254, section 6.2.
+func parsePtyRequest(s []byte) (width, height int, ok bool) {
+ _, s, ok = parseString(s)
+ if !ok {
+ return
+ }
+ width32, s, ok := parseUint32(s)
+ if !ok {
+ return
+ }
+ height32, _, ok := parseUint32(s)
+ width = int(width32)
+ height = int(height32)
+ if width < 1 {
+ ok = false
+ }
+ if height < 1 {
+ ok = false
+ }
+ return
+}
+
+func parseWinchRequest(s []byte) (width, height int, ok bool) {
+ width32, _, ok := parseUint32(s)
+ if !ok {
+ return
+ }
+ height32, _, ok := parseUint32(s)
+ if !ok {
+ return
+ }
+
+ width = int(width32)
+ height = int(height32)
+ if width < 1 {
+ ok = false
+ }
+ if height < 1 {
+ ok = false
+ }
+ return
+}
+
+func parseString(in []byte) (out string, rest []byte, ok bool) {
+ if len(in) < 4 {
+ return
+ }
+ length := binary.BigEndian.Uint32(in)
+ if uint32(len(in)) < 4+length {
+ return
+ }
+ out = string(in[4 : 4+length])
+ rest = in[4+length:]
+ ok = true
+ return
+}
+
+func parseUint32(in []byte) (uint32, []byte, bool) {
+ if len(in) < 4 {
+ return 0, nil, false
+ }
+ return binary.BigEndian.Uint32(in), in[4:], true
+}
diff --git a/vendor/github.com/shazow/ssh-chat/sshd/ratelimit.go b/vendor/github.com/shazow/ssh-chat/sshd/ratelimit.go
new file mode 100644
index 00000000..b2607e6d
--- /dev/null
+++ b/vendor/github.com/shazow/ssh-chat/sshd/ratelimit.go
@@ -0,0 +1,71 @@
+package sshd
+
+import (
+ "io"
+ "net"
+ "time"
+
+ "github.com/shazow/rateio"
+)
+
+type limitedConn struct {
+ net.Conn
+ io.Reader // Our rate-limited io.Reader for net.Conn
+}
+
+func (r *limitedConn) Read(p []byte) (n int, err error) {
+ return r.Reader.Read(p)
+}
+
+// ReadLimitConn returns a net.Conn whose io.Reader interface is rate-limited by limiter.
+func ReadLimitConn(conn net.Conn, limiter rateio.Limiter) net.Conn {
+ return &limitedConn{
+ Conn: conn,
+ Reader: rateio.NewReader(conn, limiter),
+ }
+}
+
+// Count each read as 1 unless it exceeds some number of bytes.
+type inputLimiter struct {
+ // TODO: Could do all kinds of fancy things here, like be more forgiving of
+ // connections that have been around for a while.
+
+ Amount int
+ Frequency time.Duration
+
+ remaining int
+ readCap int
+ numRead int
+ timeRead time.Time
+}
+
+// NewInputLimiter returns a rateio.Limiter with sensible defaults for
+// differentiating between humans typing and bots spamming.
+func NewInputLimiter() rateio.Limiter {
+ grace := time.Second * 3
+ return &inputLimiter{
+ Amount: 2 << 14, // ~16kb, should be plenty for a high typing rate/copypasta/large key handshakes.
+ Frequency: time.Minute * 1,
+ readCap: 128, // Allow up to 128 bytes per read (anecdotally, 1 character = 52 bytes over ssh)
+ numRead: -1024 * 1024, // Start with a 1mb grace
+ timeRead: time.Now().Add(grace),
+ }
+}
+
+// Count applies 1 if n<readCap, else n
+func (limit *inputLimiter) Count(n int) error {
+ now := time.Now()
+ if now.After(limit.timeRead) {
+ limit.numRead = 0
+ limit.timeRead = now.Add(limit.Frequency)
+ }
+ if n <= limit.readCap {
+ limit.numRead += 1
+ } else {
+ limit.numRead += n
+ }
+ if limit.numRead > limit.Amount {
+ return rateio.ErrRateExceeded
+ }
+ return nil
+}
diff --git a/vendor/github.com/shazow/ssh-chat/sshd/terminal.go b/vendor/github.com/shazow/ssh-chat/sshd/terminal.go
new file mode 100644
index 00000000..977e146e
--- /dev/null
+++ b/vendor/github.com/shazow/ssh-chat/sshd/terminal.go
@@ -0,0 +1,167 @@
+package sshd
+
+import (
+ "errors"
+ "fmt"
+ "net"
+ "sync"
+ "time"
+
+ "golang.org/x/crypto/ssh"
+ "golang.org/x/crypto/ssh/terminal"
+)
+
+var keepaliveInterval = time.Second * 30
+var keepaliveRequest = "keepalive@ssh-chat"
+
+var ErrNoSessionChannel = errors.New("no session channel")
+var ErrNotSessionChannel = errors.New("terminal requires session channel")
+
+// Connection is an interface with fields necessary to operate an sshd host.
+type Connection interface {
+ PublicKey() ssh.PublicKey
+ RemoteAddr() net.Addr
+ Name() string
+ ClientVersion() []byte
+ Close() error
+}
+
+type sshConn struct {
+ *ssh.ServerConn
+}
+
+func (c sshConn) PublicKey() ssh.PublicKey {
+ if c.Permissions == nil {
+ return nil
+ }
+
+ s, ok := c.Permissions.Extensions["pubkey"]
+ if !ok {
+ return nil
+ }
+
+ key, err := ssh.ParsePublicKey([]byte(s))
+ if err != nil {
+ return nil
+ }
+
+ return key
+}
+
+func (c sshConn) Name() string {
+ return c.User()
+}
+
+// Extending ssh/terminal to include a closer interface
+type Terminal struct {
+ terminal.Terminal
+ Conn Connection
+ Channel ssh.Channel
+
+ done chan struct{}
+ closeOnce sync.Once
+}
+
+// Make new terminal from a session channel
+func NewTerminal(conn *ssh.ServerConn, ch ssh.NewChannel) (*Terminal, error) {
+ if ch.ChannelType() != "session" {
+ return nil, ErrNotSessionChannel
+ }
+ channel, requests, err := ch.Accept()
+ if err != nil {
+ return nil, err
+ }
+ term := Terminal{
+ Terminal: *terminal.NewTerminal(channel, "Connecting..."),
+ Conn: sshConn{conn},
+ Channel: channel,
+
+ done: make(chan struct{}),
+ }
+
+ go term.listen(requests)
+
+ go func() {
+ // Keep-Alive Ticker
+ ticker := time.Tick(keepaliveInterval)
+ for {
+ select {
+ case <-ticker:
+ _, err := channel.SendRequest(keepaliveRequest, true, nil)
+ if err != nil {
+ // Connection is gone
+ logger.Printf("[%s] Keepalive failed, closing terminal: %s", term.Conn.RemoteAddr(), err)
+ term.Close()
+ return
+ }
+ case <-term.done:
+ return
+ }
+ }
+ }()
+
+ return &term, nil
+}
+
+// Find session channel and make a Terminal from it
+func NewSession(conn *ssh.ServerConn, channels <-chan ssh.NewChannel) (*Terminal, error) {
+ // Make a terminal from the first session found
+ for ch := range channels {
+ if t := ch.ChannelType(); t != "session" {
+ logger.Printf("[%s] Ignored channel type: %s", conn.RemoteAddr(), t)
+ ch.Reject(ssh.UnknownChannelType, fmt.Sprintf("unknown channel type: %s", t))
+ continue
+ }
+
+ return NewTerminal(conn, ch)
+ }
+
+ return nil, ErrNoSessionChannel
+}
+
+// Close terminal and ssh connection
+func (t *Terminal) Close() error {
+ var err error
+ t.closeOnce.Do(func() {
+ close(t.done)
+ t.Channel.Close()
+ err = t.Conn.Close()
+ })
+ return err
+}
+
+// Negotiate terminal type and settings
+func (t *Terminal) listen(requests <-chan *ssh.Request) {
+ hasShell := false
+
+ for req := range requests {
+ var width, height int
+ var ok bool
+
+ switch req.Type {
+ case "shell":
+ if !hasShell {
+ ok = true
+ hasShell = true
+ }
+ case "pty-req":
+ width, height, ok = parsePtyRequest(req.Payload)
+ if ok {
+ // TODO: Hardcode width to 100000?
+ err := t.SetSize(width, height)
+ ok = err == nil
+ }
+ case "window-change":
+ width, height, ok = parseWinchRequest(req.Payload)
+ if ok {
+ // TODO: Hardcode width to 100000?
+ err := t.SetSize(width, height)
+ ok = err == nil
+ }
+ }
+
+ if req.WantReply {
+ req.Reply(ok, nil)
+ }
+ }
+}