2018-09-30 18:02:42 -07:00
// 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 } ,
}
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
2019-10-12 06:48:45 +00:00
// net.Conn underlying the SSH connection.
2018-09-30 18:02:42 -07:00
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
}
}