mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-11-04 01:12:24 -06:00 
			
		
		
		
	
		
			
	
	
		
			279 lines
		
	
	
	
		
			8.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
		
		
			
		
	
	
			279 lines
		
	
	
	
		
			8.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| 
								 | 
							
								// 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 ssh
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								import (
							 | 
						||
| 
								 | 
							
									"bytes"
							 | 
						||
| 
								 | 
							
									"errors"
							 | 
						||
| 
								 | 
							
									"fmt"
							 | 
						||
| 
								 | 
							
									"net"
							 | 
						||
| 
								 | 
							
									"os"
							 | 
						||
| 
								 | 
							
									"sync"
							 | 
						||
| 
								 | 
							
									"time"
							 | 
						||
| 
								 | 
							
								)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// Client implements a traditional SSH client that supports shells,
							 | 
						||
| 
								 | 
							
								// subprocesses, TCP port/streamlocal forwarding and tunneled dialing.
							 | 
						||
| 
								 | 
							
								type Client struct {
							 | 
						||
| 
								 | 
							
									Conn
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									handleForwardsOnce sync.Once // guards calling (*Client).handleForwards
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									forwards        forwardList // forwarded tcpip connections from the remote side
							 | 
						||
| 
								 | 
							
									mu              sync.Mutex
							 | 
						||
| 
								 | 
							
									channelHandlers map[string]chan NewChannel
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// HandleChannelOpen returns a channel on which NewChannel requests
							 | 
						||
| 
								 | 
							
								// for the given type are sent. If the type already is being handled,
							 | 
						||
| 
								 | 
							
								// nil is returned. The channel is closed when the connection is closed.
							 | 
						||
| 
								 | 
							
								func (c *Client) HandleChannelOpen(channelType string) <-chan NewChannel {
							 | 
						||
| 
								 | 
							
									c.mu.Lock()
							 | 
						||
| 
								 | 
							
									defer c.mu.Unlock()
							 | 
						||
| 
								 | 
							
									if c.channelHandlers == nil {
							 | 
						||
| 
								 | 
							
										// The SSH channel has been closed.
							 | 
						||
| 
								 | 
							
										c := make(chan NewChannel)
							 | 
						||
| 
								 | 
							
										close(c)
							 | 
						||
| 
								 | 
							
										return c
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									ch := c.channelHandlers[channelType]
							 | 
						||
| 
								 | 
							
									if ch != nil {
							 | 
						||
| 
								 | 
							
										return nil
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									ch = make(chan NewChannel, chanSize)
							 | 
						||
| 
								 | 
							
									c.channelHandlers[channelType] = ch
							 | 
						||
| 
								 | 
							
									return ch
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// NewClient creates a Client on top of the given connection.
							 | 
						||
| 
								 | 
							
								func NewClient(c Conn, chans <-chan NewChannel, reqs <-chan *Request) *Client {
							 | 
						||
| 
								 | 
							
									conn := &Client{
							 | 
						||
| 
								 | 
							
										Conn:            c,
							 | 
						||
| 
								 | 
							
										channelHandlers: make(map[string]chan NewChannel, 1),
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									go conn.handleGlobalRequests(reqs)
							 | 
						||
| 
								 | 
							
									go conn.handleChannelOpens(chans)
							 | 
						||
| 
								 | 
							
									go func() {
							 | 
						||
| 
								 | 
							
										conn.Wait()
							 | 
						||
| 
								 | 
							
										conn.forwards.closeAll()
							 | 
						||
| 
								 | 
							
									}()
							 | 
						||
| 
								 | 
							
									return conn
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// NewClientConn establishes an authenticated SSH connection using c
							 | 
						||
| 
								 | 
							
								// as the underlying transport.  The Request and NewChannel channels
							 | 
						||
| 
								 | 
							
								// must be serviced or the connection will hang.
							 | 
						||
| 
								 | 
							
								func NewClientConn(c net.Conn, addr string, config *ClientConfig) (Conn, <-chan NewChannel, <-chan *Request, error) {
							 | 
						||
| 
								 | 
							
									fullConf := *config
							 | 
						||
| 
								 | 
							
									fullConf.SetDefaults()
							 | 
						||
| 
								 | 
							
									if fullConf.HostKeyCallback == nil {
							 | 
						||
| 
								 | 
							
										c.Close()
							 | 
						||
| 
								 | 
							
										return nil, nil, nil, errors.New("ssh: must specify HostKeyCallback")
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									conn := &connection{
							 | 
						||
| 
								 | 
							
										sshConn: sshConn{conn: c, user: fullConf.User},
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									if err := conn.clientHandshake(addr, &fullConf); err != nil {
							 | 
						||
| 
								 | 
							
										c.Close()
							 | 
						||
| 
								 | 
							
										return nil, nil, nil, fmt.Errorf("ssh: handshake failed: %v", err)
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									conn.mux = newMux(conn.transport)
							 | 
						||
| 
								 | 
							
									return conn, conn.mux.incomingChannels, conn.mux.incomingRequests, nil
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// clientHandshake performs the client side key exchange. See RFC 4253 Section
							 | 
						||
| 
								 | 
							
								// 7.
							 | 
						||
| 
								 | 
							
								func (c *connection) clientHandshake(dialAddress string, config *ClientConfig) error {
							 | 
						||
| 
								 | 
							
									if config.ClientVersion != "" {
							 | 
						||
| 
								 | 
							
										c.clientVersion = []byte(config.ClientVersion)
							 | 
						||
| 
								 | 
							
									} else {
							 | 
						||
| 
								 | 
							
										c.clientVersion = []byte(packageVersion)
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									var err error
							 | 
						||
| 
								 | 
							
									c.serverVersion, err = exchangeVersions(c.sshConn.conn, c.clientVersion)
							 | 
						||
| 
								 | 
							
									if err != nil {
							 | 
						||
| 
								 | 
							
										return err
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									c.transport = newClientTransport(
							 | 
						||
| 
								 | 
							
										newTransport(c.sshConn.conn, config.Rand, true /* is client */),
							 | 
						||
| 
								 | 
							
										c.clientVersion, c.serverVersion, config, dialAddress, c.sshConn.RemoteAddr())
							 | 
						||
| 
								 | 
							
									if err := c.transport.waitSession(); err != nil {
							 | 
						||
| 
								 | 
							
										return err
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									c.sessionID = c.transport.getSessionID()
							 | 
						||
| 
								 | 
							
									return c.clientAuthenticate(config)
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// verifyHostKeySignature verifies the host key obtained in the key
							 | 
						||
| 
								 | 
							
								// exchange.
							 | 
						||
| 
								 | 
							
								func verifyHostKeySignature(hostKey PublicKey, result *kexResult) error {
							 | 
						||
| 
								 | 
							
									sig, rest, ok := parseSignatureBody(result.Signature)
							 | 
						||
| 
								 | 
							
									if len(rest) > 0 || !ok {
							 | 
						||
| 
								 | 
							
										return errors.New("ssh: signature parse error")
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									return hostKey.Verify(result.H, sig)
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// NewSession opens a new Session for this client. (A session is a remote
							 | 
						||
| 
								 | 
							
								// execution of a program.)
							 | 
						||
| 
								 | 
							
								func (c *Client) NewSession() (*Session, error) {
							 | 
						||
| 
								 | 
							
									ch, in, err := c.OpenChannel("session", nil)
							 | 
						||
| 
								 | 
							
									if err != nil {
							 | 
						||
| 
								 | 
							
										return nil, err
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									return newSession(ch, in)
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								func (c *Client) handleGlobalRequests(incoming <-chan *Request) {
							 | 
						||
| 
								 | 
							
									for r := range incoming {
							 | 
						||
| 
								 | 
							
										// This handles keepalive messages and matches
							 | 
						||
| 
								 | 
							
										// the behaviour of OpenSSH.
							 | 
						||
| 
								 | 
							
										r.Reply(false, nil)
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// handleChannelOpens channel open messages from the remote side.
							 | 
						||
| 
								 | 
							
								func (c *Client) handleChannelOpens(in <-chan NewChannel) {
							 | 
						||
| 
								 | 
							
									for ch := range in {
							 | 
						||
| 
								 | 
							
										c.mu.Lock()
							 | 
						||
| 
								 | 
							
										handler := c.channelHandlers[ch.ChannelType()]
							 | 
						||
| 
								 | 
							
										c.mu.Unlock()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										if handler != nil {
							 | 
						||
| 
								 | 
							
											handler <- ch
							 | 
						||
| 
								 | 
							
										} else {
							 | 
						||
| 
								 | 
							
											ch.Reject(UnknownChannelType, fmt.Sprintf("unknown channel type: %v", ch.ChannelType()))
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									c.mu.Lock()
							 | 
						||
| 
								 | 
							
									for _, ch := range c.channelHandlers {
							 | 
						||
| 
								 | 
							
										close(ch)
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									c.channelHandlers = nil
							 | 
						||
| 
								 | 
							
									c.mu.Unlock()
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// Dial starts a client connection to the given SSH server. It is a
							 | 
						||
| 
								 | 
							
								// convenience function that connects to the given network address,
							 | 
						||
| 
								 | 
							
								// initiates the SSH handshake, and then sets up a Client.  For access
							 | 
						||
| 
								 | 
							
								// to incoming channels and requests, use net.Dial with NewClientConn
							 | 
						||
| 
								 | 
							
								// instead.
							 | 
						||
| 
								 | 
							
								func Dial(network, addr string, config *ClientConfig) (*Client, error) {
							 | 
						||
| 
								 | 
							
									conn, err := net.DialTimeout(network, addr, config.Timeout)
							 | 
						||
| 
								 | 
							
									if err != nil {
							 | 
						||
| 
								 | 
							
										return nil, err
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									c, chans, reqs, err := NewClientConn(conn, addr, config)
							 | 
						||
| 
								 | 
							
									if err != nil {
							 | 
						||
| 
								 | 
							
										return nil, err
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									return NewClient(c, chans, reqs), nil
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// HostKeyCallback is the function type used for verifying server
							 | 
						||
| 
								 | 
							
								// keys.  A HostKeyCallback must return nil if the host key is OK, or
							 | 
						||
| 
								 | 
							
								// an error to reject it. It receives the hostname as passed to Dial
							 | 
						||
| 
								 | 
							
								// or NewClientConn. The remote address is the RemoteAddr of the
							 | 
						||
| 
								 | 
							
								// net.Conn underlying the SSH connection.
							 | 
						||
| 
								 | 
							
								type HostKeyCallback func(hostname string, remote net.Addr, key PublicKey) error
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// BannerCallback is the function type used for treat the banner sent by
							 | 
						||
| 
								 | 
							
								// the server. A BannerCallback receives the message sent by the remote server.
							 | 
						||
| 
								 | 
							
								type BannerCallback func(message string) error
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// A ClientConfig structure is used to configure a Client. It must not be
							 | 
						||
| 
								 | 
							
								// modified after having been passed to an SSH function.
							 | 
						||
| 
								 | 
							
								type ClientConfig struct {
							 | 
						||
| 
								 | 
							
									// Config contains configuration that is shared between clients and
							 | 
						||
| 
								 | 
							
									// servers.
							 | 
						||
| 
								 | 
							
									Config
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									// User contains the username to authenticate as.
							 | 
						||
| 
								 | 
							
									User string
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									// Auth contains possible authentication methods to use with the
							 | 
						||
| 
								 | 
							
									// server. Only the first instance of a particular RFC 4252 method will
							 | 
						||
| 
								 | 
							
									// be used during authentication.
							 | 
						||
| 
								 | 
							
									Auth []AuthMethod
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									// HostKeyCallback is called during the cryptographic
							 | 
						||
| 
								 | 
							
									// handshake to validate the server's host key. The client
							 | 
						||
| 
								 | 
							
									// configuration must supply this callback for the connection
							 | 
						||
| 
								 | 
							
									// to succeed. The functions InsecureIgnoreHostKey or
							 | 
						||
| 
								 | 
							
									// FixedHostKey can be used for simplistic host key checks.
							 | 
						||
| 
								 | 
							
									HostKeyCallback HostKeyCallback
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									// BannerCallback is called during the SSH dance to display a custom
							 | 
						||
| 
								 | 
							
									// server's message. The client configuration can supply this callback to
							 | 
						||
| 
								 | 
							
									// handle it as wished. The function BannerDisplayStderr can be used for
							 | 
						||
| 
								 | 
							
									// simplistic display on Stderr.
							 | 
						||
| 
								 | 
							
									BannerCallback BannerCallback
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									// ClientVersion contains the version identification string that will
							 | 
						||
| 
								 | 
							
									// be used for the connection. If empty, a reasonable default is used.
							 | 
						||
| 
								 | 
							
									ClientVersion string
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									// HostKeyAlgorithms lists the key types that the client will
							 | 
						||
| 
								 | 
							
									// accept from the server as host key, in order of
							 | 
						||
| 
								 | 
							
									// preference. If empty, a reasonable default is used. Any
							 | 
						||
| 
								 | 
							
									// string returned from PublicKey.Type method may be used, or
							 | 
						||
| 
								 | 
							
									// any of the CertAlgoXxxx and KeyAlgoXxxx constants.
							 | 
						||
| 
								 | 
							
									HostKeyAlgorithms []string
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									// Timeout is the maximum amount of time for the TCP connection to establish.
							 | 
						||
| 
								 | 
							
									//
							 | 
						||
| 
								 | 
							
									// A Timeout of zero means no timeout.
							 | 
						||
| 
								 | 
							
									Timeout time.Duration
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// InsecureIgnoreHostKey returns a function that can be used for
							 | 
						||
| 
								 | 
							
								// ClientConfig.HostKeyCallback to accept any host key. It should
							 | 
						||
| 
								 | 
							
								// not be used for production code.
							 | 
						||
| 
								 | 
							
								func InsecureIgnoreHostKey() HostKeyCallback {
							 | 
						||
| 
								 | 
							
									return func(hostname string, remote net.Addr, key PublicKey) error {
							 | 
						||
| 
								 | 
							
										return nil
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								type fixedHostKey struct {
							 | 
						||
| 
								 | 
							
									key PublicKey
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								func (f *fixedHostKey) check(hostname string, remote net.Addr, key PublicKey) error {
							 | 
						||
| 
								 | 
							
									if f.key == nil {
							 | 
						||
| 
								 | 
							
										return fmt.Errorf("ssh: required host key was nil")
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									if !bytes.Equal(key.Marshal(), f.key.Marshal()) {
							 | 
						||
| 
								 | 
							
										return fmt.Errorf("ssh: host key mismatch")
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									return nil
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// FixedHostKey returns a function for use in
							 | 
						||
| 
								 | 
							
								// ClientConfig.HostKeyCallback to accept only a specific host key.
							 | 
						||
| 
								 | 
							
								func FixedHostKey(key PublicKey) HostKeyCallback {
							 | 
						||
| 
								 | 
							
									hk := &fixedHostKey{key}
							 | 
						||
| 
								 | 
							
									return hk.check
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// BannerDisplayStderr returns a function that can be used for
							 | 
						||
| 
								 | 
							
								// ClientConfig.BannerCallback to display banners on os.Stderr.
							 | 
						||
| 
								 | 
							
								func BannerDisplayStderr() BannerCallback {
							 | 
						||
| 
								 | 
							
									return func(banner string) error {
							 | 
						||
| 
								 | 
							
										_, err := os.Stderr.WriteString(banner)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										return err
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								}
							 |