blob: e607610d39dd8b6fb2a238c447fa0154ab49b3a8 [file] [log] [blame]
Russ Cox470549d2012-01-25 15:31:12 -05001// 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
5package ssh
6
7import (
Russ Cox470549d2012-01-25 15:31:12 -05008 "errors"
9 "fmt"
Russ Cox470549d2012-01-25 15:31:12 -050010 "net"
11 "sync"
12)
13
Adam Langleyfa50e742014-04-09 13:57:52 -070014// Client implements a traditional SSH client that supports shells,
15// subprocesses, port forwarding and tunneled dialing.
16type Client struct {
17 Conn
Han-Wen Nienhuysafdc3052013-06-21 12:46:35 -040018
Adam Langleyfa50e742014-04-09 13:57:52 -070019 forwards forwardList // forwarded tcpip connections from the remote side
20 mu sync.Mutex
21 channelHandlers map[string]chan NewChannel
Dave Cheneyb4b42222012-05-01 15:43:58 +100022}
23
Adam Langleyfa50e742014-04-09 13:57:52 -070024// HandleChannelOpen returns a channel on which NewChannel requests
25// for the given type are sent. If the type already is being handled,
26// nil is returned. The channel is closed when the connection is closed.
27func (c *Client) HandleChannelOpen(channelType string) <-chan NewChannel {
28 c.mu.Lock()
29 defer c.mu.Unlock()
30 if c.channelHandlers == nil {
31 // The SSH channel has been closed.
32 c := make(chan NewChannel)
33 close(c)
34 return c
35 }
36
37 ch := c.channelHandlers[channelType]
38 if ch != nil {
39 return nil
40 }
41
42 ch = make(chan NewChannel, 16)
43 c.channelHandlers[channelType] = ch
44 return ch
Russ Cox470549d2012-01-25 15:31:12 -050045}
46
Adam Langleyfa50e742014-04-09 13:57:52 -070047// NewClient creates a Client on top of the given connection.
48func NewClient(c Conn, chans <-chan NewChannel, reqs <-chan *Request) *Client {
49 conn := &Client{
50 Conn: c,
51 channelHandlers: make(map[string]chan NewChannel, 1),
52 }
53
54 go conn.handleGlobalRequests(reqs)
55 go conn.handleChannelOpens(chans)
56 go func() {
57 conn.Wait()
58 conn.forwards.closeAll()
59 }()
60 go conn.forwards.handleChannels(conn.HandleChannelOpen("forwarded-tcpip"))
61 return conn
Han-Wen Nienhuysafdc3052013-06-21 12:46:35 -040062}
63
Adam Langleyfa50e742014-04-09 13:57:52 -070064// NewClientConn establishes an authenticated SSH connection using c
65// as the underlying transport. The Request and NewChannel channels
66// must be serviced or the connection will hang.
67func NewClientConn(c net.Conn, addr string, config *ClientConfig) (Conn, <-chan NewChannel, <-chan *Request, error) {
68 fullConf := *config
69 fullConf.SetDefaults()
70 conn := &connection{
71 sshConn: sshConn{conn: c},
Russ Cox470549d2012-01-25 15:31:12 -050072 }
Han-Wen Nienhuysafdc3052013-06-21 12:46:35 -040073
Adam Langleyfa50e742014-04-09 13:57:52 -070074 if err := conn.clientHandshake(addr, &fullConf); err != nil {
75 c.Close()
76 return nil, nil, nil, fmt.Errorf("ssh: handshake failed: %v", err)
Russ Cox470549d2012-01-25 15:31:12 -050077 }
Adam Langleyfa50e742014-04-09 13:57:52 -070078 conn.mux = newMux(conn.transport)
79 return conn, conn.mux.incomingChannels, conn.mux.incomingRequests, nil
Russ Cox470549d2012-01-25 15:31:12 -050080}
81
Adam Langleyfa50e742014-04-09 13:57:52 -070082// clientHandshake performs the client side key exchange. See RFC 4253 Section
83// 7.
84func (c *connection) clientHandshake(dialAddress string, config *ClientConfig) error {
Adam Langleyfa50e742014-04-09 13:57:52 -070085 if config.ClientVersion != "" {
86 c.clientVersion = []byte(config.ClientVersion)
Kristopher Watts280be002014-12-18 21:06:17 -070087 } else {
88 c.clientVersion = []byte(packageVersion)
JP Sugarbroadc2c80b62013-08-27 13:40:08 -040089 }
Adam Langleyfa50e742014-04-09 13:57:52 -070090 var err error
91 c.serverVersion, err = exchangeVersions(c.sshConn.conn, c.clientVersion)
Russ Cox470549d2012-01-25 15:31:12 -050092 if err != nil {
93 return err
94 }
95
Adam Langleyfa50e742014-04-09 13:57:52 -070096 c.transport = newClientTransport(
97 newTransport(c.sshConn.conn, config.Rand, true /* is client */),
98 c.clientVersion, c.serverVersion, config, dialAddress, c.sshConn.RemoteAddr())
99 if err := c.transport.requestKeyChange(); err != nil {
Russ Cox470549d2012-01-25 15:31:12 -0500100 return err
101 }
102
Adam Langleyfa50e742014-04-09 13:57:52 -0700103 if packet, err := c.transport.readPacket(); err != nil {
Russ Cox470549d2012-01-25 15:31:12 -0500104 return err
Adam Langleyfa50e742014-04-09 13:57:52 -0700105 } else if packet[0] != msgNewKeys {
106 return unexpectedMessageError(msgNewKeys, packet[0])
Russ Cox470549d2012-01-25 15:31:12 -0500107 }
Adam Langleyfa50e742014-04-09 13:57:52 -0700108 return c.clientAuthenticate(config)
Russ Cox470549d2012-01-25 15:31:12 -0500109}
110
Adam Langleyfa50e742014-04-09 13:57:52 -0700111// verifyHostKeySignature verifies the host key obtained in the key
112// exchange.
113func verifyHostKeySignature(hostKey PublicKey, result *kexResult) error {
114 sig, rest, ok := parseSignatureBody(result.Signature)
Han-Wen Nienhuysd7d50b02013-08-28 10:50:25 -0400115 if len(rest) > 0 || !ok {
116 return errors.New("ssh: signature parse error")
117 }
Han-Wen Nienhuysd7d50b02013-08-28 10:50:25 -0400118
Adam Langleyfa50e742014-04-09 13:57:52 -0700119 return hostKey.Verify(result.H, sig)
Han-Wen Nienhuysd7d50b02013-08-28 10:50:25 -0400120}
121
Adam Langleyfa50e742014-04-09 13:57:52 -0700122// NewSession opens a new Session for this client. (A session is a remote
123// execution of a program.)
124func (c *Client) NewSession() (*Session, error) {
125 ch, in, err := c.OpenChannel("session", nil)
126 if err != nil {
Dave Cheneyb4b42222012-05-01 15:43:58 +1000127 return nil, err
128 }
Adam Langleyfa50e742014-04-09 13:57:52 -0700129 return newSession(ch, in)
Dave Cheneyb4b42222012-05-01 15:43:58 +1000130}
131
Adam Langleyfa50e742014-04-09 13:57:52 -0700132func (c *Client) handleGlobalRequests(incoming <-chan *Request) {
133 for r := range incoming {
134 // This handles keepalive messages and matches
135 // the behaviour of OpenSSH.
136 r.Reply(false, nil)
Dave Cheneyb4b42222012-05-01 15:43:58 +1000137 }
Adam Langleyfa50e742014-04-09 13:57:52 -0700138}
139
140// handleChannelOpens channel open messages from the remote side.
141func (c *Client) handleChannelOpens(in <-chan NewChannel) {
142 for ch := range in {
143 c.mu.Lock()
144 handler := c.channelHandlers[ch.ChannelType()]
145 c.mu.Unlock()
146
147 if handler != nil {
148 handler <- ch
149 } else {
150 ch.Reject(UnknownChannelType, fmt.Sprintf("unknown channel type: %v", ch.ChannelType()))
151 }
152 }
153
154 c.mu.Lock()
155 for _, ch := range c.channelHandlers {
156 close(ch)
157 }
158 c.channelHandlers = nil
159 c.mu.Unlock()
Dave Cheneyb4b42222012-05-01 15:43:58 +1000160}
161
Adam Langleyfa50e742014-04-09 13:57:52 -0700162// Dial starts a client connection to the given SSH server. It is a
163// convenience function that connects to the given network address,
164// initiates the SSH handshake, and then sets up a Client. For access
165// to incoming channels and requests, use net.Dial with NewClientConn
166// instead.
167func Dial(network, addr string, config *ClientConfig) (*Client, error) {
Russ Cox470549d2012-01-25 15:31:12 -0500168 conn, err := net.Dial(network, addr)
169 if err != nil {
170 return nil, err
171 }
Adam Langleyfa50e742014-04-09 13:57:52 -0700172 c, chans, reqs, err := NewClientConn(conn, addr, config)
173 if err != nil {
174 return nil, err
175 }
176 return NewClient(c, chans, reqs), nil
Russ Cox470549d2012-01-25 15:31:12 -0500177}
178
Adam Langleyfa50e742014-04-09 13:57:52 -0700179// A ClientConfig structure is used to configure a Client. It must not be
180// modified after having been passed to an SSH function.
Russ Cox470549d2012-01-25 15:31:12 -0500181type ClientConfig struct {
Adam Langleyfa50e742014-04-09 13:57:52 -0700182 // Config contains configuration that is shared between clients and
183 // servers.
184 Config
Russ Cox470549d2012-01-25 15:31:12 -0500185
Adam Langleyfa50e742014-04-09 13:57:52 -0700186 // User contains the username to authenticate as.
Russ Cox470549d2012-01-25 15:31:12 -0500187 User string
188
Adam Langleyfa50e742014-04-09 13:57:52 -0700189 // Auth contains possible authentication methods to use with the
190 // server. Only the first instance of a particular RFC 4252 method will
191 // be used during authentication.
192 Auth []AuthMethod
Russ Cox470549d2012-01-25 15:31:12 -0500193
Adam Langleyfa50e742014-04-09 13:57:52 -0700194 // HostKeyCallback, if not nil, is called during the cryptographic
195 // handshake to validate the server's host key. A nil HostKeyCallback
Han-Wen Nienhuysafdc3052013-06-21 12:46:35 -0400196 // implies that all host keys are accepted.
Adam Langleyfa50e742014-04-09 13:57:52 -0700197 HostKeyCallback func(hostname string, remote net.Addr, key PublicKey) error
Han-Wen Nienhuysafdc3052013-06-21 12:46:35 -0400198
Adam Langleyfa50e742014-04-09 13:57:52 -0700199 // ClientVersion contains the version identification string that will
200 // be used for the connection. If empty, a reasonable default is used.
JP Sugarbroadc2c80b62013-08-27 13:40:08 -0400201 ClientVersion string
Russ Cox470549d2012-01-25 15:31:12 -0500202}