diff options
Diffstat (limited to 'vendor/github.com')
-rw-r--r-- | vendor/github.com/shazow/rateio/LICENSE | 21 | ||||
-rw-r--r-- | vendor/github.com/shazow/rateio/doc.go | 29 | ||||
-rw-r--r-- | vendor/github.com/shazow/rateio/limiter.go | 62 | ||||
-rw-r--r-- | vendor/github.com/shazow/rateio/reader.go | 25 | ||||
-rw-r--r-- | vendor/github.com/shazow/rateio/writer.go | 25 | ||||
-rw-r--r-- | vendor/github.com/shazow/ssh-chat/sshd/LICENSE | 21 | ||||
-rw-r--r-- | vendor/github.com/shazow/ssh-chat/sshd/auth.go | 72 | ||||
-rw-r--r-- | vendor/github.com/shazow/ssh-chat/sshd/client.go | 76 | ||||
-rw-r--r-- | vendor/github.com/shazow/ssh-chat/sshd/doc.go | 34 | ||||
-rw-r--r-- | vendor/github.com/shazow/ssh-chat/sshd/logger.go | 22 | ||||
-rw-r--r-- | vendor/github.com/shazow/ssh-chat/sshd/net.go | 67 | ||||
-rw-r--r-- | vendor/github.com/shazow/ssh-chat/sshd/pty.go | 70 | ||||
-rw-r--r-- | vendor/github.com/shazow/ssh-chat/sshd/ratelimit.go | 71 | ||||
-rw-r--r-- | vendor/github.com/shazow/ssh-chat/sshd/terminal.go | 167 |
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) + } + } +} |