// 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"
	"sync"
	"time"
)

// Client implements a traditional SSH client that supports shells,
// subprocesses, TCP port/streamlocal forwarding and tunneled dialing. 
type Client struct {
	Conn

	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()
	}()
	go conn.forwards.handleChannels(conn. HandleChannelOpen("forwarded-tcpip"))
	go conn.forwards.handleChannels(conn.HandleChannelOpen("forwarded-streamlocal@openssh.com"))
	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},
	}

	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 the SSH connection. 
type HostKeyCallback func(hostname string, remote net. Addr, key PublicKey) 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

	// 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
}
