diff options
Diffstat (limited to 'teleirc/matterbridge/vendor/github.com/shazow')
25 files changed, 2411 insertions, 0 deletions
diff --git a/teleirc/matterbridge/vendor/github.com/shazow/rateio/LICENSE b/teleirc/matterbridge/vendor/github.com/shazow/rateio/LICENSE new file mode 100644 index 0000000..1901179 --- /dev/null +++ b/teleirc/matterbridge/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/teleirc/matterbridge/vendor/github.com/shazow/rateio/doc.go b/teleirc/matterbridge/vendor/github.com/shazow/rateio/doc.go new file mode 100644 index 0000000..1c5e085 --- /dev/null +++ b/teleirc/matterbridge/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/teleirc/matterbridge/vendor/github.com/shazow/rateio/limiter.go b/teleirc/matterbridge/vendor/github.com/shazow/rateio/limiter.go new file mode 100644 index 0000000..a18ce3c --- /dev/null +++ b/teleirc/matterbridge/vendor/github.com/shazow/rateio/limiter.go @@ -0,0 +1,71 @@ +package rateio + +import ( + "errors" + "time" +) + +const minInt = -int(^uint(0)>>1) - 1 + +// ErrRateExceeded is 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) *simpleLimiter { + 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) *simpleLimiter { + 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 +} + +// Delay returns a channel that can be used to block until next window +func (limit *simpleLimiter) Delay() <-chan time.Time { + waitTill := time.Now() + if limit.numRead >= limit.Amount { + waitTill = waitTill.Add(limit.Frequency) + } + return time.NewTimer(time.Until(waitTill)).C +} diff --git a/teleirc/matterbridge/vendor/github.com/shazow/rateio/reader.go b/teleirc/matterbridge/vendor/github.com/shazow/rateio/reader.go new file mode 100644 index 0000000..56bd8ff --- /dev/null +++ b/teleirc/matterbridge/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/teleirc/matterbridge/vendor/github.com/shazow/rateio/writer.go b/teleirc/matterbridge/vendor/github.com/shazow/rateio/writer.go new file mode 100644 index 0000000..d4feea4 --- /dev/null +++ b/teleirc/matterbridge/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/teleirc/matterbridge/vendor/github.com/shazow/ssh-chat/LICENSE b/teleirc/matterbridge/vendor/github.com/shazow/ssh-chat/LICENSE new file mode 100644 index 0000000..325b43a --- /dev/null +++ b/teleirc/matterbridge/vendor/github.com/shazow/ssh-chat/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/teleirc/matterbridge/vendor/github.com/shazow/ssh-chat/NOTICE b/teleirc/matterbridge/vendor/github.com/shazow/ssh-chat/NOTICE new file mode 100644 index 0000000..bde874c --- /dev/null +++ b/teleirc/matterbridge/vendor/github.com/shazow/ssh-chat/NOTICE @@ -0,0 +1,33 @@ +## x/crypto/ssh/terminal + +This project contains a fork of https://github.com/golang/crypto/tree/master/ssh/terminal +under the sshd/terminal directory. The project's original license applies: + + +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/teleirc/matterbridge/vendor/github.com/shazow/ssh-chat/internal/sanitize/sanitize.go b/teleirc/matterbridge/vendor/github.com/shazow/ssh-chat/internal/sanitize/sanitize.go new file mode 100644 index 0000000..4f9775f --- /dev/null +++ b/teleirc/matterbridge/vendor/github.com/shazow/ssh-chat/internal/sanitize/sanitize.go @@ -0,0 +1,29 @@ +package sanitize + +import "regexp" + +var ( + reStripName = regexp.MustCompile("[^\\w.-]") + reStripData = regexp.MustCompile("[^[:ascii:]]|[[:cntrl:]]") +) + +const maxLength = 16 + +// Name returns a name with only allowed characters and a reasonable length +func Name(s string) string { + s = reStripName.ReplaceAllString(s, "") + nameLength := maxLength + if len(s) <= maxLength { + nameLength = len(s) + } + s = s[:nameLength] + return s +} + +// Data returns a string with only allowed characters for client-provided metadata inputs. +func Data(s string, maxlen int) string { + if len(s) > maxlen { + s = s[:maxlen] + } + return reStripData.ReplaceAllString(s, "") +} diff --git a/teleirc/matterbridge/vendor/github.com/shazow/ssh-chat/sshd/auth.go b/teleirc/matterbridge/vendor/github.com/shazow/ssh-chat/sshd/auth.go new file mode 100644 index 0000000..a140413 --- /dev/null +++ b/teleirc/matterbridge/vendor/github.com/shazow/ssh-chat/sshd/auth.go @@ -0,0 +1,74 @@ +package sshd + +import ( + "crypto/sha256" + "encoding/base64" + "errors" + "net" + + "github.com/shazow/ssh-chat/internal/sanitize" + "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 and client agent string, returns nil if the connection should be allowed. + Check(net.Addr, ssh.PublicKey, string) error +} + +// MakeAuth makes an ssh.ServerConfig which performs authentication against an Auth implementation. +// TODO: Switch to using ssh.AuthMethod instead? +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) { + err := auth.Check(conn.RemoteAddr(), key, sanitize.Data(string(conn.ClientVersion()), 64)) + if err != nil { + 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, sanitize.Data(string(conn.ClientVersion()), 64)) + 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/teleirc/matterbridge/vendor/github.com/shazow/ssh-chat/sshd/client.go b/teleirc/matterbridge/vendor/github.com/shazow/ssh-chat/sshd/client.go new file mode 100644 index 0000000..004aa47 --- /dev/null +++ b/teleirc/matterbridge/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/teleirc/matterbridge/vendor/github.com/shazow/ssh-chat/sshd/doc.go b/teleirc/matterbridge/vendor/github.com/shazow/ssh-chat/sshd/doc.go new file mode 100644 index 0000000..21cd914 --- /dev/null +++ b/teleirc/matterbridge/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/teleirc/matterbridge/vendor/github.com/shazow/ssh-chat/sshd/logger.go b/teleirc/matterbridge/vendor/github.com/shazow/ssh-chat/sshd/logger.go new file mode 100644 index 0000000..c4ebc04 --- /dev/null +++ b/teleirc/matterbridge/vendor/github.com/shazow/ssh-chat/sshd/logger.go @@ -0,0 +1,23 @@ +package sshd + +import "io" +import stdlog "log" + +var logger *stdlog.Logger + +// SetLogger sets the package logging output to use w. +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/teleirc/matterbridge/vendor/github.com/shazow/ssh-chat/sshd/net.go b/teleirc/matterbridge/vendor/github.com/shazow/ssh-chat/sshd/net.go new file mode 100644 index 0000000..678454b --- /dev/null +++ b/teleirc/matterbridge/vendor/github.com/shazow/ssh-chat/sshd/net.go @@ -0,0 +1,75 @@ +package sshd + +import ( + "net" + "time" + + "github.com/shazow/rateio" + "golang.org/x/crypto/ssh" +) + +// SSHListener is the container for the connection and ssh-related configuration +type SSHListener struct { + net.Listener + config *ssh.ServerConfig + + RateLimit func() rateio.Limiter + HandlerFunc func(term *Terminal) +} + +// ListenSSH makes 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()) + } + + // If the connection doesn't write anything back for too long before we get + // a valid session, it should be dropped. + var handleTimeout = 20 * time.Second + conn.SetReadDeadline(time.Now().Add(handleTimeout)) + defer conn.SetReadDeadline(time.Time{}) + + // 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) +} + +// Serve Accepts 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) + conn.Close() // Must be closed to avoid a leak + return + } + l.HandlerFunc(term) + }() + } +} diff --git a/teleirc/matterbridge/vendor/github.com/shazow/ssh-chat/sshd/pty.go b/teleirc/matterbridge/vendor/github.com/shazow/ssh-chat/sshd/pty.go new file mode 100644 index 0000000..d3ed9ca --- /dev/null +++ b/teleirc/matterbridge/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) (term string, width, height int, ok bool) { + term, 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/teleirc/matterbridge/vendor/github.com/shazow/ssh-chat/sshd/ratelimit.go b/teleirc/matterbridge/vendor/github.com/shazow/ssh-chat/sshd/ratelimit.go new file mode 100644 index 0000000..b2607e6 --- /dev/null +++ b/teleirc/matterbridge/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/teleirc/matterbridge/vendor/github.com/shazow/ssh-chat/sshd/terminal.go b/teleirc/matterbridge/vendor/github.com/shazow/ssh-chat/sshd/terminal.go new file mode 100644 index 0000000..e8d9901 --- /dev/null +++ b/teleirc/matterbridge/vendor/github.com/shazow/ssh-chat/sshd/terminal.go @@ -0,0 +1,246 @@ +package sshd + +import ( + "errors" + "fmt" + "net" + "sync" + "time" + + "github.com/shazow/ssh-chat/sshd/terminal" + "golang.org/x/crypto/ssh" +) + +var keepaliveInterval = time.Second * 30 +var keepaliveRequest = "keepalive@ssh-chat" + +// ErrNoSessionChannel is returned when there is no session channel. +var ErrNoSessionChannel = errors.New("no session channel") + +// ErrNotSessionChannel is returned when a channel is not a 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() +} + +// EnvVar is an environment variable key-value pair +type EnvVar struct { + Key string + Value string +} + +func (v EnvVar) String() string { + return v.Key + "=" + v.Value +} + +// Env is a wrapper type around []EnvVar with some helper methods +type Env []EnvVar + +// Get returns the latest value for a given key, or empty string if not found +func (e Env) Get(key string) string { + for i := len(e) - 1; i >= 0; i-- { + if e[i].Key == key { + return e[i].Value + } + } + return "" +} + +// Terminal extends ssh/terminal to include a close method +type Terminal struct { + terminal.Terminal + Conn Connection + Channel ssh.Channel + + done chan struct{} + closeOnce sync.Once + + mu sync.Mutex + env []EnvVar + term string +} + +// Make new terminal from a session channel +// TODO: For v2, make a separate `Serve(ctx context.Context) error` method to activate the Terminal +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, ""), + Conn: sshConn{conn}, + Channel: channel, + + done: make(chan struct{}), + } + + ready := make(chan struct{}) + go term.listen(requests, ready) + + 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 + } + } + }() + + // We need to wait for term.ready to acquire a shell before we return, this + // gives the SSH session a chance to populate the env vars and other state. + // TODO: Make the timeout configurable + // TODO: Use context.Context for abort/timeout in the future, will need to change the API. + select { + case <-ready: // shell acquired + return &term, nil + case <-term.done: + return nil, errors.New("terminal aborted") + case <-time.NewTimer(time.Minute).C: + return nil, errors.New("timed out starting terminal") + } +} + +// NewSession Finds a 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 +} + +// listen negotiates the terminal type and state +// ready is closed when the terminal is ready. +func (t *Terminal) listen(requests <-chan *ssh.Request, ready chan<- struct{}) { + hasShell := false + + for req := range requests { + var width, height int + var ok bool + + switch req.Type { + case "shell": + if !hasShell { + ok = true + hasShell = true + close(ready) + } + case "pty-req": + var term string + term, width, height, ok = parsePtyRequest(req.Payload) + if ok { + // TODO: Hardcode width to 100000? + err := t.SetSize(width, height) + ok = err == nil + // Save the term: + t.mu.Lock() + t.term = term + t.mu.Unlock() + } + case "window-change": + width, height, ok = parseWinchRequest(req.Payload) + if ok { + // TODO: Hardcode width to 100000? + err := t.SetSize(width, height) + ok = err == nil + } + case "env": + var v EnvVar + if err := ssh.Unmarshal(req.Payload, &v); err == nil { + t.mu.Lock() + t.env = append(t.env, v) + t.mu.Unlock() + ok = true + } + } + + if req.WantReply { + req.Reply(ok, nil) + } + } +} + +// Env returns a list of environment key-values that have been set. They are +// returned in the order that they have been set, there is no deduplication or +// other pre-processing applied. +func (t *Terminal) Env() Env { + t.mu.Lock() + defer t.mu.Unlock() + return Env(t.env) +} + +// Term returns the terminal string value as set by the pty. +// If there was no pty request, it falls back to the TERM value passed in as an +// Env variable. +func (t *Terminal) Term() string { + t.mu.Lock() + defer t.mu.Unlock() + if t.term != "" { + return t.term + } + return Env(t.env).Get("TERM") +} diff --git a/teleirc/matterbridge/vendor/github.com/shazow/ssh-chat/sshd/terminal/LICENSE b/teleirc/matterbridge/vendor/github.com/shazow/ssh-chat/sshd/terminal/LICENSE new file mode 100644 index 0000000..6a66aea --- /dev/null +++ b/teleirc/matterbridge/vendor/github.com/shazow/ssh-chat/sshd/terminal/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/teleirc/matterbridge/vendor/github.com/shazow/ssh-chat/sshd/terminal/terminal.go b/teleirc/matterbridge/vendor/github.com/shazow/ssh-chat/sshd/terminal/terminal.go new file mode 100644 index 0000000..ec04e8f --- /dev/null +++ b/teleirc/matterbridge/vendor/github.com/shazow/ssh-chat/sshd/terminal/terminal.go @@ -0,0 +1,1027 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package terminal + +import ( + "bytes" + "io" + "strconv" + "sync" + "unicode/utf8" + + "golang.org/x/text/width" +) + +// EscapeCodes contains escape sequences that can be written to the terminal in +// order to achieve different styles of text. +type EscapeCodes struct { + // Foreground colors + Black, Red, Green, Yellow, Blue, Magenta, Cyan, White []byte + + // Reset all attributes + Reset []byte +} + +var vt100EscapeCodes = EscapeCodes{ + Black: []byte{keyEscape, '[', '3', '0', 'm'}, + Red: []byte{keyEscape, '[', '3', '1', 'm'}, + Green: []byte{keyEscape, '[', '3', '2', 'm'}, + Yellow: []byte{keyEscape, '[', '3', '3', 'm'}, + Blue: []byte{keyEscape, '[', '3', '4', 'm'}, + Magenta: []byte{keyEscape, '[', '3', '5', 'm'}, + Cyan: []byte{keyEscape, '[', '3', '6', 'm'}, + White: []byte{keyEscape, '[', '3', '7', 'm'}, + + Reset: []byte{keyEscape, '[', '0', 'm'}, +} + +// Terminal contains the state for running a VT100 terminal that is capable of +// reading lines of input. +type Terminal struct { + // AutoCompleteCallback, if non-null, is called for each keypress with + // the full input line and the current position of the cursor (in + // bytes, as an index into |line|). If it returns ok=false, the key + // press is processed normally. Otherwise it returns a replacement line + // and the new cursor position. + AutoCompleteCallback func(line string, pos int, key rune) (newLine string, newPos int, ok bool) + + // Escape contains a pointer to the escape codes for this terminal. + // It's always a valid pointer, although the escape codes themselves + // may be empty if the terminal doesn't support them. + Escape *EscapeCodes + + // lock protects the terminal and the state in this object from + // concurrent processing of a key press and a Write() call. + lock sync.Mutex + + c io.ReadWriter + prompt []rune + + // line is the current line being entered. + line []rune + // pos is the logical position of the cursor in line + pos int + // echo is true if local echo is enabled + echo bool + // pasteActive is true iff there is a bracketed paste operation in + // progress. + pasteActive bool + + // cursorX contains the current X value of the cursor where the left + // edge is 0. cursorY contains the row number where the first row of + // the current line is 0. + cursorX, cursorY int + // maxLine is the greatest value of cursorY so far. + maxLine int + + termWidth, termHeight int + + // outBuf contains the terminal data to be sent. + outBuf []byte + // remainder contains the remainder of any partial key sequences after + // a read. It aliases into inBuf. + remainder []byte + inBuf [256]byte + + // history contains previously entered commands so that they can be + // accessed with the up and down keys. + history stRingBuffer + // historyIndex stores the currently accessed history entry, where zero + // means the immediately previous entry. + historyIndex int + // When navigating up and down the history it's possible to return to + // the incomplete, initial line. That value is stored in + // historyPending. + historyPending string + + // enterClear will clear the input line on enter, instead of printing a + // new line after the input. It's useful for replacing the input with + // something else without echoing it. + enterClear bool +} + +// NewTerminal runs a VT100 terminal on the given ReadWriter. If the ReadWriter is +// a local terminal, that terminal must first have been put into raw mode. +// prompt is a string that is written at the start of each input line (i.e. +// "> "). +func NewTerminal(c io.ReadWriter, prompt string) *Terminal { + return &Terminal{ + Escape: &vt100EscapeCodes, + c: c, + prompt: []rune(prompt), + termWidth: 80, + termHeight: 24, + echo: true, + historyIndex: -1, + } +} + +const ( + keyCtrlD = 4 + keyCtrlU = 21 + keyEnter = '\r' + keyEscape = 27 + keyBackspace = 127 + keyUnknown = 0xd800 /* UTF-16 surrogate area */ + iota + keyUp + keyDown + keyLeft + keyRight + keyAltLeft + keyAltRight + keyAltF + keyAltB + keyHome + keyEnd + keyDeleteWord + keyDeleteLine + keyClearScreen + keyPasteStart + keyPasteEnd +) + +var ( + crlf = []byte{'\r', '\n'} + pasteStart = []byte{keyEscape, '[', '2', '0', '0', '~'} + pasteEnd = []byte{keyEscape, '[', '2', '0', '1', '~'} +) + +// bytesToKey tries to parse a key sequence from b. If successful, it returns +// the key and the remainder of the input. Otherwise it returns utf8.RuneError. +func bytesToKey(b []byte, pasteActive bool) (rune, []byte) { + if len(b) == 0 { + return utf8.RuneError, nil + } + + if !pasteActive { + switch b[0] { + case 1: // ^A + return keyHome, b[1:] + case 2: // ^B + return keyLeft, b[1:] + case 5: // ^E + return keyEnd, b[1:] + case 6: // ^F + return keyRight, b[1:] + case 8: // ^H + return keyBackspace, b[1:] + case 11: // ^K + return keyDeleteLine, b[1:] + case 12: // ^L + return keyClearScreen, b[1:] + case 23: // ^W + return keyDeleteWord, b[1:] + case 14: // ^N + return keyDown, b[1:] + case 16: // ^P + return keyUp, b[1:] + } + } + + if b[0] != keyEscape { + if !utf8.FullRune(b) { + return utf8.RuneError, b + } + r, l := utf8.DecodeRune(b) + return r, b[l:] + } + + if !pasteActive && len(b) >= 3 && b[0] == keyEscape && b[1] == '[' { + switch b[2] { + case 'A': + return keyUp, b[3:] + case 'B': + return keyDown, b[3:] + case 'C': + return keyRight, b[3:] + case 'D': + return keyLeft, b[3:] + case 'H': + return keyHome, b[3:] + case 'F': + return keyEnd, b[3:] + } + } + + if !pasteActive && len(b) >= 6 && b[0] == keyEscape && b[1] == '[' && b[2] == '1' && b[3] == ';' && b[4] == '3' { + switch b[5] { + case 'C': + return keyAltRight, b[6:] + case 'D': + return keyAltLeft, b[6:] + } + } + + if !pasteActive && len(b) >= 2 && b[0] == keyEscape { + switch b[1] { + case 'f': + return keyAltF, b[2:] + case 'b': + return keyAltB, b[2:] + } + } + + if !pasteActive && len(b) >= 6 && bytes.Equal(b[:6], pasteStart) { + return keyPasteStart, b[6:] + } + + if pasteActive && len(b) >= 6 && bytes.Equal(b[:6], pasteEnd) { + return keyPasteEnd, b[6:] + } + + // If we get here then we have a key that we don't recognise, or a + // partial sequence. It's not clear how one should find the end of a + // sequence without knowing them all, but it seems that [a-zA-Z~] only + // appears at the end of a sequence. + for i, c := range b[0:] { + if c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c == '~' { + return keyUnknown, b[i+1:] + } + } + + return utf8.RuneError, b +} + +// queue appends data to the end of t.outBuf +func (t *Terminal) queue(data []rune) { + t.outBuf = append(t.outBuf, []byte(string(data))...) +} + +var eraseUnderCursor = []rune{' ', keyEscape, '[', 'D'} +var space = []rune{' '} + +func isPrintable(key rune) bool { + isInSurrogateArea := key >= 0xd800 && key <= 0xdbff + return key >= 32 && !isInSurrogateArea +} + +// moveCursorToPos appends data to t.outBuf which will move the cursor to the +// given, logical position in the text. +func (t *Terminal) moveCursorToPos(pos int) { + if !t.echo { + return + } + + x := visualLength(t.prompt) + visualLength(t.line[:pos]) + y := x / t.termWidth + x = x % t.termWidth + + up := 0 + if y < t.cursorY { + up = t.cursorY - y + } + + down := 0 + if y > t.cursorY { + down = y - t.cursorY + } + + left := 0 + if x < t.cursorX { + left = t.cursorX - x + } + + right := 0 + if x > t.cursorX { + right = x - t.cursorX + } + + t.cursorX = x + t.cursorY = y + t.move(up, down, left, right) +} + +func (t *Terminal) move(up, down, left, right int) { + m := []rune{} + + // 1 unit up can be expressed as ^[A + // 5 units up can be expressed as ^[[5A + + if up == 1 { + m = append(m, keyEscape, '[', 'A') + } else if up > 1 { + m = append(m, keyEscape, '[') + m = append(m, []rune(strconv.Itoa(up))...) + m = append(m, 'A') + } + + if down == 1 { + m = append(m, keyEscape, '[', 'B') + } else if down > 1 { + m = append(m, keyEscape, '[') + m = append(m, []rune(strconv.Itoa(down))...) + m = append(m, 'B') + } + + if right == 1 { + m = append(m, keyEscape, '[', 'C') + } else if right > 1 { + m = append(m, keyEscape, '[') + m = append(m, []rune(strconv.Itoa(right))...) + m = append(m, 'C') + } + + if left == 1 { + m = append(m, keyEscape, '[', 'D') + } else if left > 1 { + m = append(m, keyEscape, '[') + m = append(m, []rune(strconv.Itoa(left))...) + m = append(m, 'D') + } + + t.queue(m) +} + +func (t *Terminal) clearLineToRight() { + op := []rune{keyEscape, '[', 'K'} + t.queue(op) +} + +func (t *Terminal) clearScreenBelow() { + op := []rune{keyEscape, '[', 'J'} + t.queue(op) +} + +const maxLineLength = 4096 + +func (t *Terminal) setLine(newLine []rune, newPos int) { + if t.echo { + t.moveCursorToPos(0) + t.writeLine(newLine) + for i := len(newLine); i < len(t.line); i++ { + t.writeLine(space) + } + t.line = newLine + t.moveCursorToPos(newPos) + } + t.line = newLine + t.pos = newPos +} + +func (t *Terminal) advanceCursor(places int) { + t.cursorX += places + t.cursorY += t.cursorX / t.termWidth + if t.cursorY > t.maxLine { + t.maxLine = t.cursorY + } + t.cursorX = t.cursorX % t.termWidth + + if places > 0 && t.cursorX == 0 { + // Normally terminals will advance the current position + // when writing a character. But that doesn't happen + // for the last character in a line. However, when + // writing a character (except a new line) that causes + // a line wrap, the position will be advanced two + // places. + // + // So, if we are stopping at the end of a line, we + // need to write a newline so that our cursor can be + // advanced to the next line. + t.outBuf = append(t.outBuf, '\r', '\n') + } +} + +func (t *Terminal) eraseNPreviousChars(n int) { + if n == 0 { + return + } + + if t.pos < n { + n = t.pos + } + t.pos -= n + t.moveCursorToPos(t.pos) + + copy(t.line[t.pos:], t.line[n+t.pos:]) + t.line = t.line[:len(t.line)-n] + if t.echo { + t.writeLine(t.line[t.pos:]) + for i := 0; i < n; i++ { + t.queue(space) + } + t.advanceCursor(n) + t.moveCursorToPos(t.pos) + } +} + +// countToLeftWord returns then number of characters from the cursor to the +// start of the previous word. +func (t *Terminal) countToLeftWord() int { + if t.pos == 0 { + return 0 + } + + pos := t.pos - 1 + for pos > 0 { + if t.line[pos] != ' ' { + break + } + pos-- + } + for pos > 0 { + if t.line[pos] == ' ' { + pos++ + break + } + pos-- + } + + return t.pos - pos +} + +// countToRightWord returns then number of characters from the cursor to the +// start of the next word. +func (t *Terminal) countToRightWord() int { + pos := t.pos + for pos < len(t.line) { + if t.line[pos] == ' ' { + break + } + pos++ + } + for pos < len(t.line) { + if t.line[pos] != ' ' { + break + } + pos++ + } + return pos - t.pos +} + +// visualLength returns the number of visible glyphs in s. +func visualLength(runes []rune) int { + inEscapeSeq := false + length := 0 + + for _, r := range runes { + switch { + case inEscapeSeq: + if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') { + inEscapeSeq = false + } + case r == '\x1b': + inEscapeSeq = true + default: + length++ + kind := width.LookupRune(r).Kind() + if kind == width.EastAsianFullwidth || kind == width.EastAsianWide { + length++ + } + } + } + + return length +} + +// handleKey processes the given key and, optionally, returns a line of text +// that the user has entered. +func (t *Terminal) handleKey(key rune) (line string, ok bool) { + if t.pasteActive && key != keyEnter { + t.addKeyToLine(key) + return + } + + switch key { + case keyBackspace: + if t.pos == 0 { + return + } + t.eraseNPreviousChars(1) + case keyAltB: + fallthrough + case keyAltLeft: + // move left by a word. + t.pos -= t.countToLeftWord() + t.moveCursorToPos(t.pos) + case keyAltF: + fallthrough + case keyAltRight: + // move right by a word. + t.pos += t.countToRightWord() + t.moveCursorToPos(t.pos) + case keyLeft: + if t.pos == 0 { + return + } + t.pos-- + t.moveCursorToPos(t.pos) + case keyRight: + if t.pos == len(t.line) { + return + } + t.pos++ + t.moveCursorToPos(t.pos) + case keyHome: + if t.pos == 0 { + return + } + t.pos = 0 + t.moveCursorToPos(t.pos) + case keyEnd: + if t.pos == len(t.line) { + return + } + t.pos = len(t.line) + t.moveCursorToPos(t.pos) + case keyUp: + entry, ok := t.history.NthPreviousEntry(t.historyIndex + 1) + if !ok { + return "", false + } + if t.historyIndex == -1 { + t.historyPending = string(t.line) + } + t.historyIndex++ + runes := []rune(entry) + t.setLine(runes, len(runes)) + case keyDown: + switch t.historyIndex { + case -1: + return + case 0: + runes := []rune(t.historyPending) + t.setLine(runes, len(runes)) + t.historyIndex-- + default: + entry, ok := t.history.NthPreviousEntry(t.historyIndex - 1) + if ok { + t.historyIndex-- + runes := []rune(entry) + t.setLine(runes, len(runes)) + } + } + case keyEnter: + line = string(t.line) + if t.enterClear { + // Clear line on enter instead of starting a new line. + reset := []rune{ + // Clear whole line + keyEscape, '[', '2', 'K', + // Clear screen below (for any wrapped lines) + keyEscape, '[', 'J', + } + t.queue(reset) + // FIXME: This causes a slice out of bounds panic, but without it we + // get an offset with emojis + // t.cursorX += len(reset) + } else { + // Pushing the line up resets the cursor to 0,0 and we render a + // fresh prompt. + t.moveCursorToPos(len(t.line)) + t.queue([]rune("\r\n")) + t.cursorX = 0 + t.cursorY = 0 + } + ok = true + t.line = t.line[:0] + t.pos = 0 + t.maxLine = 0 + case keyDeleteWord: + // Delete zero or more spaces and then one or more characters. + t.eraseNPreviousChars(t.countToLeftWord()) + case keyDeleteLine: + // Delete everything from the current cursor position to the + // end of line. + for i := t.pos; i < len(t.line); i++ { + t.queue(space) + t.advanceCursor(1) + } + t.line = t.line[:t.pos] + t.moveCursorToPos(t.pos) + case keyCtrlD: + // Erase the character under the current position. + // The EOF case when the line is empty is handled in + // readLine(). + if t.pos < len(t.line) { + t.pos++ + t.eraseNPreviousChars(1) + } + case keyCtrlU: + t.eraseNPreviousChars(t.pos) + case keyClearScreen: + // Erases the screen and moves the cursor to the home position. + t.queue([]rune("\x1b[2J\x1b[H")) + t.queue(t.prompt) + t.cursorX, t.cursorY = 0, 0 + t.advanceCursor(visualLength(t.prompt)) + t.setLine(t.line, t.pos) + default: + if t.AutoCompleteCallback != nil { + prefix := string(t.line[:t.pos]) + suffix := string(t.line[t.pos:]) + + t.lock.Unlock() + newLine, newPos, completeOk := t.AutoCompleteCallback(prefix+suffix, len(prefix), key) + t.lock.Lock() + + if completeOk { + t.setLine([]rune(newLine), utf8.RuneCount([]byte(newLine)[:newPos])) + return + } + } + if !isPrintable(key) { + return + } + if len(t.line) == maxLineLength { + return + } + t.addKeyToLine(key) + } + return +} + +// addKeyToLine inserts the given key at the current position in the current +// line. +func (t *Terminal) addKeyToLine(key rune) { + if len(t.line) == cap(t.line) { + newLine := make([]rune, len(t.line), 2*(1+len(t.line))) + copy(newLine, t.line) + t.line = newLine + } + t.line = t.line[:len(t.line)+1] + copy(t.line[t.pos+1:], t.line[t.pos:]) + t.line[t.pos] = key + if t.echo { + t.writeLine(t.line[t.pos:]) + } + t.pos++ + t.moveCursorToPos(t.pos) +} + +func (t *Terminal) writeLine(line []rune) { + for len(line) != 0 { + remainingOnLine := t.termWidth - t.cursorX + todo := len(line) + if todo > remainingOnLine { + todo = remainingOnLine + } + t.queue(line[:todo]) + t.advanceCursor(visualLength(line[:todo])) + line = line[todo:] + } +} + +// writeWithCRLF writes buf to w but replaces all occurrences of \n with \r\n. +func writeWithCRLF(w io.Writer, buf []byte) (n int, err error) { + for len(buf) > 0 { + i := bytes.IndexByte(buf, '\n') + todo := len(buf) + if i >= 0 { + todo = i + } + + var nn int + nn, err = w.Write(buf[:todo]) + n += nn + if err != nil { + return n, err + } + buf = buf[todo:] + + if i >= 0 { + if _, err = w.Write(crlf); err != nil { + return n, err + } + n++ + buf = buf[1:] + } + } + + return n, nil +} + +func (t *Terminal) Write(buf []byte) (n int, err error) { + t.lock.Lock() + defer t.lock.Unlock() + + if t.cursorX == 0 && t.cursorY == 0 { + // This is the easy case: there's nothing on the screen that we + // have to move out of the way. + return writeWithCRLF(t.c, buf) + } + + // We have a prompt and possibly user input on the screen. We + // have to clear it first. + t.move(0 /* up */, 0 /* down */, t.cursorX /* left */, 0 /* right */) + t.cursorX = 0 + t.clearLineToRight() + + for t.cursorY > 0 { + t.move(1 /* up */, 0, 0, 0) + t.cursorY-- + t.clearLineToRight() + } + + if _, err = t.c.Write(t.outBuf); err != nil { + return + } + t.outBuf = t.outBuf[:0] + + if n, err = writeWithCRLF(t.c, buf); err != nil { + return + } + + t.writeLine(t.prompt) + if t.echo { + t.writeLine(t.line) + } + + t.moveCursorToPos(t.pos) + + if _, err = t.c.Write(t.outBuf); err != nil { + return + } + t.outBuf = t.outBuf[:0] + return +} + +// ReadPassword temporarily changes the prompt and reads a password, without +// echo, from the terminal. +func (t *Terminal) ReadPassword(prompt string) (line string, err error) { + t.lock.Lock() + defer t.lock.Unlock() + + oldPrompt := t.prompt + t.prompt = []rune(prompt) + t.echo = false + + line, err = t.readLine() + + t.prompt = oldPrompt + t.echo = true + + return +} + +// ReadLine returns a line of input from the terminal. +func (t *Terminal) ReadLine() (line string, err error) { + t.lock.Lock() + defer t.lock.Unlock() + + return t.readLine() +} + +func (t *Terminal) readLine() (line string, err error) { + // t.lock must be held at this point + + if t.cursorX == 0 && t.cursorY == 0 { + t.writeLine(t.prompt) + t.c.Write(t.outBuf) + t.outBuf = t.outBuf[:0] + } + + lineIsPasted := t.pasteActive + + for { + rest := t.remainder + lineOk := false + for !lineOk { + var key rune + key, rest = bytesToKey(rest, t.pasteActive) + if key == utf8.RuneError { + break + } + if !t.pasteActive { + if key == keyCtrlD { + if len(t.line) == 0 { + return "", io.EOF + } + } + if key == keyPasteStart { + t.pasteActive = true + if len(t.line) == 0 { + lineIsPasted = true + } + continue + } + } else if key == keyPasteEnd { + t.pasteActive = false + continue + } + if !t.pasteActive { + lineIsPasted = false + } + line, lineOk = t.handleKey(key) + } + if len(rest) > 0 { + n := copy(t.inBuf[:], rest) + t.remainder = t.inBuf[:n] + } else { + t.remainder = nil + } + t.c.Write(t.outBuf) + t.outBuf = t.outBuf[:0] + if lineOk { + if t.echo { + t.historyIndex = -1 + t.history.Add(line) + } + if lineIsPasted { + err = ErrPasteIndicator + } + return + } + + // t.remainder is a slice at the beginning of t.inBuf + // containing a partial key sequence + readBuf := t.inBuf[len(t.remainder):] + var n int + + t.lock.Unlock() + n, err = t.c.Read(readBuf) + t.lock.Lock() + + if err != nil { + return + } + + t.remainder = t.inBuf[:n+len(t.remainder)] + } +} + +// SetPrompt sets the prompt to be used when reading subsequent lines. +func (t *Terminal) SetPrompt(prompt string) { + t.lock.Lock() + defer t.lock.Unlock() + + t.prompt = []rune(prompt) +} + +func (t *Terminal) clearAndRepaintLinePlusNPrevious(numPrevLines int) { + // Move cursor to column zero at the start of the line. + t.move(t.cursorY, 0, t.cursorX, 0) + t.cursorX, t.cursorY = 0, 0 + t.clearLineToRight() + for t.cursorY < numPrevLines { + // Move down a line + t.move(0, 1, 0, 0) + t.cursorY++ + t.clearLineToRight() + } + // Move back to beginning. + t.move(t.cursorY, 0, 0, 0) + t.cursorX, t.cursorY = 0, 0 + + t.queue(t.prompt) + t.advanceCursor(visualLength(t.prompt)) + t.writeLine(t.line) + t.moveCursorToPos(t.pos) +} + +func (t *Terminal) SetSize(width, height int) error { + t.lock.Lock() + defer t.lock.Unlock() + + if width == 0 { + width = 1 + } + + oldWidth := t.termWidth + t.termWidth, t.termHeight = width, height + + switch { + case width == oldWidth: + // If the width didn't change then nothing else needs to be + // done. + return nil + case len(t.line) == 0 && t.cursorX == 0 && t.cursorY == 0: + // If there is nothing on current line and no prompt printed, + // just do nothing + return nil + case width < oldWidth: + // Some terminals (e.g. xterm) will truncate lines that were + // too long when shinking. Others, (e.g. gnome-terminal) will + // attempt to wrap them. For the former, repainting t.maxLine + // works great, but that behaviour goes badly wrong in the case + // of the latter because they have doubled every full line. + + // We assume that we are working on a terminal that wraps lines + // and adjust the cursor position based on every previous line + // wrapping and turning into two. This causes the prompt on + // xterms to move upwards, which isn't great, but it avoids a + // huge mess with gnome-terminal. + if t.cursorX >= t.termWidth { + t.cursorX = t.termWidth - 1 + } + t.cursorY *= 2 + t.clearAndRepaintLinePlusNPrevious(t.maxLine * 2) + case width > oldWidth: + // If the terminal expands then our position calculations will + // be wrong in the future because we think the cursor is + // |t.pos| chars into the string, but there will be a gap at + // the end of any wrapped line. + // + // But the position will actually be correct until we move, so + // we can move back to the beginning and repaint everything. + t.clearAndRepaintLinePlusNPrevious(t.maxLine) + } + + _, err := t.c.Write(t.outBuf) + t.outBuf = t.outBuf[:0] + return err +} + +// SetEnterClear sets whether the input line should be cleared or echoed on +// enter. +func (t *Terminal) SetEnterClear(on bool) { + t.lock.Lock() + defer t.lock.Unlock() + + t.enterClear = on +} + +type pasteIndicatorError struct{} + +func (pasteIndicatorError) Error() string { + return "terminal: ErrPasteIndicator not correctly handled" +} + +// ErrPasteIndicator may be returned from ReadLine as the error, in addition +// to valid line data. It indicates that bracketed paste mode is enabled and +// that the returned line consists only of pasted data. Programs may wish to +// interpret pasted data more literally than typed data. +var ErrPasteIndicator = pasteIndicatorError{} + +// SetBracketedPasteMode requests that the terminal bracket paste operations +// with markers. Not all terminals support this but, if it is supported, then +// enabling this mode will stop any autocomplete callback from running due to +// pastes. Additionally, any lines that are completely pasted will be returned +// from ReadLine with the error set to ErrPasteIndicator. +func (t *Terminal) SetBracketedPasteMode(on bool) { + if on { + io.WriteString(t.c, "\x1b[?2004h") + } else { + io.WriteString(t.c, "\x1b[?2004l") + } +} + +// stRingBuffer is a ring buffer of strings. +type stRingBuffer struct { + // entries contains max elements. + entries []string + max int + // head contains the index of the element most recently added to the ring. + head int + // size contains the number of elements in the ring. + size int +} + +func (s *stRingBuffer) Add(a string) { + if s.entries == nil { + const defaultNumEntries = 100 + s.entries = make([]string, defaultNumEntries) + s.max = defaultNumEntries + } + + s.head = (s.head + 1) % s.max + s.entries[s.head] = a + if s.size < s.max { + s.size++ + } +} + +// NthPreviousEntry returns the value passed to the nth previous call to Add. +// If n is zero then the immediately prior value is returned, if one, then the +// next most recent, and so on. If such an element doesn't exist then ok is +// false. +func (s *stRingBuffer) NthPreviousEntry(n int) (value string, ok bool) { + if n >= s.size { + return "", false + } + index := s.head - n + if index < 0 { + index += s.max + } + return s.entries[index], true +} + +// readPasswordLine reads from reader until it finds \n or io.EOF. +// The slice returned does not include the \n. +// readPasswordLine also ignores any \r it finds. +func readPasswordLine(reader io.Reader) ([]byte, error) { + var buf [1]byte + var ret []byte + + for { + n, err := reader.Read(buf[:]) + if n > 0 { + switch buf[0] { + case '\n': + return ret, nil + case '\r': + // remove \r from passwords on Windows + default: + ret = append(ret, buf[0]) + } + continue + } + if err != nil { + if err == io.EOF && len(ret) > 0 { + return ret, nil + } + return ret, err + } + } +} diff --git a/teleirc/matterbridge/vendor/github.com/shazow/ssh-chat/sshd/terminal/util.go b/teleirc/matterbridge/vendor/github.com/shazow/ssh-chat/sshd/terminal/util.go new file mode 100644 index 0000000..e553f7e --- /dev/null +++ b/teleirc/matterbridge/vendor/github.com/shazow/ssh-chat/sshd/terminal/util.go @@ -0,0 +1,114 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build aix darwin dragonfly freebsd linux,!appengine netbsd openbsd + +// Package terminal provides support functions for dealing with terminals, as +// commonly found on UNIX systems. +// +// Putting a terminal into raw mode is the most common requirement: +// +// oldState, err := terminal.MakeRaw(0) +// if err != nil { +// panic(err) +// } +// defer terminal.Restore(0, oldState) +package terminal + +import ( + "golang.org/x/sys/unix" +) + +// State contains the state of a terminal. +type State struct { + termios unix.Termios +} + +// IsTerminal returns whether the given file descriptor is a terminal. +func IsTerminal(fd int) bool { + _, err := unix.IoctlGetTermios(fd, ioctlReadTermios) + return err == nil +} + +// MakeRaw put the terminal connected to the given file descriptor into raw +// mode and returns the previous state of the terminal so that it can be +// restored. +func MakeRaw(fd int) (*State, error) { + termios, err := unix.IoctlGetTermios(fd, ioctlReadTermios) + if err != nil { + return nil, err + } + + oldState := State{termios: *termios} + + // This attempts to replicate the behaviour documented for cfmakeraw in + // the termios(3) manpage. + termios.Iflag &^= unix.IGNBRK | unix.BRKINT | unix.PARMRK | unix.ISTRIP | unix.INLCR | unix.IGNCR | unix.ICRNL | unix.IXON + termios.Oflag &^= unix.OPOST + termios.Lflag &^= unix.ECHO | unix.ECHONL | unix.ICANON | unix.ISIG | unix.IEXTEN + termios.Cflag &^= unix.CSIZE | unix.PARENB + termios.Cflag |= unix.CS8 + termios.Cc[unix.VMIN] = 1 + termios.Cc[unix.VTIME] = 0 + if err := unix.IoctlSetTermios(fd, ioctlWriteTermios, termios); err != nil { + return nil, err + } + + return &oldState, nil +} + +// GetState returns the current state of a terminal which may be useful to +// restore the terminal after a signal. +func GetState(fd int) (*State, error) { + termios, err := unix.IoctlGetTermios(fd, ioctlReadTermios) + if err != nil { + return nil, err + } + + return &State{termios: *termios}, nil +} + +// Restore restores the terminal connected to the given file descriptor to a +// previous state. +func Restore(fd int, state *State) error { + return unix.IoctlSetTermios(fd, ioctlWriteTermios, &state.termios) +} + +// GetSize returns the dimensions of the given terminal. +func GetSize(fd int) (width, height int, err error) { + ws, err := unix.IoctlGetWinsize(fd, unix.TIOCGWINSZ) + if err != nil { + return -1, -1, err + } + return int(ws.Col), int(ws.Row), nil +} + +// passwordReader is an io.Reader that reads from a specific file descriptor. +type passwordReader int + +func (r passwordReader) Read(buf []byte) (int, error) { + return unix.Read(int(r), buf) +} + +// ReadPassword reads a line of input from a terminal without local echo. This +// is commonly used for inputting passwords and other sensitive data. The slice +// returned does not include the \n. +func ReadPassword(fd int) ([]byte, error) { + termios, err := unix.IoctlGetTermios(fd, ioctlReadTermios) + if err != nil { + return nil, err + } + + newState := *termios + newState.Lflag &^= unix.ECHO + newState.Lflag |= unix.ICANON | unix.ISIG + newState.Iflag |= unix.ICRNL + if err := unix.IoctlSetTermios(fd, ioctlWriteTermios, &newState); err != nil { + return nil, err + } + + defer unix.IoctlSetTermios(fd, ioctlWriteTermios, termios) + + return readPasswordLine(passwordReader(fd)) +} diff --git a/teleirc/matterbridge/vendor/github.com/shazow/ssh-chat/sshd/terminal/util_aix.go b/teleirc/matterbridge/vendor/github.com/shazow/ssh-chat/sshd/terminal/util_aix.go new file mode 100644 index 0000000..dfcd627 --- /dev/null +++ b/teleirc/matterbridge/vendor/github.com/shazow/ssh-chat/sshd/terminal/util_aix.go @@ -0,0 +1,12 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build aix + +package terminal + +import "golang.org/x/sys/unix" + +const ioctlReadTermios = unix.TCGETS +const ioctlWriteTermios = unix.TCSETS diff --git a/teleirc/matterbridge/vendor/github.com/shazow/ssh-chat/sshd/terminal/util_bsd.go b/teleirc/matterbridge/vendor/github.com/shazow/ssh-chat/sshd/terminal/util_bsd.go new file mode 100644 index 0000000..cb23a59 --- /dev/null +++ b/teleirc/matterbridge/vendor/github.com/shazow/ssh-chat/sshd/terminal/util_bsd.go @@ -0,0 +1,12 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build darwin dragonfly freebsd netbsd openbsd + +package terminal + +import "golang.org/x/sys/unix" + +const ioctlReadTermios = unix.TIOCGETA +const ioctlWriteTermios = unix.TIOCSETA diff --git a/teleirc/matterbridge/vendor/github.com/shazow/ssh-chat/sshd/terminal/util_linux.go b/teleirc/matterbridge/vendor/github.com/shazow/ssh-chat/sshd/terminal/util_linux.go new file mode 100644 index 0000000..5fadfe8 --- /dev/null +++ b/teleirc/matterbridge/vendor/github.com/shazow/ssh-chat/sshd/terminal/util_linux.go @@ -0,0 +1,10 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package terminal + +import "golang.org/x/sys/unix" + +const ioctlReadTermios = unix.TCGETS +const ioctlWriteTermios = unix.TCSETS diff --git a/teleirc/matterbridge/vendor/github.com/shazow/ssh-chat/sshd/terminal/util_plan9.go b/teleirc/matterbridge/vendor/github.com/shazow/ssh-chat/sshd/terminal/util_plan9.go new file mode 100644 index 0000000..9317ac7 --- /dev/null +++ b/teleirc/matterbridge/vendor/github.com/shazow/ssh-chat/sshd/terminal/util_plan9.go @@ -0,0 +1,58 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package terminal provides support functions for dealing with terminals, as +// commonly found on UNIX systems. +// +// Putting a terminal into raw mode is the most common requirement: +// +// oldState, err := terminal.MakeRaw(0) +// if err != nil { +// panic(err) +// } +// defer terminal.Restore(0, oldState) +package terminal + +import ( + "fmt" + "runtime" +) + +type State struct{} + +// IsTerminal returns whether the given file descriptor is a terminal. +func IsTerminal(fd int) bool { + return false +} + +// MakeRaw put the terminal connected to the given file descriptor into raw +// mode and returns the previous state of the terminal so that it can be +// restored. +func MakeRaw(fd int) (*State, error) { + return nil, fmt.Errorf("terminal: MakeRaw not implemented on %s/%s", runtime.GOOS, runtime.GOARCH) +} + +// GetState returns the current state of a terminal which may be useful to +// restore the terminal after a signal. +func GetState(fd int) (*State, error) { + return nil, fmt.Errorf("terminal: GetState not implemented on %s/%s", runtime.GOOS, runtime.GOARCH) +} + +// Restore restores the terminal connected to the given file descriptor to a +// previous state. +func Restore(fd int, state *State) error { + return fmt.Errorf("terminal: Restore not implemented on %s/%s", runtime.GOOS, runtime.GOARCH) +} + +// GetSize returns the dimensions of the given terminal. +func GetSize(fd int) (width, height int, err error) { + return 0, 0, fmt.Errorf("terminal: GetSize not implemented on %s/%s", runtime.GOOS, runtime.GOARCH) +} + +// ReadPassword reads a line of input from a terminal without local echo. This +// is commonly used for inputting passwords and other sensitive data. The slice +// returned does not include the \n. +func ReadPassword(fd int) ([]byte, error) { + return nil, fmt.Errorf("terminal: ReadPassword not implemented on %s/%s", runtime.GOOS, runtime.GOARCH) +} diff --git a/teleirc/matterbridge/vendor/github.com/shazow/ssh-chat/sshd/terminal/util_solaris.go b/teleirc/matterbridge/vendor/github.com/shazow/ssh-chat/sshd/terminal/util_solaris.go new file mode 100644 index 0000000..7b6b6fb --- /dev/null +++ b/teleirc/matterbridge/vendor/github.com/shazow/ssh-chat/sshd/terminal/util_solaris.go @@ -0,0 +1,125 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build solaris + +package terminal + +import ( + "io" + "syscall" + + "golang.org/x/sys/unix" +) + +// State contains the state of a terminal. +type State struct { + termios unix.Termios +} + +// IsTerminal returns whether the given file descriptor is a terminal. +func IsTerminal(fd int) bool { + _, err := unix.IoctlGetTermio(fd, unix.TCGETA) + return err == nil +} + +// ReadPassword reads a line of input from a terminal without local echo. This +// is commonly used for inputting passwords and other sensitive data. The slice +// returned does not include the \n. +func ReadPassword(fd int) ([]byte, error) { + // see also: http://src.illumos.org/source/xref/illumos-gate/usr/src/lib/libast/common/uwin/getpass.c + val, err := unix.IoctlGetTermios(fd, unix.TCGETS) + if err != nil { + return nil, err + } + oldState := *val + + newState := oldState + newState.Lflag &^= syscall.ECHO + newState.Lflag |= syscall.ICANON | syscall.ISIG + newState.Iflag |= syscall.ICRNL + err = unix.IoctlSetTermios(fd, unix.TCSETS, &newState) + if err != nil { + return nil, err + } + + defer unix.IoctlSetTermios(fd, unix.TCSETS, &oldState) + + var buf [16]byte + var ret []byte + for { + n, err := syscall.Read(fd, buf[:]) + if err != nil { + return nil, err + } + if n == 0 { + if len(ret) == 0 { + return nil, io.EOF + } + break + } + if buf[n-1] == '\n' { + n-- + } + ret = append(ret, buf[:n]...) + if n < len(buf) { + break + } + } + + return ret, nil +} + +// MakeRaw puts the terminal connected to the given file descriptor into raw +// mode and returns the previous state of the terminal so that it can be +// restored. +// see http://cr.illumos.org/~webrev/andy_js/1060/ +func MakeRaw(fd int) (*State, error) { + termios, err := unix.IoctlGetTermios(fd, unix.TCGETS) + if err != nil { + return nil, err + } + + oldState := State{termios: *termios} + + termios.Iflag &^= unix.IGNBRK | unix.BRKINT | unix.PARMRK | unix.ISTRIP | unix.INLCR | unix.IGNCR | unix.ICRNL | unix.IXON + termios.Oflag &^= unix.OPOST + termios.Lflag &^= unix.ECHO | unix.ECHONL | unix.ICANON | unix.ISIG | unix.IEXTEN + termios.Cflag &^= unix.CSIZE | unix.PARENB + termios.Cflag |= unix.CS8 + termios.Cc[unix.VMIN] = 1 + termios.Cc[unix.VTIME] = 0 + + if err := unix.IoctlSetTermios(fd, unix.TCSETS, termios); err != nil { + return nil, err + } + + return &oldState, nil +} + +// Restore restores the terminal connected to the given file descriptor to a +// previous state. +func Restore(fd int, oldState *State) error { + return unix.IoctlSetTermios(fd, unix.TCSETS, &oldState.termios) +} + +// GetState returns the current state of a terminal which may be useful to +// restore the terminal after a signal. +func GetState(fd int) (*State, error) { + termios, err := unix.IoctlGetTermios(fd, unix.TCGETS) + if err != nil { + return nil, err + } + + return &State{termios: *termios}, nil +} + +// GetSize returns the dimensions of the given terminal. +func GetSize(fd int) (width, height int, err error) { + ws, err := unix.IoctlGetWinsize(fd, unix.TIOCGWINSZ) + if err != nil { + return 0, 0, err + } + return int(ws.Col), int(ws.Row), nil +} diff --git a/teleirc/matterbridge/vendor/github.com/shazow/ssh-chat/sshd/terminal/util_windows.go b/teleirc/matterbridge/vendor/github.com/shazow/ssh-chat/sshd/terminal/util_windows.go new file mode 100644 index 0000000..6cb8a95 --- /dev/null +++ b/teleirc/matterbridge/vendor/github.com/shazow/ssh-chat/sshd/terminal/util_windows.go @@ -0,0 +1,103 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build windows + +// Package terminal provides support functions for dealing with terminals, as +// commonly found on UNIX systems. +// +// Putting a terminal into raw mode is the most common requirement: +// +// oldState, err := terminal.MakeRaw(0) +// if err != nil { +// panic(err) +// } +// defer terminal.Restore(0, oldState) +package terminal + +import ( + "os" + + "golang.org/x/sys/windows" +) + +type State struct { + mode uint32 +} + +// IsTerminal returns whether the given file descriptor is a terminal. +func IsTerminal(fd int) bool { + var st uint32 + err := windows.GetConsoleMode(windows.Handle(fd), &st) + return err == nil +} + +// MakeRaw put the terminal connected to the given file descriptor into raw +// mode and returns the previous state of the terminal so that it can be +// restored. +func MakeRaw(fd int) (*State, error) { + var st uint32 + if err := windows.GetConsoleMode(windows.Handle(fd), &st); err != nil { + return nil, err + } + raw := st &^ (windows.ENABLE_ECHO_INPUT | windows.ENABLE_PROCESSED_INPUT | windows.ENABLE_LINE_INPUT | windows.ENABLE_PROCESSED_OUTPUT) + if err := windows.SetConsoleMode(windows.Handle(fd), raw); err != nil { + return nil, err + } + return &State{st}, nil +} + +// GetState returns the current state of a terminal which may be useful to +// restore the terminal after a signal. +func GetState(fd int) (*State, error) { + var st uint32 + if err := windows.GetConsoleMode(windows.Handle(fd), &st); err != nil { + return nil, err + } + return &State{st}, nil +} + +// Restore restores the terminal connected to the given file descriptor to a +// previous state. +func Restore(fd int, state *State) error { + return windows.SetConsoleMode(windows.Handle(fd), state.mode) +} + +// GetSize returns the dimensions of the given terminal. +func GetSize(fd int) (width, height int, err error) { + var info windows.ConsoleScreenBufferInfo + if err := windows.GetConsoleScreenBufferInfo(windows.Handle(fd), &info); err != nil { + return 0, 0, err + } + return int(info.Size.X), int(info.Size.Y), nil +} + +// ReadPassword reads a line of input from a terminal without local echo. This +// is commonly used for inputting passwords and other sensitive data. The slice +// returned does not include the \n. +func ReadPassword(fd int) ([]byte, error) { + var st uint32 + if err := windows.GetConsoleMode(windows.Handle(fd), &st); err != nil { + return nil, err + } + old := st + + st &^= (windows.ENABLE_ECHO_INPUT) + st |= (windows.ENABLE_PROCESSED_INPUT | windows.ENABLE_LINE_INPUT | windows.ENABLE_PROCESSED_OUTPUT) + if err := windows.SetConsoleMode(windows.Handle(fd), st); err != nil { + return nil, err + } + + defer windows.SetConsoleMode(windows.Handle(fd), old) + + var h windows.Handle + p, _ := windows.GetCurrentProcess() + if err := windows.DuplicateHandle(p, windows.Handle(fd), p, &h, 0, false, windows.DUPLICATE_SAME_ACCESS); err != nil { + return nil, err + } + + f := os.NewFile(uintptr(h), "stdin") + defer f.Close() + return readPasswordLine(f) +} |
