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 }