net-ssh 5.0.0.beta1 → 5.0.0.beta2

Sign up to get free protection for your applications and to get access to all the features.
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