net-ssh-backports 6.3.0.backports
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.
- checksums.yaml +7 -0
- data/.github/workflows/ci.yml +93 -0
- data/.gitignore +13 -0
- data/.rubocop.yml +21 -0
- data/.rubocop_todo.yml +1074 -0
- data/.travis.yml +51 -0
- data/CHANGES.txt +698 -0
- data/Gemfile +13 -0
- data/Gemfile.noed25519 +12 -0
- data/ISSUE_TEMPLATE.md +30 -0
- data/LICENSE.txt +19 -0
- data/Manifest +132 -0
- data/README.md +287 -0
- data/Rakefile +105 -0
- data/THANKS.txt +110 -0
- data/appveyor.yml +58 -0
- data/lib/net/ssh/authentication/agent.rb +284 -0
- data/lib/net/ssh/authentication/certificate.rb +183 -0
- data/lib/net/ssh/authentication/constants.rb +20 -0
- data/lib/net/ssh/authentication/ed25519.rb +185 -0
- data/lib/net/ssh/authentication/ed25519_loader.rb +31 -0
- data/lib/net/ssh/authentication/key_manager.rb +297 -0
- data/lib/net/ssh/authentication/methods/abstract.rb +69 -0
- data/lib/net/ssh/authentication/methods/hostbased.rb +72 -0
- data/lib/net/ssh/authentication/methods/keyboard_interactive.rb +77 -0
- data/lib/net/ssh/authentication/methods/none.rb +34 -0
- data/lib/net/ssh/authentication/methods/password.rb +80 -0
- data/lib/net/ssh/authentication/methods/publickey.rb +95 -0
- data/lib/net/ssh/authentication/pageant.rb +497 -0
- data/lib/net/ssh/authentication/pub_key_fingerprint.rb +43 -0
- data/lib/net/ssh/authentication/session.rb +163 -0
- data/lib/net/ssh/buffer.rb +434 -0
- data/lib/net/ssh/buffered_io.rb +202 -0
- data/lib/net/ssh/config.rb +406 -0
- data/lib/net/ssh/connection/channel.rb +695 -0
- data/lib/net/ssh/connection/constants.rb +33 -0
- data/lib/net/ssh/connection/event_loop.rb +123 -0
- data/lib/net/ssh/connection/keepalive.rb +59 -0
- data/lib/net/ssh/connection/session.rb +712 -0
- data/lib/net/ssh/connection/term.rb +180 -0
- data/lib/net/ssh/errors.rb +106 -0
- data/lib/net/ssh/key_factory.rb +218 -0
- data/lib/net/ssh/known_hosts.rb +264 -0
- data/lib/net/ssh/loggable.rb +62 -0
- data/lib/net/ssh/packet.rb +106 -0
- data/lib/net/ssh/prompt.rb +62 -0
- data/lib/net/ssh/proxy/command.rb +123 -0
- data/lib/net/ssh/proxy/errors.rb +16 -0
- data/lib/net/ssh/proxy/http.rb +98 -0
- data/lib/net/ssh/proxy/https.rb +50 -0
- data/lib/net/ssh/proxy/jump.rb +54 -0
- data/lib/net/ssh/proxy/socks4.rb +67 -0
- data/lib/net/ssh/proxy/socks5.rb +140 -0
- data/lib/net/ssh/service/forward.rb +426 -0
- data/lib/net/ssh/test/channel.rb +147 -0
- data/lib/net/ssh/test/extensions.rb +173 -0
- data/lib/net/ssh/test/kex.rb +46 -0
- data/lib/net/ssh/test/local_packet.rb +53 -0
- data/lib/net/ssh/test/packet.rb +101 -0
- data/lib/net/ssh/test/remote_packet.rb +40 -0
- data/lib/net/ssh/test/script.rb +180 -0
- data/lib/net/ssh/test/socket.rb +65 -0
- data/lib/net/ssh/test.rb +94 -0
- data/lib/net/ssh/transport/algorithms.rb +502 -0
- data/lib/net/ssh/transport/cipher_factory.rb +103 -0
- data/lib/net/ssh/transport/constants.rb +40 -0
- data/lib/net/ssh/transport/ctr.rb +115 -0
- data/lib/net/ssh/transport/hmac/abstract.rb +97 -0
- data/lib/net/ssh/transport/hmac/md5.rb +10 -0
- data/lib/net/ssh/transport/hmac/md5_96.rb +9 -0
- data/lib/net/ssh/transport/hmac/none.rb +13 -0
- data/lib/net/ssh/transport/hmac/ripemd160.rb +11 -0
- data/lib/net/ssh/transport/hmac/sha1.rb +11 -0
- data/lib/net/ssh/transport/hmac/sha1_96.rb +9 -0
- data/lib/net/ssh/transport/hmac/sha2_256.rb +11 -0
- data/lib/net/ssh/transport/hmac/sha2_256_96.rb +9 -0
- data/lib/net/ssh/transport/hmac/sha2_256_etm.rb +12 -0
- data/lib/net/ssh/transport/hmac/sha2_512.rb +11 -0
- data/lib/net/ssh/transport/hmac/sha2_512_96.rb +9 -0
- data/lib/net/ssh/transport/hmac/sha2_512_etm.rb +12 -0
- data/lib/net/ssh/transport/hmac.rb +47 -0
- data/lib/net/ssh/transport/identity_cipher.rb +57 -0
- data/lib/net/ssh/transport/kex/abstract.rb +130 -0
- data/lib/net/ssh/transport/kex/abstract5656.rb +72 -0
- data/lib/net/ssh/transport/kex/curve25519_sha256.rb +39 -0
- data/lib/net/ssh/transport/kex/curve25519_sha256_loader.rb +30 -0
- data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha1.rb +37 -0
- data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha256.rb +11 -0
- data/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb +122 -0
- data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb +72 -0
- data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha256.rb +11 -0
- data/lib/net/ssh/transport/kex/ecdh_sha2_nistp256.rb +39 -0
- data/lib/net/ssh/transport/kex/ecdh_sha2_nistp384.rb +21 -0
- data/lib/net/ssh/transport/kex/ecdh_sha2_nistp521.rb +21 -0
- data/lib/net/ssh/transport/kex.rb +31 -0
- data/lib/net/ssh/transport/key_expander.rb +30 -0
- data/lib/net/ssh/transport/openssl.rb +253 -0
- data/lib/net/ssh/transport/packet_stream.rb +280 -0
- data/lib/net/ssh/transport/server_version.rb +77 -0
- data/lib/net/ssh/transport/session.rb +354 -0
- data/lib/net/ssh/transport/state.rb +208 -0
- data/lib/net/ssh/verifiers/accept_new.rb +33 -0
- data/lib/net/ssh/verifiers/accept_new_or_local_tunnel.rb +33 -0
- data/lib/net/ssh/verifiers/always.rb +58 -0
- data/lib/net/ssh/verifiers/never.rb +19 -0
- data/lib/net/ssh/version.rb +68 -0
- data/lib/net/ssh.rb +330 -0
- data/net-ssh-public_cert.pem +20 -0
- data/net-ssh.gemspec +44 -0
- data/support/ssh_tunnel_bug.rb +65 -0
- metadata +271 -0
@@ -0,0 +1,434 @@
|
|
1
|
+
require 'net/ssh/transport/openssl'
|
2
|
+
|
3
|
+
require 'net/ssh/authentication/certificate'
|
4
|
+
require 'net/ssh/authentication/ed25519_loader'
|
5
|
+
|
6
|
+
module Net
|
7
|
+
module SSH
|
8
|
+
# Net::SSH::Buffer is a flexible class for building and parsing binary
|
9
|
+
# data packets. It provides a stream-like interface for sequentially
|
10
|
+
# reading data items from the buffer, as well as a useful helper method
|
11
|
+
# for building binary packets given a signature.
|
12
|
+
#
|
13
|
+
# Writing to a buffer always appends to the end, regardless of where the
|
14
|
+
# read cursor is. Reading, on the other hand, always begins at the first
|
15
|
+
# byte of the buffer and increments the read cursor, with subsequent reads
|
16
|
+
# taking up where the last left off.
|
17
|
+
#
|
18
|
+
# As a consumer of the Net::SSH library, you will rarely come into contact
|
19
|
+
# with these buffer objects directly, but it could happen. Also, if you
|
20
|
+
# are ever implementing a protocol on top of SSH (e.g. SFTP), this buffer
|
21
|
+
# class can be quite handy.
|
22
|
+
class Buffer
|
23
|
+
# This is a convenience method for creating and populating a new buffer
|
24
|
+
# from a single command. The arguments must be even in length, with the
|
25
|
+
# first of each pair of arguments being a symbol naming the type of the
|
26
|
+
# data that follows. If the type is :raw, the value is written directly
|
27
|
+
# to the hash.
|
28
|
+
#
|
29
|
+
# b = Buffer.from(:byte, 1, :string, "hello", :raw, "\1\2\3\4")
|
30
|
+
# #-> "\1\0\0\0\5hello\1\2\3\4"
|
31
|
+
#
|
32
|
+
# The supported data types are:
|
33
|
+
#
|
34
|
+
# * :raw => write the next value verbatim (#write)
|
35
|
+
# * :int64 => write an 8-byte integer (#write_int64)
|
36
|
+
# * :long => write a 4-byte integer (#write_long)
|
37
|
+
# * :byte => write a single byte (#write_byte)
|
38
|
+
# * :string => write a 4-byte length followed by character data (#write_string)
|
39
|
+
# * :mstring => same as string, but caller cannot resuse the string, avoids potential duplication (#write_moved)
|
40
|
+
# * :bool => write a single byte, interpreted as a boolean (#write_bool)
|
41
|
+
# * :bignum => write an SSH-encoded bignum (#write_bignum)
|
42
|
+
# * :key => write an SSH-encoded key value (#write_key)
|
43
|
+
#
|
44
|
+
# Any of these, except for :raw, accepts an Array argument, to make it
|
45
|
+
# easier to write multiple values of the same type in a briefer manner.
|
46
|
+
def self.from(*args)
|
47
|
+
raise ArgumentError, "odd number of arguments given" unless args.length % 2 == 0
|
48
|
+
|
49
|
+
buffer = new
|
50
|
+
0.step(args.length - 1, 2) do |index|
|
51
|
+
type = args[index]
|
52
|
+
value = args[index + 1]
|
53
|
+
if type == :raw
|
54
|
+
buffer.append(value.to_s)
|
55
|
+
elsif Array === value
|
56
|
+
buffer.send("write_#{type}", *value)
|
57
|
+
else
|
58
|
+
buffer.send("write_#{type}", value)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
buffer
|
63
|
+
end
|
64
|
+
|
65
|
+
# exposes the raw content of the buffer
|
66
|
+
attr_reader :content
|
67
|
+
|
68
|
+
# the current position of the pointer in the buffer
|
69
|
+
attr_accessor :position
|
70
|
+
|
71
|
+
# Creates a new buffer, initialized to the given content. The position
|
72
|
+
# is initialized to the beginning of the buffer.
|
73
|
+
def initialize(content=String.new)
|
74
|
+
@content = content.to_s
|
75
|
+
@position = 0
|
76
|
+
end
|
77
|
+
|
78
|
+
# Returns the length of the buffer's content.
|
79
|
+
def length
|
80
|
+
@content.length
|
81
|
+
end
|
82
|
+
|
83
|
+
# Returns the number of bytes available to be read (e.g., how many bytes
|
84
|
+
# remain between the current position and the end of the buffer).
|
85
|
+
def available
|
86
|
+
length - position
|
87
|
+
end
|
88
|
+
|
89
|
+
# Returns a copy of the buffer's content.
|
90
|
+
def to_s
|
91
|
+
(@content || "").dup
|
92
|
+
end
|
93
|
+
|
94
|
+
# Compares the contents of the two buffers, returning +true+ only if they
|
95
|
+
# are identical in size and content.
|
96
|
+
def ==(buffer)
|
97
|
+
to_s == buffer.to_s
|
98
|
+
end
|
99
|
+
|
100
|
+
# Returns +true+ if the buffer contains no data (e.g., it is of zero length).
|
101
|
+
def empty?
|
102
|
+
@content.empty?
|
103
|
+
end
|
104
|
+
|
105
|
+
# Resets the pointer to the start of the buffer. Subsequent reads will
|
106
|
+
# begin at position 0.
|
107
|
+
def reset!
|
108
|
+
@position = 0
|
109
|
+
end
|
110
|
+
|
111
|
+
# Returns true if the pointer is at the end of the buffer. Subsequent
|
112
|
+
# reads will return nil, in this case.
|
113
|
+
def eof?
|
114
|
+
@position >= length
|
115
|
+
end
|
116
|
+
|
117
|
+
# Resets the buffer, making it empty. Also, resets the read position to
|
118
|
+
# 0.
|
119
|
+
def clear!
|
120
|
+
@content = String.new
|
121
|
+
@position = 0
|
122
|
+
end
|
123
|
+
|
124
|
+
# Consumes n bytes from the buffer, where n is the current position
|
125
|
+
# unless otherwise specified. This is useful for removing data from the
|
126
|
+
# buffer that has previously been read, when you are expecting more data
|
127
|
+
# to be appended. It helps to keep the size of buffers down when they
|
128
|
+
# would otherwise tend to grow without bound.
|
129
|
+
#
|
130
|
+
# Returns the buffer object itself.
|
131
|
+
def consume!(n=position)
|
132
|
+
if n >= length
|
133
|
+
# optimize for a fairly common case
|
134
|
+
clear!
|
135
|
+
elsif n > 0
|
136
|
+
@content = @content[n..-1] || String.new
|
137
|
+
@position -= n
|
138
|
+
@position = 0 if @position < 0
|
139
|
+
end
|
140
|
+
self
|
141
|
+
end
|
142
|
+
|
143
|
+
# Appends the given text to the end of the buffer. Does not alter the
|
144
|
+
# read position. Returns the buffer object itself.
|
145
|
+
def append(text)
|
146
|
+
@content << text
|
147
|
+
self
|
148
|
+
end
|
149
|
+
|
150
|
+
# Returns all text from the current pointer to the end of the buffer as
|
151
|
+
# a new Net::SSH::Buffer object.
|
152
|
+
def remainder_as_buffer
|
153
|
+
Buffer.new(@content[@position..-1])
|
154
|
+
end
|
155
|
+
|
156
|
+
# Reads all data up to and including the given pattern, which may be a
|
157
|
+
# String, Fixnum, or Regexp and is interpreted exactly as String#index
|
158
|
+
# does. Returns nil if nothing matches. Increments the position to point
|
159
|
+
# immediately after the pattern, if it does match. Returns all data up to
|
160
|
+
# and including the text that matched the pattern.
|
161
|
+
def read_to(pattern)
|
162
|
+
index = @content.index(pattern, @position) or return nil
|
163
|
+
length = case pattern
|
164
|
+
when String then pattern.length
|
165
|
+
when Integer then 1
|
166
|
+
when Regexp then $&.length
|
167
|
+
end
|
168
|
+
index && read(index + length)
|
169
|
+
end
|
170
|
+
|
171
|
+
# Reads and returns the next +count+ bytes from the buffer, starting from
|
172
|
+
# the read position. If +count+ is +nil+, this will return all remaining
|
173
|
+
# text in the buffer. This method will increment the pointer.
|
174
|
+
def read(count=nil)
|
175
|
+
count ||= length
|
176
|
+
count = length - @position if @position + count > length
|
177
|
+
@position += count
|
178
|
+
@content[@position - count, count]
|
179
|
+
end
|
180
|
+
|
181
|
+
# Reads (as #read) and returns the given number of bytes from the buffer,
|
182
|
+
# and then consumes (as #consume!) all data up to the new read position.
|
183
|
+
def read!(count=nil)
|
184
|
+
data = read(count)
|
185
|
+
consume!
|
186
|
+
data
|
187
|
+
end
|
188
|
+
|
189
|
+
# Calls block(self) until the buffer is empty, and returns all results.
|
190
|
+
def read_all(&block)
|
191
|
+
Enumerator.new { |e| e << yield(self) until eof? }.to_a
|
192
|
+
end
|
193
|
+
|
194
|
+
# Return the next 8 bytes as a 64-bit integer (in network byte order).
|
195
|
+
# Returns nil if there are less than 8 bytes remaining to be read in the
|
196
|
+
# buffer.
|
197
|
+
def read_int64
|
198
|
+
hi = read_long or return nil
|
199
|
+
lo = read_long or return nil
|
200
|
+
return (hi << 32) + lo
|
201
|
+
end
|
202
|
+
|
203
|
+
# Return the next four bytes as a long integer (in network byte order).
|
204
|
+
# Returns nil if there are less than 4 bytes remaining to be read in the
|
205
|
+
# buffer.
|
206
|
+
def read_long
|
207
|
+
b = read(4) or return nil
|
208
|
+
b.unpack("N").first
|
209
|
+
end
|
210
|
+
|
211
|
+
# Read and return the next byte in the buffer. Returns nil if called at
|
212
|
+
# the end of the buffer.
|
213
|
+
def read_byte
|
214
|
+
b = read(1) or return nil
|
215
|
+
b.getbyte(0)
|
216
|
+
end
|
217
|
+
|
218
|
+
# Read and return an SSH2-encoded string. The string starts with a long
|
219
|
+
# integer that describes the number of bytes remaining in the string.
|
220
|
+
# Returns nil if there are not enough bytes to satisfy the request.
|
221
|
+
def read_string
|
222
|
+
length = read_long or return nil
|
223
|
+
read(length)
|
224
|
+
end
|
225
|
+
|
226
|
+
# Read a single byte and convert it into a boolean, using 'C' rules
|
227
|
+
# (i.e., zero is false, non-zero is true).
|
228
|
+
def read_bool
|
229
|
+
b = read_byte or return nil
|
230
|
+
b != 0
|
231
|
+
end
|
232
|
+
|
233
|
+
# Read a bignum (OpenSSL::BN) from the buffer, in SSH2 format. It is
|
234
|
+
# essentially just a string, which is reinterpreted to be a bignum in
|
235
|
+
# binary format.
|
236
|
+
def read_bignum
|
237
|
+
data = read_string
|
238
|
+
return unless data
|
239
|
+
|
240
|
+
OpenSSL::BN.new(data, 2)
|
241
|
+
end
|
242
|
+
|
243
|
+
# Read a key from the buffer. The key will start with a string
|
244
|
+
# describing its type. The remainder of the key is defined by the
|
245
|
+
# type that was read.
|
246
|
+
def read_key
|
247
|
+
type = read_string
|
248
|
+
return (type ? read_keyblob(type) : nil)
|
249
|
+
end
|
250
|
+
|
251
|
+
def read_private_keyblob(type)
|
252
|
+
case type
|
253
|
+
when /^ssh-rsa$/
|
254
|
+
key = OpenSSL::PKey::RSA.new
|
255
|
+
n = read_bignum
|
256
|
+
e = read_bignum
|
257
|
+
d = read_bignum
|
258
|
+
iqmp = read_bignum
|
259
|
+
p = read_bignum
|
260
|
+
q = read_bignum
|
261
|
+
_unkown1 = read_bignum
|
262
|
+
_unkown2 = read_bignum
|
263
|
+
dmp1 = d % (p - 1)
|
264
|
+
dmq1 = d % (q - 1)
|
265
|
+
if key.respond_to?(:set_key)
|
266
|
+
key.set_key(n, e, d)
|
267
|
+
else
|
268
|
+
key.e = e
|
269
|
+
key.n = n
|
270
|
+
key.d = d
|
271
|
+
end
|
272
|
+
if key.respond_to?(:set_factors)
|
273
|
+
key.set_factors(p, q)
|
274
|
+
else
|
275
|
+
key.p = p
|
276
|
+
key.q = q
|
277
|
+
end
|
278
|
+
if key.respond_to?(:set_crt_params)
|
279
|
+
key.set_crt_params(dmp1, dmq1, iqmp)
|
280
|
+
else
|
281
|
+
key.dmp1 = dmp1
|
282
|
+
key.dmq1 = dmq1
|
283
|
+
key.iqmp = iqmp
|
284
|
+
end
|
285
|
+
key
|
286
|
+
else
|
287
|
+
raise Exception, "Cannot decode private key of type #{type}"
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
# Read a keyblob of the given type from the buffer, and return it as
|
292
|
+
# a key. Only RSA, DSA, and ECDSA keys are supported.
|
293
|
+
def read_keyblob(type)
|
294
|
+
case type
|
295
|
+
when /^(.*)-cert-v01@openssh\.com$/
|
296
|
+
key = Net::SSH::Authentication::Certificate.read_certblob(self, $1)
|
297
|
+
when /^ssh-dss$/
|
298
|
+
key = OpenSSL::PKey::DSA.new
|
299
|
+
if key.respond_to?(:set_pqg)
|
300
|
+
key.set_pqg(read_bignum, read_bignum, read_bignum)
|
301
|
+
else
|
302
|
+
key.p = read_bignum
|
303
|
+
key.q = read_bignum
|
304
|
+
key.g = read_bignum
|
305
|
+
end
|
306
|
+
if key.respond_to?(:set_key)
|
307
|
+
key.set_key(read_bignum, nil)
|
308
|
+
else
|
309
|
+
key.pub_key = read_bignum
|
310
|
+
end
|
311
|
+
when /^ssh-rsa$/
|
312
|
+
key = OpenSSL::PKey::RSA.new
|
313
|
+
if key.respond_to?(:set_key)
|
314
|
+
e = read_bignum
|
315
|
+
n = read_bignum
|
316
|
+
key.set_key(n, e, nil)
|
317
|
+
else
|
318
|
+
key.e = read_bignum
|
319
|
+
key.n = read_bignum
|
320
|
+
end
|
321
|
+
when /^ssh-ed25519$/
|
322
|
+
Net::SSH::Authentication::ED25519Loader.raiseUnlessLoaded("unsupported key type `#{type}'")
|
323
|
+
key = Net::SSH::Authentication::ED25519::PubKey.read_keyblob(self)
|
324
|
+
when /^ecdsa\-sha2\-(\w*)$/
|
325
|
+
key = OpenSSL::PKey::EC.read_keyblob($1, self)
|
326
|
+
else
|
327
|
+
raise NotImplementedError, "unsupported key type `#{type}'"
|
328
|
+
end
|
329
|
+
|
330
|
+
return key
|
331
|
+
end
|
332
|
+
|
333
|
+
# Reads the next string from the buffer, and returns a new Buffer
|
334
|
+
# object that wraps it.
|
335
|
+
def read_buffer
|
336
|
+
Buffer.new(read_string)
|
337
|
+
end
|
338
|
+
|
339
|
+
# Writes the given data literally into the string. Does not alter the
|
340
|
+
# read position. Returns the buffer object.
|
341
|
+
def write(*data)
|
342
|
+
data.each { |datum| @content << datum.dup.force_encoding('BINARY') }
|
343
|
+
self
|
344
|
+
end
|
345
|
+
|
346
|
+
# Optimized version of write where the caller gives up ownership of string
|
347
|
+
# to the method. This way we can mutate the string.
|
348
|
+
def write_moved(string)
|
349
|
+
@content <<
|
350
|
+
if string.frozen?
|
351
|
+
string.dup.force_encoding('BINARY')
|
352
|
+
else
|
353
|
+
string.force_encoding('BINARY')
|
354
|
+
end
|
355
|
+
self
|
356
|
+
end
|
357
|
+
|
358
|
+
# Writes each argument to the buffer as a network-byte-order-encoded
|
359
|
+
# 64-bit integer (8 bytes). Does not alter the read position. Returns the
|
360
|
+
# buffer object.
|
361
|
+
def write_int64(*n)
|
362
|
+
n.each do |i|
|
363
|
+
hi = (i >> 32) & 0xFFFFFFFF
|
364
|
+
lo = i & 0xFFFFFFFF
|
365
|
+
@content << [hi, lo].pack("N2")
|
366
|
+
end
|
367
|
+
self
|
368
|
+
end
|
369
|
+
|
370
|
+
# Writes each argument to the buffer as a network-byte-order-encoded
|
371
|
+
# long (4-byte) integer. Does not alter the read position. Returns the
|
372
|
+
# buffer object.
|
373
|
+
def write_long(*n)
|
374
|
+
@content << n.pack("N*")
|
375
|
+
self
|
376
|
+
end
|
377
|
+
|
378
|
+
# Writes each argument to the buffer as a byte. Does not alter the read
|
379
|
+
# position. Returns the buffer object.
|
380
|
+
def write_byte(*n)
|
381
|
+
n.each { |b| @content << b.chr }
|
382
|
+
self
|
383
|
+
end
|
384
|
+
|
385
|
+
# Writes each argument to the buffer as an SSH2-encoded string. Each
|
386
|
+
# string is prefixed by its length, encoded as a 4-byte long integer.
|
387
|
+
# Does not alter the read position. Returns the buffer object.
|
388
|
+
def write_string(*text)
|
389
|
+
text.each do |string|
|
390
|
+
s = string.to_s
|
391
|
+
write_long(s.bytesize)
|
392
|
+
write(s)
|
393
|
+
end
|
394
|
+
self
|
395
|
+
end
|
396
|
+
|
397
|
+
# Writes each argument to the buffer as an SSH2-encoded string. Each
|
398
|
+
# string is prefixed by its length, encoded as a 4-byte long integer.
|
399
|
+
# Does not alter the read position. Returns the buffer object.
|
400
|
+
# Might alter arguments see write_moved
|
401
|
+
def write_mstring(*text)
|
402
|
+
text.each do |string|
|
403
|
+
s = string.to_s
|
404
|
+
write_long(s.bytesize)
|
405
|
+
write_moved(s)
|
406
|
+
end
|
407
|
+
self
|
408
|
+
end
|
409
|
+
|
410
|
+
# Writes each argument to the buffer as a (C-style) boolean, with 1
|
411
|
+
# meaning true, and 0 meaning false. Does not alter the read position.
|
412
|
+
# Returns the buffer object.
|
413
|
+
def write_bool(*b)
|
414
|
+
b.each { |v| @content << (v ? "\1" : "\0") }
|
415
|
+
self
|
416
|
+
end
|
417
|
+
|
418
|
+
# Writes each argument to the buffer as a bignum (SSH2-style). No
|
419
|
+
# checking is done to ensure that the arguments are, in fact, bignums.
|
420
|
+
# Does not alter the read position. Returns the buffer object.
|
421
|
+
def write_bignum(*n)
|
422
|
+
@content << n.map { |b| b.to_ssh }.join
|
423
|
+
self
|
424
|
+
end
|
425
|
+
|
426
|
+
# Writes the given arguments to the buffer as SSH2-encoded keys. Does not
|
427
|
+
# alter the read position. Returns the buffer object.
|
428
|
+
def write_key(*key)
|
429
|
+
key.each { |k| append(k.to_blob) }
|
430
|
+
self
|
431
|
+
end
|
432
|
+
end
|
433
|
+
end
|
434
|
+
end;
|
@@ -0,0 +1,202 @@
|
|
1
|
+
require 'net/ssh/buffer'
|
2
|
+
require 'net/ssh/loggable'
|
3
|
+
|
4
|
+
module Net
|
5
|
+
module SSH
|
6
|
+
# This module is used to extend sockets and other IO objects, to allow
|
7
|
+
# them to be buffered for both read and write. This abstraction makes it
|
8
|
+
# quite easy to write a select-based event loop
|
9
|
+
# (see Net::SSH::Connection::Session#listen_to).
|
10
|
+
#
|
11
|
+
# The general idea is that instead of calling #read directly on an IO that
|
12
|
+
# has been extended with this module, you call #fill (to add pending input
|
13
|
+
# to the internal read buffer), and then #read_available (to read from that
|
14
|
+
# buffer). Likewise, you don't call #write directly, you call #enqueue to
|
15
|
+
# add data to the write buffer, and then #send_pending or #wait_for_pending_sends
|
16
|
+
# to actually send the data across the wire.
|
17
|
+
#
|
18
|
+
# In this way you can easily use the object as an argument to IO.select,
|
19
|
+
# calling #fill when it is available for read, or #send_pending when it is
|
20
|
+
# available for write, and then call #enqueue and #read_available during
|
21
|
+
# the idle times.
|
22
|
+
#
|
23
|
+
# socket = TCPSocket.new(address, port)
|
24
|
+
# socket.extend(Net::SSH::BufferedIo)
|
25
|
+
#
|
26
|
+
# ssh.listen_to(socket)
|
27
|
+
#
|
28
|
+
# ssh.loop do
|
29
|
+
# if socket.available > 0
|
30
|
+
# puts socket.read_available
|
31
|
+
# socket.enqueue("response\n")
|
32
|
+
# end
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# Note that this module must be used to extend an instance, and should not
|
36
|
+
# be included in a class. If you do want to use it via an include, then you
|
37
|
+
# must make sure to invoke the private #initialize_buffered_io method in
|
38
|
+
# your class' #initialize method:
|
39
|
+
#
|
40
|
+
# class Foo < IO
|
41
|
+
# include Net::SSH::BufferedIo
|
42
|
+
#
|
43
|
+
# def initialize
|
44
|
+
# initialize_buffered_io
|
45
|
+
# # ...
|
46
|
+
# end
|
47
|
+
# end
|
48
|
+
module BufferedIo
|
49
|
+
include Loggable
|
50
|
+
|
51
|
+
# Called when the #extend is called on an object, with this module as the
|
52
|
+
# argument. It ensures that the modules instance variables are all properly
|
53
|
+
# initialized.
|
54
|
+
def self.extended(object) #:nodoc:
|
55
|
+
# need to use __send__ because #send is overridden in Socket
|
56
|
+
object.__send__(:initialize_buffered_io)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Tries to read up to +n+ bytes of data from the remote end, and appends
|
60
|
+
# the data to the input buffer. It returns the number of bytes read, or 0
|
61
|
+
# if no data was available to be read.
|
62
|
+
def fill(n=8192)
|
63
|
+
input.consume!
|
64
|
+
data = recv(n)
|
65
|
+
debug { "read #{data.length} bytes" }
|
66
|
+
input.append(data)
|
67
|
+
return data.length
|
68
|
+
rescue EOFError => e
|
69
|
+
@input_errors << e
|
70
|
+
return 0
|
71
|
+
end
|
72
|
+
|
73
|
+
# Read up to +length+ bytes from the input buffer. If +length+ is nil,
|
74
|
+
# all available data is read from the buffer. (See #available.)
|
75
|
+
def read_available(length=nil)
|
76
|
+
input.read(length || available)
|
77
|
+
end
|
78
|
+
|
79
|
+
# Returns the number of bytes available to be read from the input buffer.
|
80
|
+
# (See #read_available.)
|
81
|
+
def available
|
82
|
+
input.available
|
83
|
+
end
|
84
|
+
|
85
|
+
# Enqueues data in the output buffer, to be written when #send_pending
|
86
|
+
# is called. Note that the data is _not_ sent immediately by this method!
|
87
|
+
def enqueue(data)
|
88
|
+
output.append(data)
|
89
|
+
end
|
90
|
+
|
91
|
+
# Returns +true+ if there is data waiting in the output buffer, and
|
92
|
+
# +false+ otherwise.
|
93
|
+
def pending_write?
|
94
|
+
output.length > 0
|
95
|
+
end
|
96
|
+
|
97
|
+
# Sends as much of the pending output as possible. Returns +true+ if any
|
98
|
+
# data was sent, and +false+ otherwise.
|
99
|
+
def send_pending
|
100
|
+
if output.length > 0
|
101
|
+
sent = send(output.to_s, 0)
|
102
|
+
debug { "sent #{sent} bytes" }
|
103
|
+
output.consume!(sent)
|
104
|
+
return sent > 0
|
105
|
+
else
|
106
|
+
return false
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# Calls #send_pending repeatedly, if necessary, blocking until the output
|
111
|
+
# buffer is empty.
|
112
|
+
def wait_for_pending_sends
|
113
|
+
send_pending
|
114
|
+
while output.length > 0
|
115
|
+
result = IO.select(nil, [self]) or next
|
116
|
+
next unless result[1].any?
|
117
|
+
|
118
|
+
send_pending
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
public # these methods are primarily for use in tests
|
123
|
+
|
124
|
+
def write_buffer #:nodoc:
|
125
|
+
output.to_s
|
126
|
+
end
|
127
|
+
|
128
|
+
def read_buffer #:nodoc:
|
129
|
+
input.to_s
|
130
|
+
end
|
131
|
+
|
132
|
+
private
|
133
|
+
|
134
|
+
#--
|
135
|
+
# Can't use attr_reader here (after +private+) without incurring the
|
136
|
+
# wrath of "ruby -w". We hates it.
|
137
|
+
#++
|
138
|
+
|
139
|
+
def input; @input; end
|
140
|
+
|
141
|
+
def output; @output; end
|
142
|
+
|
143
|
+
# Initializes the intput and output buffers for this object. This method
|
144
|
+
# is called automatically when the module is mixed into an object via
|
145
|
+
# Object#extend (see Net::SSH::BufferedIo.extended), but must be called
|
146
|
+
# explicitly in the +initialize+ method of any class that uses
|
147
|
+
# Module#include to add this module.
|
148
|
+
def initialize_buffered_io
|
149
|
+
@input = Net::SSH::Buffer.new
|
150
|
+
@input_errors = []
|
151
|
+
@output = Net::SSH::Buffer.new
|
152
|
+
@output_errors = []
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
# Fixes for two issues by Miklós Fazekas:
|
157
|
+
#
|
158
|
+
# * if client closes a forwarded connection, but the server is
|
159
|
+
# reading, net-ssh terminates with IOError socket closed.
|
160
|
+
# * if client force closes (RST) a forwarded connection, but
|
161
|
+
# server is reading, net-ssh terminates with [an exception]
|
162
|
+
#
|
163
|
+
# See:
|
164
|
+
#
|
165
|
+
# http://net-ssh.lighthouseapp.com/projects/36253/tickets/7
|
166
|
+
# http://github.com/net-ssh/net-ssh/tree/portfwfix
|
167
|
+
#
|
168
|
+
module ForwardedBufferedIo
|
169
|
+
def fill(n=8192)
|
170
|
+
begin
|
171
|
+
super(n)
|
172
|
+
rescue Errno::ECONNRESET => e
|
173
|
+
debug { "connection was reset => shallowing exception:#{e}" }
|
174
|
+
return 0
|
175
|
+
rescue IOError => e
|
176
|
+
if e.message =~ /closed/ then
|
177
|
+
debug { "connection was reset => shallowing exception:#{e}" }
|
178
|
+
return 0
|
179
|
+
else
|
180
|
+
raise
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
def send_pending
|
186
|
+
begin
|
187
|
+
super
|
188
|
+
rescue Errno::ECONNRESET => e
|
189
|
+
debug { "connection was reset => shallowing exception:#{e}" }
|
190
|
+
return 0
|
191
|
+
rescue IOError => e
|
192
|
+
if e.message =~ /closed/ then
|
193
|
+
debug { "connection was reset => shallowing exception:#{e}" }
|
194
|
+
return 0
|
195
|
+
else
|
196
|
+
raise
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|