github.com/glycerine/xcryptossh@v7.0.4+incompatible/client.go (about)

     1  // Copyright 2011 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package ssh
     6  
     7  import (
     8  	"bytes"
     9  	"context"
    10  	"errors"
    11  	"fmt"
    12  	"net"
    13  	"sync"
    14  	"time"
    15  )
    16  
    17  // Client implements a traditional SSH client that supports shells,
    18  // subprocesses, TCP port/streamlocal forwarding and tunneled dialing.
    19  type Client struct {
    20  	Conn
    21  	Halt *Halter
    22  
    23  	Forwards        ForwardList // forwarded tcpip connections from the remote side
    24  	Mu              sync.Mutex
    25  	ChannelHandlers map[string]chan NewChannel
    26  
    27  	TmpCtx context.Context
    28  }
    29  
    30  // HandleChannelOpen returns a channel on which NewChannel requests
    31  // for the given type are sent. If the type already is being handled,
    32  // nil is returned. The channel is closed when the connection is closed.
    33  func (c *Client) HandleChannelOpen(channelType string) <-chan NewChannel {
    34  	c.Mu.Lock()
    35  	defer c.Mu.Unlock()
    36  	if c.ChannelHandlers == nil {
    37  		// The SSH channel has been closed.
    38  		c := make(chan NewChannel)
    39  		close(c)
    40  		return c
    41  	}
    42  
    43  	ch := c.ChannelHandlers[channelType]
    44  	if ch != nil {
    45  		return nil
    46  	}
    47  
    48  	ch = make(chan NewChannel, chanSize)
    49  	c.ChannelHandlers[channelType] = ch
    50  	return ch
    51  }
    52  
    53  // NewClient creates a Client on top of the given connection.
    54  func NewClient(ctx context.Context, c Conn, chans <-chan NewChannel, reqs <-chan *Request, halt *Halter) *Client {
    55  	conn := &Client{
    56  		Conn:            c,
    57  		ChannelHandlers: make(map[string]chan NewChannel, 1),
    58  		Halt:            halt,
    59  	}
    60  
    61  	go conn.HandleGlobalRequests(ctx, reqs)
    62  	go conn.HandleChannelOpens(ctx, chans)
    63  	go func() {
    64  		conn.Wait()
    65  		conn.Forwards.CloseAll()
    66  	}()
    67  	go conn.Forwards.HandleChannels(ctx, conn.HandleChannelOpen("forwarded-tcpip"), c)
    68  	go conn.Forwards.HandleChannels(ctx, conn.HandleChannelOpen("forwarded-streamlocal@openssh.com"), c)
    69  	return conn
    70  }
    71  
    72  // NewClientConn establishes an authenticated SSH connection using c
    73  // as the underlying transport.  The Request and NewChannel channels
    74  // must be serviced or the connection will hang.
    75  func NewClientConn(ctx context.Context, c net.Conn, addr string, config *ClientConfig) (Conn, <-chan NewChannel, <-chan *Request, error) {
    76  	fullConf := *config
    77  	fullConf.SetDefaults()
    78  	if fullConf.HostKeyCallback == nil {
    79  		c.Close()
    80  		return nil, nil, nil, errors.New("ssh: must specify HostKeyCallback")
    81  	}
    82  	if fullConf.Halt == nil {
    83  		c.Close()
    84  		return nil, nil, nil, errors.New("ssh: config must provide Halt")
    85  	}
    86  	conn := newConnection(c, &fullConf.Config, &fullConf)
    87  
    88  	// can block on conn here, we need to get a close
    89  	// on conn in.
    90  	if err := conn.clientHandshake(ctx, addr, &fullConf); err != nil {
    91  		c.Close()
    92  		return nil, nil, nil, fmt.Errorf("ssh: handshake failed: %v", err)
    93  	}
    94  
    95  	conn.mux = newMux(ctx, conn.transport, conn.halt)
    96  	return conn, conn.mux.incomingChannels, conn.mux.incomingRequests, nil
    97  }
    98  
    99  // clientHandshake performs the client side key exchange. See RFC 4253 Section
   100  // 7.
   101  func (c *connection) clientHandshake(ctx context.Context, dialAddress string, config *ClientConfig) error {
   102  	if config.ClientVersion != "" {
   103  		c.clientVersion = []byte(config.ClientVersion)
   104  	} else {
   105  		c.clientVersion = []byte(packageVersion)
   106  	}
   107  	var err error
   108  	c.serverVersion, err = exchangeVersions(c.sshConn.conn, c.clientVersion)
   109  	if err != nil {
   110  		return err
   111  	}
   112  
   113  	c.transport = newClientTransport(ctx,
   114  		newTransport(c.sshConn.conn, config.Rand, true /* is client */, &config.Config),
   115  		c.clientVersion, c.serverVersion, config, dialAddress, c.sshConn.RemoteAddr())
   116  	if c.transport == nil {
   117  		return ErrShutDown
   118  	}
   119  	if err := c.transport.waitSession(ctx); err != nil {
   120  		return err
   121  	}
   122  
   123  	c.sessionID = c.transport.getSessionID()
   124  	return c.clientAuthenticate(ctx, config)
   125  }
   126  
   127  // verifyHostKeySignature verifies the host key obtained in the key
   128  // exchange.
   129  func verifyHostKeySignature(hostKey PublicKey, result *kexResult) error {
   130  	sig, rest, ok := parseSignatureBody(result.Signature)
   131  	if len(rest) > 0 || !ok {
   132  		return errors.New("ssh: signature parse error")
   133  	}
   134  
   135  	return hostKey.Verify(result.H, sig)
   136  }
   137  
   138  // NewSession opens a new Session for this client. (A session is a remote
   139  // execution of a program.)
   140  func (c *Client) NewSession(ctx context.Context) (*Session, error) {
   141  	ch, in, err := c.OpenChannel(ctx, "session", nil, nil)
   142  	if err != nil {
   143  		return nil, err
   144  	}
   145  	return newSession(ch, in)
   146  }
   147  
   148  func (c *Client) HandleGlobalRequests(ctx context.Context, incoming <-chan *Request) {
   149  
   150  	for {
   151  		select {
   152  		case r := <-incoming:
   153  			if r != nil {
   154  				// This handles keepalive messages and matches
   155  				// the behaviour of OpenSSH.
   156  				r.Reply(false, nil)
   157  			}
   158  		case <-c.Halt.ReqStopChan():
   159  			return
   160  		case <-c.Conn.Done():
   161  			return
   162  		case <-ctx.Done():
   163  			return
   164  		}
   165  	}
   166  }
   167  
   168  // handleChannelOpens channel open messages from the remote side.
   169  func (c *Client) HandleChannelOpens(ctx context.Context, in <-chan NewChannel) {
   170  
   171  	for {
   172  		select {
   173  		case <-c.Halt.ReqStopChan():
   174  			return
   175  		case <-c.Conn.Done():
   176  			return
   177  		case <-ctx.Done():
   178  			return
   179  		case ch := <-in:
   180  			if ch != nil {
   181  				c.Mu.Lock()
   182  				handler := c.ChannelHandlers[ch.ChannelType()]
   183  				c.Mu.Unlock()
   184  				if handler != nil {
   185  					select {
   186  					case handler <- ch:
   187  					case <-c.Halt.ReqStopChan():
   188  						return
   189  					case <-c.Conn.Done():
   190  						return
   191  					case <-ctx.Done():
   192  						return
   193  					}
   194  				} else {
   195  					ch.Reject(UnknownChannelType, fmt.Sprintf("unknown channel type: %v", ch.ChannelType()))
   196  				}
   197  			}
   198  		}
   199  	}
   200  	c.Mu.Lock()
   201  	for _, ch := range c.ChannelHandlers {
   202  		close(ch)
   203  	}
   204  	c.ChannelHandlers = nil
   205  	c.Mu.Unlock()
   206  }
   207  
   208  // Dial starts a client connection to the given SSH server. It is a
   209  // convenience function that connects to the given network address,
   210  // initiates the SSH handshake, and then sets up a Client.  For access
   211  // to incoming channels and requests, use net.Dial with NewClientConn
   212  // instead.
   213  func Dial(ctx context.Context, network, addr string, config *ClientConfig) (*Client, error) {
   214  	conn, err := net.DialTimeout(network, addr, config.Timeout)
   215  	if err != nil {
   216  		return nil, err
   217  	}
   218  	c, chans, reqs, err := NewClientConn(ctx, conn, addr, config)
   219  	if err != nil {
   220  		return nil, err
   221  	}
   222  	// use the clicfg.Halt because config.Halt might still be nil.
   223  	return NewClient(ctx, c, chans, reqs, c.(*connection).clicfg.Halt), nil
   224  }
   225  
   226  // HostKeyCallback is the function type used for verifying server
   227  // keys.  A HostKeyCallback must return nil if the host key is OK, or
   228  // an error to reject it. It receives the hostname as passed to Dial
   229  // or NewClientConn. The remote address is the RemoteAddr of the
   230  // net.Conn underlying the the SSH connection.
   231  type HostKeyCallback func(hostname string, remote net.Addr, key PublicKey) error
   232  
   233  // A ClientConfig structure is used to configure a Client. It must not be
   234  // modified after having been passed to an SSH function.
   235  type ClientConfig struct {
   236  	// Config contains configuration that is shared between clients and
   237  	// servers.
   238  	Config
   239  
   240  	// User contains the username to authenticate as.
   241  	User string
   242  
   243  	// HostPort has the IP:port in string form.
   244  	HostPort string
   245  
   246  	// Auth contains possible authentication methods to use with the
   247  	// server. Only the first instance of a particular RFC 4252 method will
   248  	// be used during authentication.
   249  	Auth []AuthMethod
   250  
   251  	// HostKeyCallback is called during the cryptographic
   252  	// handshake to validate the server's host key. The client
   253  	// configuration must supply this callback for the connection
   254  	// to succeed. The functions InsecureIgnoreHostKey or
   255  	// FixedHostKey can be used for simplistic host key checks.
   256  	HostKeyCallback HostKeyCallback
   257  
   258  	// ClientVersion contains the version identification string that will
   259  	// be used for the connection. If empty, a reasonable default is used.
   260  	ClientVersion string
   261  
   262  	// HostKeyAlgorithms lists the key types that the client will
   263  	// accept from the server as host key, in order of
   264  	// preference. If empty, a reasonable default is used. Any
   265  	// string returned from PublicKey.Type method may be used, or
   266  	// any of the CertAlgoXxxx and KeyAlgoXxxx constants.
   267  	HostKeyAlgorithms []string
   268  
   269  	// Timeout is the maximum amount of time for the TCP connection to establish.
   270  	//
   271  	// A Timeout of zero means no timeout.
   272  	Timeout time.Duration
   273  }
   274  
   275  // InsecureIgnoreHostKey returns a function that can be used for
   276  // ClientConfig.HostKeyCallback to accept any host key. It should
   277  // not be used for production code.
   278  func InsecureIgnoreHostKey() HostKeyCallback {
   279  	return func(hostname string, remote net.Addr, key PublicKey) error {
   280  		return nil
   281  	}
   282  }
   283  
   284  type fixedHostKey struct {
   285  	key PublicKey
   286  }
   287  
   288  func (f *fixedHostKey) check(hostname string, remote net.Addr, key PublicKey) error {
   289  	if f.key == nil {
   290  		return fmt.Errorf("ssh: required host key was nil")
   291  	}
   292  	if !bytes.Equal(key.Marshal(), f.key.Marshal()) {
   293  		return fmt.Errorf("ssh: host key mismatch")
   294  	}
   295  	return nil
   296  }
   297  
   298  // FixedHostKey returns a function for use in
   299  // ClientConfig.HostKeyCallback to accept only a specific host key.
   300  func FixedHostKey(key PublicKey) HostKeyCallback {
   301  	hk := &fixedHostKey{key}
   302  	return hk.check
   303  }