net-ssh 5.0.0.beta1 → 5.0.0.beta2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/.rubocop_todo.yml +98 -258
  5. data/CHANGES.txt +8 -0
  6. data/Gemfile +1 -3
  7. data/Rakefile +37 -39
  8. data/lib/net/ssh.rb +26 -25
  9. data/lib/net/ssh/authentication/agent.rb +228 -225
  10. data/lib/net/ssh/authentication/certificate.rb +166 -164
  11. data/lib/net/ssh/authentication/constants.rb +17 -14
  12. data/lib/net/ssh/authentication/ed25519.rb +107 -104
  13. data/lib/net/ssh/authentication/ed25519_loader.rb +32 -28
  14. data/lib/net/ssh/authentication/key_manager.rb +5 -3
  15. data/lib/net/ssh/authentication/methods/abstract.rb +53 -47
  16. data/lib/net/ssh/authentication/methods/hostbased.rb +32 -33
  17. data/lib/net/ssh/authentication/methods/keyboard_interactive.rb +2 -4
  18. data/lib/net/ssh/authentication/methods/none.rb +10 -10
  19. data/lib/net/ssh/authentication/methods/password.rb +13 -13
  20. data/lib/net/ssh/authentication/methods/publickey.rb +54 -55
  21. data/lib/net/ssh/authentication/pageant.rb +468 -465
  22. data/lib/net/ssh/authentication/pub_key_fingerprint.rb +44 -0
  23. data/lib/net/ssh/authentication/session.rb +127 -123
  24. data/lib/net/ssh/buffer.rb +305 -303
  25. data/lib/net/ssh/buffered_io.rb +163 -162
  26. data/lib/net/ssh/config.rb +230 -227
  27. data/lib/net/ssh/connection/channel.rb +659 -654
  28. data/lib/net/ssh/connection/constants.rb +30 -26
  29. data/lib/net/ssh/connection/event_loop.rb +108 -104
  30. data/lib/net/ssh/connection/keepalive.rb +54 -50
  31. data/lib/net/ssh/connection/session.rb +677 -678
  32. data/lib/net/ssh/connection/term.rb +180 -176
  33. data/lib/net/ssh/errors.rb +101 -99
  34. data/lib/net/ssh/key_factory.rb +108 -108
  35. data/lib/net/ssh/known_hosts.rb +148 -154
  36. data/lib/net/ssh/loggable.rb +56 -54
  37. data/lib/net/ssh/packet.rb +82 -78
  38. data/lib/net/ssh/prompt.rb +55 -53
  39. data/lib/net/ssh/proxy/command.rb +103 -102
  40. data/lib/net/ssh/proxy/errors.rb +12 -8
  41. data/lib/net/ssh/proxy/http.rb +92 -91
  42. data/lib/net/ssh/proxy/https.rb +42 -39
  43. data/lib/net/ssh/proxy/jump.rb +50 -47
  44. data/lib/net/ssh/proxy/socks4.rb +0 -2
  45. data/lib/net/ssh/proxy/socks5.rb +11 -11
  46. data/lib/net/ssh/ruby_compat.rb +1 -0
  47. data/lib/net/ssh/service/forward.rb +364 -362
  48. data/lib/net/ssh/test.rb +85 -83
  49. data/lib/net/ssh/test/channel.rb +146 -142
  50. data/lib/net/ssh/test/extensions.rb +148 -146
  51. data/lib/net/ssh/test/kex.rb +35 -31
  52. data/lib/net/ssh/test/local_packet.rb +48 -44
  53. data/lib/net/ssh/test/packet.rb +87 -84
  54. data/lib/net/ssh/test/remote_packet.rb +35 -31
  55. data/lib/net/ssh/test/script.rb +173 -171
  56. data/lib/net/ssh/test/socket.rb +59 -55
  57. data/lib/net/ssh/transport/algorithms.rb +413 -412
  58. data/lib/net/ssh/transport/cipher_factory.rb +108 -105
  59. data/lib/net/ssh/transport/constants.rb +35 -31
  60. data/lib/net/ssh/transport/ctr.rb +1 -1
  61. data/lib/net/ssh/transport/hmac.rb +1 -1
  62. data/lib/net/ssh/transport/hmac/abstract.rb +67 -64
  63. data/lib/net/ssh/transport/hmac/sha2_256_96.rb +1 -1
  64. data/lib/net/ssh/transport/hmac/sha2_512_96.rb +1 -1
  65. data/lib/net/ssh/transport/identity_cipher.rb +55 -51
  66. data/lib/net/ssh/transport/kex.rb +2 -4
  67. data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha1.rb +47 -40
  68. data/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb +201 -197
  69. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb +53 -56
  70. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp256.rb +94 -87
  71. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp384.rb +17 -10
  72. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp521.rb +17 -10
  73. data/lib/net/ssh/transport/key_expander.rb +29 -25
  74. data/lib/net/ssh/transport/openssl.rb +17 -30
  75. data/lib/net/ssh/transport/packet_stream.rb +193 -192
  76. data/lib/net/ssh/transport/server_version.rb +64 -66
  77. data/lib/net/ssh/transport/session.rb +286 -284
  78. data/lib/net/ssh/transport/state.rb +198 -196
  79. data/lib/net/ssh/verifiers/lenient.rb +29 -25
  80. data/lib/net/ssh/verifiers/null.rb +13 -9
  81. data/lib/net/ssh/verifiers/secure.rb +45 -45
  82. data/lib/net/ssh/verifiers/strict.rb +20 -16
  83. data/lib/net/ssh/version.rb +55 -53
  84. data/net-ssh.gemspec +4 -4
  85. data/support/ssh_tunnel_bug.rb +2 -2
  86. metadata +25 -24
  87. metadata.gz.sig +0 -0
@@ -6,243 +6,244 @@ require 'net/ssh/transport/cipher_factory'
6
6
  require 'net/ssh/transport/hmac'
7
7
  require 'net/ssh/transport/state'
8
8
 
9
+ module Net
10
+ module SSH
11
+ module Transport
9
12
 
10
- module Net; module SSH; module Transport
13
+ # A module that builds additional functionality onto the Net::SSH::BufferedIo
14
+ # module. It adds SSH encryption, compression, and packet validation, as
15
+ # per the SSH2 protocol. It also adds an abstraction for polling packets,
16
+ # to allow for both blocking and non-blocking reads.
17
+ module PacketStream
18
+ PROXY_COMMAND_HOST_IP = '<no hostip for proxy command>'.freeze
11
19
 
12
- # A module that builds additional functionality onto the Net::SSH::BufferedIo
13
- # module. It adds SSH encryption, compression, and packet validation, as
14
- # per the SSH2 protocol. It also adds an abstraction for polling packets,
15
- # to allow for both blocking and non-blocking reads.
16
- module PacketStream
17
- PROXY_COMMAND_HOST_IP = '<no hostip for proxy command>'.freeze
20
+ include BufferedIo
18
21
 
19
- include BufferedIo
20
-
21
- def self.extended(object)
22
- object.__send__(:initialize_ssh)
23
- end
22
+ def self.extended(object)
23
+ object.__send__(:initialize_ssh)
24
+ end
24
25
 
25
- # The map of "hints" that can be used to modify the behavior of the packet
26
- # stream. For instance, when authentication succeeds, an "authenticated"
27
- # hint is set, which is used to determine whether or not to compress the
28
- # data when using the "delayed" compression algorithm.
29
- attr_reader :hints
30
-
31
- # The server state object, which encapsulates the algorithms used to interpret
32
- # packets coming from the server.
33
- attr_reader :server
34
-
35
- # The client state object, which encapsulates the algorithms used to build
36
- # packets to send to the server.
37
- attr_reader :client
38
-
39
- # The name of the client (local) end of the socket, as reported by the
40
- # socket.
41
- def client_name
42
- @client_name ||= begin
43
- sockaddr = getsockname
44
- begin
45
- Socket.getnameinfo(sockaddr, Socket::NI_NAMEREQD).first
46
- rescue
47
- begin
48
- Socket.getnameinfo(sockaddr).first
49
- rescue
26
+ # The map of "hints" that can be used to modify the behavior of the packet
27
+ # stream. For instance, when authentication succeeds, an "authenticated"
28
+ # hint is set, which is used to determine whether or not to compress the
29
+ # data when using the "delayed" compression algorithm.
30
+ attr_reader :hints
31
+
32
+ # The server state object, which encapsulates the algorithms used to interpret
33
+ # packets coming from the server.
34
+ attr_reader :server
35
+
36
+ # The client state object, which encapsulates the algorithms used to build
37
+ # packets to send to the server.
38
+ attr_reader :client
39
+
40
+ # The name of the client (local) end of the socket, as reported by the
41
+ # socket.
42
+ def client_name
43
+ @client_name ||= begin
44
+ sockaddr = getsockname
50
45
  begin
51
- Socket.gethostbyname(Socket.gethostname).first
52
- rescue
53
- lwarn { "the client ipaddr/name could not be determined" }
54
- "unknown"
46
+ Socket.getnameinfo(sockaddr, Socket::NI_NAMEREQD).first
47
+ rescue StandardError
48
+ begin
49
+ Socket.getnameinfo(sockaddr).first
50
+ rescue StandardError
51
+ begin
52
+ Socket.gethostbyname(Socket.gethostname).first
53
+ rescue StandardError
54
+ lwarn { "the client ipaddr/name could not be determined" }
55
+ "unknown"
56
+ end
57
+ end
55
58
  end
56
59
  end
57
60
  end
58
- end
59
- end
60
61
 
61
- # The IP address of the peer (remote) end of the socket, as reported by
62
- # the socket.
63
- def peer_ip
64
- @peer_ip ||=
65
- if respond_to?(:getpeername)
66
- addr = getpeername
67
- Socket.getnameinfo(addr, Socket::NI_NUMERICHOST | Socket::NI_NUMERICSERV).first
68
- else
69
- PROXY_COMMAND_HOST_IP
62
+ # The IP address of the peer (remote) end of the socket, as reported by
63
+ # the socket.
64
+ def peer_ip
65
+ @peer_ip ||=
66
+ if respond_to?(:getpeername)
67
+ addr = getpeername
68
+ Socket.getnameinfo(addr, Socket::NI_NUMERICHOST | Socket::NI_NUMERICSERV).first
69
+ else
70
+ PROXY_COMMAND_HOST_IP
71
+ end
70
72
  end
71
- end
72
73
 
73
- # Returns true if the IO is available for reading, and false otherwise.
74
- def available_for_read?
75
- result = IO.select([self], nil, nil, 0)
76
- result && result.first.any?
77
- end
74
+ # Returns true if the IO is available for reading, and false otherwise.
75
+ def available_for_read?
76
+ result = IO.select([self], nil, nil, 0)
77
+ result && result.first.any?
78
+ end
78
79
 
79
- # Returns the next full packet. If the mode parameter is :nonblock (the
80
- # default), then this will return immediately, whether a packet is
81
- # available or not, and will return nil if there is no packet ready to be
82
- # returned. If the mode parameter is :block, then this method will block
83
- # until a packet is available.
84
- def next_packet(mode=:nonblock)
85
- case mode
86
- when :nonblock then
87
- packet = poll_next_packet
88
- return packet if packet
89
-
90
- if available_for_read?
91
- if fill <= 0
92
- result = poll_next_packet
93
- if result.nil?
94
- raise Net::SSH::Disconnect, "connection closed by remote host"
95
- else
96
- return result
80
+ # Returns the next full packet. If the mode parameter is :nonblock (the
81
+ # default), then this will return immediately, whether a packet is
82
+ # available or not, and will return nil if there is no packet ready to be
83
+ # returned. If the mode parameter is :block, then this method will block
84
+ # until a packet is available.
85
+ def next_packet(mode=:nonblock)
86
+ case mode
87
+ when :nonblock then
88
+ packet = poll_next_packet
89
+ return packet if packet
90
+
91
+ if available_for_read?
92
+ if fill <= 0
93
+ result = poll_next_packet
94
+ if result.nil?
95
+ raise Net::SSH::Disconnect, "connection closed by remote host"
96
+ else
97
+ return result
98
+ end
99
+ end
97
100
  end
98
- end
99
- end
100
- poll_next_packet
101
+ poll_next_packet
101
102
 
102
- when :block then
103
- loop do
104
- packet = poll_next_packet
105
- return packet if packet
103
+ when :block then
104
+ loop do
105
+ packet = poll_next_packet
106
+ return packet if packet
106
107
 
107
- loop do
108
- result = IO.select([self]) or next
109
- break if result.first.any?
110
- end
108
+ loop do
109
+ result = IO.select([self]) or next
110
+ break if result.first.any?
111
+ end
112
+
113
+ raise Net::SSH::Disconnect, "connection closed by remote host" if fill <= 0
114
+ end
111
115
 
112
- if fill <= 0
113
- raise Net::SSH::Disconnect, "connection closed by remote host"
116
+ else
117
+ raise ArgumentError, "expected :block or :nonblock, got #{mode.inspect}"
114
118
  end
115
119
  end
116
120
 
117
- else
118
- raise ArgumentError, "expected :block or :nonblock, got #{mode.inspect}"
119
- end
120
- end
121
-
122
- # Enqueues a packet to be sent, and blocks until the entire packet is
123
- # sent.
124
- def send_packet(payload)
125
- enqueue_packet(payload)
126
- wait_for_pending_sends
127
- end
128
-
129
- # Enqueues a packet to be sent, but does not immediately send the packet.
130
- # The given payload is pre-processed according to the algorithms specified
131
- # in the client state (compression, cipher, and hmac).
132
- def enqueue_packet(payload)
133
- # try to compress the packet
134
- payload = client.compress(payload)
121
+ # Enqueues a packet to be sent, and blocks until the entire packet is
122
+ # sent.
123
+ def send_packet(payload)
124
+ enqueue_packet(payload)
125
+ wait_for_pending_sends
126
+ end
135
127
 
136
- # the length of the packet, minus the padding
137
- actual_length = 4 + payload.bytesize + 1
128
+ # Enqueues a packet to be sent, but does not immediately send the packet.
129
+ # The given payload is pre-processed according to the algorithms specified
130
+ # in the client state (compression, cipher, and hmac).
131
+ def enqueue_packet(payload)
132
+ # try to compress the packet
133
+ payload = client.compress(payload)
138
134
 
139
- # compute the padding length
140
- padding_length = client.block_size - (actual_length % client.block_size)
141
- padding_length += client.block_size if padding_length < 4
135
+ # the length of the packet, minus the padding
136
+ actual_length = 4 + payload.bytesize + 1
142
137
 
143
- # compute the packet length (sans the length field itself)
144
- packet_length = payload.bytesize + padding_length + 1
138
+ # compute the padding length
139
+ padding_length = client.block_size - (actual_length % client.block_size)
140
+ padding_length += client.block_size if padding_length < 4
145
141
 
146
- if packet_length < 16
147
- padding_length += client.block_size
148
- packet_length = payload.bytesize + padding_length + 1
149
- end
142
+ # compute the packet length (sans the length field itself)
143
+ packet_length = payload.bytesize + padding_length + 1
150
144
 
151
- padding = Array.new(padding_length) { rand(256) }.pack("C*")
145
+ if packet_length < 16
146
+ padding_length += client.block_size
147
+ packet_length = payload.bytesize + padding_length + 1
148
+ end
152
149
 
153
- unencrypted_data = [packet_length, padding_length, payload, padding].pack("NCA*A*")
154
- mac = client.hmac.digest([client.sequence_number, unencrypted_data].pack("NA*"))
150
+ padding = Array.new(padding_length) { rand(256) }.pack("C*")
155
151
 
156
- encrypted_data = client.update_cipher(unencrypted_data) << client.final_cipher
157
- message = encrypted_data + mac
152
+ unencrypted_data = [packet_length, padding_length, payload, padding].pack("NCA*A*")
153
+ mac = client.hmac.digest([client.sequence_number, unencrypted_data].pack("NA*"))
158
154
 
159
- debug { "queueing packet nr #{client.sequence_number} type #{payload.getbyte(0)} len #{packet_length}" }
160
- enqueue(message)
155
+ encrypted_data = client.update_cipher(unencrypted_data) << client.final_cipher
156
+ message = encrypted_data + mac
161
157
 
162
- client.increment(packet_length)
158
+ debug { "queueing packet nr #{client.sequence_number} type #{payload.getbyte(0)} len #{packet_length}" }
159
+ enqueue(message)
163
160
 
164
- self
165
- end
161
+ client.increment(packet_length)
166
162
 
167
- # Performs any pending cleanup necessary on the IO and its associated
168
- # state objects. (See State#cleanup).
169
- def cleanup
170
- client.cleanup
171
- server.cleanup
172
- end
163
+ self
164
+ end
173
165
 
174
- # If the IO object requires a rekey operation (as indicated by either its
175
- # client or server state objects, see State#needs_rekey?), this will
176
- # yield. Otherwise, this does nothing.
177
- def if_needs_rekey?
178
- if client.needs_rekey? || server.needs_rekey?
179
- yield
180
- client.reset! if client.needs_rekey?
181
- server.reset! if server.needs_rekey?
182
- end
183
- end
166
+ # Performs any pending cleanup necessary on the IO and its associated
167
+ # state objects. (See State#cleanup).
168
+ def cleanup
169
+ client.cleanup
170
+ server.cleanup
171
+ end
184
172
 
185
- protected
173
+ # If the IO object requires a rekey operation (as indicated by either its
174
+ # client or server state objects, see State#needs_rekey?), this will
175
+ # yield. Otherwise, this does nothing.
176
+ def if_needs_rekey?
177
+ if client.needs_rekey? || server.needs_rekey?
178
+ yield
179
+ client.reset! if client.needs_rekey?
180
+ server.reset! if server.needs_rekey?
181
+ end
182
+ end
186
183
 
187
- # Called when this module is used to extend an object. It initializes
188
- # the states and generally prepares the object for use as a packet stream.
189
- def initialize_ssh
190
- @hints = {}
191
- @server = State.new(self, :server)
192
- @client = State.new(self, :client)
193
- @packet = nil
194
- initialize_buffered_io
195
- end
184
+ protected
196
185
 
197
- # Tries to read the next packet. If there is insufficient data to read
198
- # an entire packet, this returns immediately, otherwise the packet is
199
- # read, post-processed according to the cipher, hmac, and compression
200
- # algorithms specified in the server state object, and returned as a
201
- # new Packet object.
202
- def poll_next_packet
203
- if @packet.nil?
204
- minimum = server.block_size < 4 ? 4 : server.block_size
205
- return nil if available < minimum
206
- data = read_available(minimum)
207
-
208
- # decipher it
209
- @packet = Net::SSH::Buffer.new(server.update_cipher(data))
210
- @packet_length = @packet.read_long
186
+ # Called when this module is used to extend an object. It initializes
187
+ # the states and generally prepares the object for use as a packet stream.
188
+ def initialize_ssh
189
+ @hints = {}
190
+ @server = State.new(self, :server)
191
+ @client = State.new(self, :client)
192
+ @packet = nil
193
+ initialize_buffered_io
211
194
  end
212
195
 
213
- need = @packet_length + 4 - server.block_size
214
- raise Net::SSH::Exception, "padding error, need #{need} block #{server.block_size}" if need % server.block_size != 0
196
+ # Tries to read the next packet. If there is insufficient data to read
197
+ # an entire packet, this returns immediately, otherwise the packet is
198
+ # read, post-processed according to the cipher, hmac, and compression
199
+ # algorithms specified in the server state object, and returned as a
200
+ # new Packet object.
201
+ def poll_next_packet
202
+ if @packet.nil?
203
+ minimum = server.block_size < 4 ? 4 : server.block_size
204
+ return nil if available < minimum
205
+ data = read_available(minimum)
206
+
207
+ # decipher it
208
+ @packet = Net::SSH::Buffer.new(server.update_cipher(data))
209
+ @packet_length = @packet.read_long
210
+ end
215
211
 
216
- return nil if available < need + server.hmac.mac_length
212
+ need = @packet_length + 4 - server.block_size
213
+ raise Net::SSH::Exception, "padding error, need #{need} block #{server.block_size}" if need % server.block_size != 0
217
214
 
218
- if need > 0
219
- # read the remainder of the packet and decrypt it.
220
- data = read_available(need)
221
- @packet.append(server.update_cipher(data))
222
- end
215
+ return nil if available < need + server.hmac.mac_length
216
+
217
+ if need > 0
218
+ # read the remainder of the packet and decrypt it.
219
+ data = read_available(need)
220
+ @packet.append(server.update_cipher(data))
221
+ end
223
222
 
224
- # get the hmac from the tail of the packet (if one exists), and
225
- # then validate it.
226
- real_hmac = read_available(server.hmac.mac_length) || ""
223
+ # get the hmac from the tail of the packet (if one exists), and
224
+ # then validate it.
225
+ real_hmac = read_available(server.hmac.mac_length) || ""
227
226
 
228
- @packet.append(server.final_cipher)
229
- padding_length = @packet.read_byte
227
+ @packet.append(server.final_cipher)
228
+ padding_length = @packet.read_byte
230
229
 
231
- payload = @packet.read(@packet_length - padding_length - 1)
230
+ payload = @packet.read(@packet_length - padding_length - 1)
232
231
 
233
- my_computed_hmac = server.hmac.digest([server.sequence_number, @packet.content].pack("NA*"))
234
- raise Net::SSH::Exception, "corrupted mac detected" if real_hmac != my_computed_hmac
232
+ my_computed_hmac = server.hmac.digest([server.sequence_number, @packet.content].pack("NA*"))
233
+ raise Net::SSH::Exception, "corrupted hmac detected" if real_hmac != my_computed_hmac
235
234
 
236
- # try to decompress the payload, in case compression is active
237
- payload = server.decompress(payload)
235
+ # try to decompress the payload, in case compression is active
236
+ payload = server.decompress(payload)
238
237
 
239
- debug { "received packet nr #{server.sequence_number} type #{payload.getbyte(0)} len #{@packet_length}" }
238
+ debug { "received packet nr #{server.sequence_number} type #{payload.getbyte(0)} len #{@packet_length}" }
240
239
 
241
- server.increment(@packet_length)
242
- @packet = nil
240
+ server.increment(@packet_length)
241
+ @packet = nil
243
242
 
244
- return Packet.new(payload)
243
+ return Packet.new(payload)
244
+ end
245
245
  end
246
- end
247
246
 
248
- end; end; end
247
+ end
248
+ end
249
+ end
@@ -2,77 +2,75 @@ require 'net/ssh/errors'
2
2
  require 'net/ssh/loggable'
3
3
  require 'net/ssh/version'
4
4
 
5
- module Net; module SSH; module Transport
5
+ module Net
6
+ module SSH
7
+ module Transport
6
8
 
7
- # Negotiates the SSH protocol version and trades information about server
8
- # and client. This is never used directly--it is always called by the
9
- # transport layer as part of the initialization process of the transport
10
- # layer.
11
- #
12
- # Note that this class also encapsulates the negotiated version, and acts as
13
- # the authoritative reference for any queries regarding the version in effect.
14
- class ServerVersion
15
- include Loggable
16
-
17
- # The SSH version string as reported by Net::SSH
18
- PROTO_VERSION = "SSH-2.0-Ruby/Net::SSH_#{Net::SSH::Version::CURRENT} #{RUBY_PLATFORM}"
19
-
20
- # Any header text sent by the server prior to sending the version.
21
- attr_reader :header
22
-
23
- # The version string reported by the server.
24
- attr_reader :version
25
-
26
- # Instantiates a new ServerVersion and immediately (and synchronously)
27
- # negotiates the SSH protocol in effect, using the given socket.
28
- def initialize(socket, logger, timeout = nil)
29
- @header = ""
30
- @version = nil
31
- @logger = logger
32
- negotiate!(socket, timeout)
33
- end
34
-
35
- private
36
-
37
- # Negotiates the SSH protocol to use, via the given socket. If the server
38
- # reports an incompatible SSH version (e.g., SSH1), this will raise an
39
- # exception.
40
- def negotiate!(socket, timeout)
41
- info { "negotiating protocol version" }
42
-
43
- debug { "local is `#{PROTO_VERSION}'" }
44
- socket.write "#{PROTO_VERSION}\r\n"
45
- socket.flush
46
-
47
- if timeout && !IO.select([socket], nil, nil, timeout)
48
- raise Net::SSH::ConnectionTimeout, "timeout during server version negotiating"
9
+ # Negotiates the SSH protocol version and trades information about server
10
+ # and client. This is never used directly--it is always called by the
11
+ # transport layer as part of the initialization process of the transport
12
+ # layer.
13
+ #
14
+ # Note that this class also encapsulates the negotiated version, and acts as
15
+ # the authoritative reference for any queries regarding the version in effect.
16
+ class ServerVersion
17
+ include Loggable
18
+
19
+ # The SSH version string as reported by Net::SSH
20
+ PROTO_VERSION = "SSH-2.0-Ruby/Net::SSH_#{Net::SSH::Version::CURRENT} #{RUBY_PLATFORM}"
21
+
22
+ # Any header text sent by the server prior to sending the version.
23
+ attr_reader :header
24
+
25
+ # The version string reported by the server.
26
+ attr_reader :version
27
+
28
+ # Instantiates a new ServerVersion and immediately (and synchronously)
29
+ # negotiates the SSH protocol in effect, using the given socket.
30
+ def initialize(socket, logger, timeout = nil)
31
+ @header = ""
32
+ @version = nil
33
+ @logger = logger
34
+ negotiate!(socket, timeout)
49
35
  end
50
- loop do
51
- @version = ""
36
+
37
+ private
38
+
39
+ # Negotiates the SSH protocol to use, via the given socket. If the server
40
+ # reports an incompatible SSH version (e.g., SSH1), this will raise an
41
+ # exception.
42
+ def negotiate!(socket, timeout)
43
+ info { "negotiating protocol version" }
44
+
45
+ debug { "local is `#{PROTO_VERSION}'" }
46
+ socket.write "#{PROTO_VERSION}\r\n"
47
+ socket.flush
48
+
49
+ raise Net::SSH::ConnectionTimeout, "timeout during server version negotiating" if timeout && !IO.select([socket], nil, nil, timeout)
52
50
  loop do
53
- begin
54
- b = socket.readpartial(1)
55
- raise Net::SSH::Disconnect, "connection closed by remote host" if b.nil?
56
- rescue EOFError
57
- raise Net::SSH::Disconnect, "connection closed by remote host"
51
+ @version = ""
52
+ loop do
53
+ begin
54
+ b = socket.readpartial(1)
55
+ raise Net::SSH::Disconnect, "connection closed by remote host" if b.nil?
56
+ rescue EOFError
57
+ raise Net::SSH::Disconnect, "connection closed by remote host"
58
+ end
59
+ @version << b
60
+ break if b == "\n"
58
61
  end
59
- @version << b
60
- break if b == "\n"
62
+ break if @version.match(/^SSH-/)
63
+ @header << @version
61
64
  end
62
- break if @version.match(/^SSH-/)
63
- @header << @version
64
- end
65
-
66
- @version.chomp!
67
- debug { "remote is `#{@version}'" }
68
-
69
- unless @version.match(/^SSH-(1\.99|2\.0)-/)
70
- raise Net::SSH::Exception, "incompatible SSH version `#{@version}'"
71
- end
72
-
73
- if timeout && !IO.select(nil, [socket], nil, timeout)
74
- raise Net::SSH::ConnectionTimeout, "timeout during client version negotiating"
65
+
66
+ @version.chomp!
67
+ debug { "remote is `#{@version}'" }
68
+
69
+ raise Net::SSH::Exception, "incompatible SSH version `#{@version}'" unless @version.match(/^SSH-(1\.99|2\.0)-/)
70
+
71
+ raise Net::SSH::ConnectionTimeout, "timeout during client version negotiating" if timeout && !IO.select(nil, [socket], nil, timeout)
75
72
  end
76
73
  end
74
+ end
77
75
  end
78
- end; end; end
76
+ end