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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/.rubocop_todo.yml +98 -258
- data/CHANGES.txt +8 -0
- data/Gemfile +1 -3
- data/Rakefile +37 -39
- data/lib/net/ssh.rb +26 -25
- data/lib/net/ssh/authentication/agent.rb +228 -225
- data/lib/net/ssh/authentication/certificate.rb +166 -164
- data/lib/net/ssh/authentication/constants.rb +17 -14
- data/lib/net/ssh/authentication/ed25519.rb +107 -104
- data/lib/net/ssh/authentication/ed25519_loader.rb +32 -28
- data/lib/net/ssh/authentication/key_manager.rb +5 -3
- data/lib/net/ssh/authentication/methods/abstract.rb +53 -47
- data/lib/net/ssh/authentication/methods/hostbased.rb +32 -33
- data/lib/net/ssh/authentication/methods/keyboard_interactive.rb +2 -4
- data/lib/net/ssh/authentication/methods/none.rb +10 -10
- data/lib/net/ssh/authentication/methods/password.rb +13 -13
- data/lib/net/ssh/authentication/methods/publickey.rb +54 -55
- data/lib/net/ssh/authentication/pageant.rb +468 -465
- data/lib/net/ssh/authentication/pub_key_fingerprint.rb +44 -0
- data/lib/net/ssh/authentication/session.rb +127 -123
- data/lib/net/ssh/buffer.rb +305 -303
- data/lib/net/ssh/buffered_io.rb +163 -162
- data/lib/net/ssh/config.rb +230 -227
- data/lib/net/ssh/connection/channel.rb +659 -654
- data/lib/net/ssh/connection/constants.rb +30 -26
- data/lib/net/ssh/connection/event_loop.rb +108 -104
- data/lib/net/ssh/connection/keepalive.rb +54 -50
- data/lib/net/ssh/connection/session.rb +677 -678
- data/lib/net/ssh/connection/term.rb +180 -176
- data/lib/net/ssh/errors.rb +101 -99
- data/lib/net/ssh/key_factory.rb +108 -108
- data/lib/net/ssh/known_hosts.rb +148 -154
- data/lib/net/ssh/loggable.rb +56 -54
- data/lib/net/ssh/packet.rb +82 -78
- data/lib/net/ssh/prompt.rb +55 -53
- data/lib/net/ssh/proxy/command.rb +103 -102
- data/lib/net/ssh/proxy/errors.rb +12 -8
- data/lib/net/ssh/proxy/http.rb +92 -91
- data/lib/net/ssh/proxy/https.rb +42 -39
- data/lib/net/ssh/proxy/jump.rb +50 -47
- data/lib/net/ssh/proxy/socks4.rb +0 -2
- data/lib/net/ssh/proxy/socks5.rb +11 -11
- data/lib/net/ssh/ruby_compat.rb +1 -0
- data/lib/net/ssh/service/forward.rb +364 -362
- data/lib/net/ssh/test.rb +85 -83
- data/lib/net/ssh/test/channel.rb +146 -142
- data/lib/net/ssh/test/extensions.rb +148 -146
- data/lib/net/ssh/test/kex.rb +35 -31
- data/lib/net/ssh/test/local_packet.rb +48 -44
- data/lib/net/ssh/test/packet.rb +87 -84
- data/lib/net/ssh/test/remote_packet.rb +35 -31
- data/lib/net/ssh/test/script.rb +173 -171
- data/lib/net/ssh/test/socket.rb +59 -55
- data/lib/net/ssh/transport/algorithms.rb +413 -412
- data/lib/net/ssh/transport/cipher_factory.rb +108 -105
- data/lib/net/ssh/transport/constants.rb +35 -31
- data/lib/net/ssh/transport/ctr.rb +1 -1
- data/lib/net/ssh/transport/hmac.rb +1 -1
- data/lib/net/ssh/transport/hmac/abstract.rb +67 -64
- data/lib/net/ssh/transport/hmac/sha2_256_96.rb +1 -1
- data/lib/net/ssh/transport/hmac/sha2_512_96.rb +1 -1
- data/lib/net/ssh/transport/identity_cipher.rb +55 -51
- data/lib/net/ssh/transport/kex.rb +2 -4
- data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha1.rb +47 -40
- data/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb +201 -197
- data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb +53 -56
- data/lib/net/ssh/transport/kex/ecdh_sha2_nistp256.rb +94 -87
- data/lib/net/ssh/transport/kex/ecdh_sha2_nistp384.rb +17 -10
- data/lib/net/ssh/transport/kex/ecdh_sha2_nistp521.rb +17 -10
- data/lib/net/ssh/transport/key_expander.rb +29 -25
- data/lib/net/ssh/transport/openssl.rb +17 -30
- data/lib/net/ssh/transport/packet_stream.rb +193 -192
- data/lib/net/ssh/transport/server_version.rb +64 -66
- data/lib/net/ssh/transport/session.rb +286 -284
- data/lib/net/ssh/transport/state.rb +198 -196
- data/lib/net/ssh/verifiers/lenient.rb +29 -25
- data/lib/net/ssh/verifiers/null.rb +13 -9
- data/lib/net/ssh/verifiers/secure.rb +45 -45
- data/lib/net/ssh/verifiers/strict.rb +20 -16
- data/lib/net/ssh/version.rb +55 -53
- data/net-ssh.gemspec +4 -4
- data/support/ssh_tunnel_bug.rb +2 -2
- metadata +25 -24
- metadata.gz.sig +0 -0
@@ -0,0 +1,44 @@
|
|
1
|
+
|
2
|
+
require 'openssl'
|
3
|
+
|
4
|
+
module Net
|
5
|
+
module SSH
|
6
|
+
module Authentication
|
7
|
+
# Public key fingerprinting utility module - internal not part of API.
|
8
|
+
# This is included in pubkey classes and called from there. All RSA, DSA, and ECC keys
|
9
|
+
# are supported.
|
10
|
+
#
|
11
|
+
# require 'net/ssh'
|
12
|
+
# my_pubkey_text = File.read('/path/to/id_ed25519.pub')
|
13
|
+
# #=> "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDB2NBh4GJPPUN1kXPMu8b633Xcv55WoKC3OkBjFAbzJ alice@example.com"
|
14
|
+
# my_pubkey = Net::SSH::KeyFactory.load_data_public_key(my_pubkey_text)
|
15
|
+
# #=> #<Net::SSH::Authentication::ED25519::PubKey:0x00007fc8e91819b0
|
16
|
+
# my_pubkey.fingerprint
|
17
|
+
# #=> "2f:7f:97:21:76:a4:0f:38:c4:fe:d8:b4:6a:39:72:30"
|
18
|
+
# my_pubkey.fingerprint('SHA256')
|
19
|
+
# #=> "SHA256:u6mXnY8P1b0FODGp8mckqOB33u8+jvkSCtJbD5Q9klg"
|
20
|
+
module PubKeyFingerprint # :nodoc:
|
21
|
+
# Return the key's fingerprint. Algorithm may be either +MD5+ (default),
|
22
|
+
# or +SHA256+. For +SHA256+, fingerprints are in the same format
|
23
|
+
# returned by OpenSSH's <tt>`ssh-add -l -E SHA256`</tt>, i.e.,
|
24
|
+
# trailing base64 padding '=' characters are stripped and the
|
25
|
+
# literal string +SHA256:+ is prepended.
|
26
|
+
def fingerprint(algorithm='MD5')
|
27
|
+
@fingerprint ||= {}
|
28
|
+
@fingerprint[algorithm] ||= PubKeyFingerprint.fingerprint(to_blob, algorithm)
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.fingerprint(blob, algorithm='MD5')
|
32
|
+
case algorithm.to_s.upcase
|
33
|
+
when 'MD5'
|
34
|
+
OpenSSL::Digest.hexdigest(algorithm, blob).scan(/../).join(":")
|
35
|
+
when 'SHA256'
|
36
|
+
"SHA256:#{Base64.encode64(OpenSSL::Digest.digest(algorithm, blob)).chomp.gsub(/=+\z/, '')}"
|
37
|
+
else
|
38
|
+
raise OpenSSL::Digest::DigestError, "unsupported ssh key digest #{algorithm}"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -8,149 +8,153 @@ require 'net/ssh/authentication/methods/hostbased'
|
|
8
8
|
require 'net/ssh/authentication/methods/password'
|
9
9
|
require 'net/ssh/authentication/methods/keyboard_interactive'
|
10
10
|
|
11
|
-
module Net
|
11
|
+
module Net
|
12
|
+
module SSH
|
13
|
+
module Authentication
|
12
14
|
|
13
|
-
|
14
|
-
|
15
|
-
|
15
|
+
# Raised if the current authentication method is not allowed
|
16
|
+
class DisallowedMethod < Net::SSH::Exception
|
17
|
+
end
|
16
18
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
19
|
+
# Represents an authentication session. It manages the authentication of
|
20
|
+
# a user over an established connection (the "transport" object, see
|
21
|
+
# Net::SSH::Transport::Session).
|
22
|
+
#
|
23
|
+
# The use of an authentication session to manage user authentication is
|
24
|
+
# internal to Net::SSH (specifically Net::SSH.start). Consumers of the
|
25
|
+
# Net::SSH library will never need to access this class directly.
|
26
|
+
class Session
|
27
|
+
include Loggable
|
28
|
+
include Constants
|
29
|
+
include Transport::Constants
|
26
30
|
|
27
|
-
|
28
|
-
|
31
|
+
# transport layer abstraction
|
32
|
+
attr_reader :transport
|
29
33
|
|
30
|
-
|
31
|
-
|
34
|
+
# the list of authentication methods to try
|
35
|
+
attr_reader :auth_methods
|
32
36
|
|
33
|
-
|
34
|
-
|
37
|
+
# the list of authentication methods that are allowed
|
38
|
+
attr_reader :allowed_auth_methods
|
35
39
|
|
36
|
-
|
37
|
-
|
40
|
+
# a hash of options, given at construction time
|
41
|
+
attr_reader :options
|
38
42
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
43
|
+
# Instantiates a new Authentication::Session object over the given
|
44
|
+
# transport layer abstraction.
|
45
|
+
def initialize(transport, options={})
|
46
|
+
self.logger = transport.logger
|
47
|
+
@transport = transport
|
44
48
|
|
45
|
-
|
46
|
-
|
49
|
+
@auth_methods = options[:auth_methods] || Net::SSH::Config.default_auth_methods
|
50
|
+
@options = options
|
47
51
|
|
48
|
-
|
49
|
-
|
52
|
+
@allowed_auth_methods = @auth_methods
|
53
|
+
end
|
50
54
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
55
|
+
# Attempts to authenticate the given user, in preparation for the next
|
56
|
+
# service request. Returns true if an authentication method succeeds in
|
57
|
+
# authenticating the user, and false otherwise.
|
58
|
+
def authenticate(next_service, username, password=nil)
|
59
|
+
debug { "beginning authentication of `#{username}'" }
|
60
|
+
|
61
|
+
transport.send_message(transport.service_request("ssh-userauth"))
|
62
|
+
expect_message(SERVICE_ACCEPT)
|
63
|
+
|
64
|
+
key_manager = KeyManager.new(logger, options)
|
65
|
+
keys.each { |key| key_manager.add(key) } unless keys.empty?
|
66
|
+
key_data.each { |key2| key_manager.add_key_data(key2) } unless key_data.empty?
|
67
|
+
|
68
|
+
attempted = []
|
69
|
+
|
70
|
+
@auth_methods.each do |name|
|
71
|
+
begin
|
72
|
+
next unless @allowed_auth_methods.include?(name)
|
73
|
+
attempted << name
|
74
|
+
|
75
|
+
debug { "trying #{name}" }
|
76
|
+
begin
|
77
|
+
auth_class = Methods.const_get(name.split(/\W+/).map { |p| p.capitalize }.join)
|
78
|
+
method = auth_class.new(self, key_manager: key_manager, password_prompt: options[:password_prompt])
|
79
|
+
rescue NameError
|
80
|
+
debug {"Mechanism #{name} was requested, but isn't a known type. Ignoring it."}
|
81
|
+
next
|
82
|
+
end
|
83
|
+
|
84
|
+
return true if method.authenticate(next_service, username, password)
|
85
|
+
rescue Net::SSH::Authentication::DisallowedMethod
|
86
|
+
end
|
78
87
|
end
|
79
88
|
|
80
|
-
|
81
|
-
|
89
|
+
error { "all authorization methods failed (tried #{attempted.join(', ')})" }
|
90
|
+
return false
|
91
|
+
ensure
|
92
|
+
key_manager.finish if key_manager
|
82
93
|
end
|
83
|
-
end
|
84
|
-
|
85
|
-
error { "all authorization methods failed (tried #{attempted.join(', ')})" }
|
86
|
-
return false
|
87
|
-
ensure
|
88
|
-
key_manager.finish if key_manager
|
89
|
-
end
|
90
|
-
|
91
|
-
# Blocks until a packet is received. It silently handles USERAUTH_BANNER
|
92
|
-
# packets, and will raise an error if any packet is received that is not
|
93
|
-
# valid during user authentication.
|
94
|
-
def next_message
|
95
|
-
loop do
|
96
|
-
packet = transport.next_message
|
97
|
-
|
98
|
-
case packet.type
|
99
|
-
when USERAUTH_BANNER
|
100
|
-
info { packet[:message] }
|
101
|
-
# TODO add a hook for people to retrieve the banner when it is sent
|
102
|
-
|
103
|
-
when USERAUTH_FAILURE
|
104
|
-
@allowed_auth_methods = packet[:authentications].split(/,/)
|
105
|
-
debug { "allowed methods: #{packet[:authentications]}" }
|
106
|
-
return packet
|
107
|
-
|
108
|
-
when USERAUTH_METHOD_RANGE, SERVICE_ACCEPT
|
109
|
-
return packet
|
110
|
-
|
111
|
-
when USERAUTH_SUCCESS
|
112
|
-
transport.hint :authenticated
|
113
|
-
return packet
|
114
94
|
|
115
|
-
|
116
|
-
|
95
|
+
# Blocks until a packet is received. It silently handles USERAUTH_BANNER
|
96
|
+
# packets, and will raise an error if any packet is received that is not
|
97
|
+
# valid during user authentication.
|
98
|
+
def next_message
|
99
|
+
loop do
|
100
|
+
packet = transport.next_message
|
101
|
+
|
102
|
+
case packet.type
|
103
|
+
when USERAUTH_BANNER
|
104
|
+
info { packet[:message] }
|
105
|
+
# TODO add a hook for people to retrieve the banner when it is sent
|
106
|
+
|
107
|
+
when USERAUTH_FAILURE
|
108
|
+
@allowed_auth_methods = packet[:authentications].split(/,/)
|
109
|
+
debug { "allowed methods: #{packet[:authentications]}" }
|
110
|
+
return packet
|
111
|
+
|
112
|
+
when USERAUTH_METHOD_RANGE, SERVICE_ACCEPT
|
113
|
+
return packet
|
114
|
+
|
115
|
+
when USERAUTH_SUCCESS
|
116
|
+
transport.hint :authenticated
|
117
|
+
return packet
|
118
|
+
|
119
|
+
else
|
120
|
+
raise Net::SSH::Exception, "unexpected message #{packet.type} (#{packet})"
|
121
|
+
end
|
122
|
+
end
|
117
123
|
end
|
118
|
-
end
|
119
|
-
end
|
120
124
|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
message
|
129
|
-
end
|
125
|
+
# Blocks until a packet is received, and returns it if it is of the given
|
126
|
+
# type. If it is not, an exception is raised.
|
127
|
+
def expect_message(type)
|
128
|
+
message = next_message
|
129
|
+
raise Net::SSH::Exception, "expected #{type}, got #{message.type} (#{message})" unless message.type == type
|
130
|
+
message
|
131
|
+
end
|
130
132
|
|
131
|
-
|
133
|
+
private
|
132
134
|
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
135
|
+
# Returns an array of paths to the key files usually defined
|
136
|
+
# by system default.
|
137
|
+
def default_keys
|
138
|
+
if defined?(OpenSSL::PKey::EC)
|
139
|
+
%w[~/.ssh/id_ed25519 ~/.ssh/id_rsa ~/.ssh/id_dsa ~/.ssh/id_ecdsa
|
140
|
+
~/.ssh2/id_ed25519 ~/.ssh2/id_rsa ~/.ssh2/id_dsa ~/.ssh2/id_ecdsa]
|
141
|
+
else
|
142
|
+
%w[~/.ssh/id_dsa ~/.ssh/id_rsa ~/.ssh2/id_dsa ~/.ssh2/id_rsa]
|
143
|
+
end
|
141
144
|
end
|
142
|
-
end
|
143
145
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
146
|
+
# Returns an array of paths to the key files that should be used when
|
147
|
+
# attempting any key-based authentication mechanism.
|
148
|
+
def keys
|
149
|
+
Array(options[:keys])
|
150
|
+
end
|
149
151
|
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
152
|
+
# Returns an array of the key data that should be used when
|
153
|
+
# attempting any key-based authentication mechanism.
|
154
|
+
def key_data
|
155
|
+
Array(options[:key_data])
|
156
|
+
end
|
154
157
|
end
|
158
|
+
end
|
155
159
|
end
|
156
|
-
end
|
160
|
+
end
|
data/lib/net/ssh/buffer.rb
CHANGED
@@ -4,254 +4,255 @@ require 'net/ssh/transport/openssl'
|
|
4
4
|
require 'net/ssh/authentication/certificate'
|
5
5
|
require 'net/ssh/authentication/ed25519_loader'
|
6
6
|
|
7
|
-
module Net
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
# Writing to a buffer always appends to the end, regardless of where the
|
15
|
-
# read cursor is. Reading, on the other hand, always begins at the first
|
16
|
-
# byte of the buffer and increments the read cursor, with subsequent reads
|
17
|
-
# taking up where the last left off.
|
18
|
-
#
|
19
|
-
# As a consumer of the Net::SSH library, you will rarely come into contact
|
20
|
-
# with these buffer objects directly, but it could happen. Also, if you
|
21
|
-
# are ever implementing a protocol on top of SSH (e.g. SFTP), this buffer
|
22
|
-
# class can be quite handy.
|
23
|
-
class Buffer
|
24
|
-
# This is a convenience method for creating and populating a new buffer
|
25
|
-
# from a single command. The arguments must be even in length, with the
|
26
|
-
# first of each pair of arguments being a symbol naming the type of the
|
27
|
-
# data that follows. If the type is :raw, the value is written directly
|
28
|
-
# to the hash.
|
29
|
-
#
|
30
|
-
# b = Buffer.from(:byte, 1, :string, "hello", :raw, "\1\2\3\4")
|
31
|
-
# #-> "\1\0\0\0\5hello\1\2\3\4"
|
32
|
-
#
|
33
|
-
# The supported data types are:
|
7
|
+
module Net
|
8
|
+
module SSH
|
9
|
+
|
10
|
+
# Net::SSH::Buffer is a flexible class for building and parsing binary
|
11
|
+
# data packets. It provides a stream-like interface for sequentially
|
12
|
+
# reading data items from the buffer, as well as a useful helper method
|
13
|
+
# for building binary packets given a signature.
|
34
14
|
#
|
35
|
-
#
|
36
|
-
#
|
37
|
-
#
|
38
|
-
#
|
39
|
-
# * :string => write a 4-byte length followed by character data (#write_string)
|
40
|
-
# * :mstring => same as string, but caller cannot resuse the string, avoids potential duplication (#write_moved)
|
41
|
-
# * :bool => write a single byte, interpreted as a boolean (#write_bool)
|
42
|
-
# * :bignum => write an SSH-encoded bignum (#write_bignum)
|
43
|
-
# * :key => write an SSH-encoded key value (#write_key)
|
15
|
+
# Writing to a buffer always appends to the end, regardless of where the
|
16
|
+
# read cursor is. Reading, on the other hand, always begins at the first
|
17
|
+
# byte of the buffer and increments the read cursor, with subsequent reads
|
18
|
+
# taking up where the last left off.
|
44
19
|
#
|
45
|
-
#
|
46
|
-
#
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
20
|
+
# As a consumer of the Net::SSH library, you will rarely come into contact
|
21
|
+
# with these buffer objects directly, but it could happen. Also, if you
|
22
|
+
# are ever implementing a protocol on top of SSH (e.g. SFTP), this buffer
|
23
|
+
# class can be quite handy.
|
24
|
+
class Buffer
|
25
|
+
# This is a convenience method for creating and populating a new buffer
|
26
|
+
# from a single command. The arguments must be even in length, with the
|
27
|
+
# first of each pair of arguments being a symbol naming the type of the
|
28
|
+
# data that follows. If the type is :raw, the value is written directly
|
29
|
+
# to the hash.
|
30
|
+
#
|
31
|
+
# b = Buffer.from(:byte, 1, :string, "hello", :raw, "\1\2\3\4")
|
32
|
+
# #-> "\1\0\0\0\5hello\1\2\3\4"
|
33
|
+
#
|
34
|
+
# The supported data types are:
|
35
|
+
#
|
36
|
+
# * :raw => write the next value verbatim (#write)
|
37
|
+
# * :int64 => write an 8-byte integer (#write_int64)
|
38
|
+
# * :long => write a 4-byte integer (#write_long)
|
39
|
+
# * :byte => write a single byte (#write_byte)
|
40
|
+
# * :string => write a 4-byte length followed by character data (#write_string)
|
41
|
+
# * :mstring => same as string, but caller cannot resuse the string, avoids potential duplication (#write_moved)
|
42
|
+
# * :bool => write a single byte, interpreted as a boolean (#write_bool)
|
43
|
+
# * :bignum => write an SSH-encoded bignum (#write_bignum)
|
44
|
+
# * :key => write an SSH-encoded key value (#write_key)
|
45
|
+
#
|
46
|
+
# Any of these, except for :raw, accepts an Array argument, to make it
|
47
|
+
# easier to write multiple values of the same type in a briefer manner.
|
48
|
+
def self.from(*args)
|
49
|
+
raise ArgumentError, "odd number of arguments given" unless args.length % 2 == 0
|
50
|
+
|
51
|
+
buffer = new
|
52
|
+
0.step(args.length - 1, 2) do |index|
|
53
|
+
type = args[index]
|
54
|
+
value = args[index + 1]
|
55
|
+
if type == :raw
|
56
|
+
buffer.append(value.to_s)
|
57
|
+
elsif Array === value
|
58
|
+
buffer.send("write_#{type}", *value)
|
59
|
+
else
|
60
|
+
buffer.send("write_#{type}", value)
|
61
|
+
end
|
60
62
|
end
|
61
|
-
end
|
62
63
|
|
63
|
-
|
64
|
-
|
64
|
+
buffer
|
65
|
+
end
|
65
66
|
|
66
|
-
|
67
|
-
|
67
|
+
# exposes the raw content of the buffer
|
68
|
+
attr_reader :content
|
68
69
|
|
69
|
-
|
70
|
-
|
70
|
+
# the current position of the pointer in the buffer
|
71
|
+
attr_accessor :position
|
71
72
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
73
|
+
# Creates a new buffer, initialized to the given content. The position
|
74
|
+
# is initialized to the beginning of the buffer.
|
75
|
+
def initialize(content="")
|
76
|
+
@content = content.to_s
|
77
|
+
@position = 0
|
78
|
+
end
|
78
79
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
80
|
+
# Returns the length of the buffer's content.
|
81
|
+
def length
|
82
|
+
@content.length
|
83
|
+
end
|
83
84
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
85
|
+
# Returns the number of bytes available to be read (e.g., how many bytes
|
86
|
+
# remain between the current position and the end of the buffer).
|
87
|
+
def available
|
88
|
+
length - position
|
89
|
+
end
|
89
90
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
91
|
+
# Returns a copy of the buffer's content.
|
92
|
+
def to_s
|
93
|
+
(@content || "").dup
|
94
|
+
end
|
94
95
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
96
|
+
# Compares the contents of the two buffers, returning +true+ only if they
|
97
|
+
# are identical in size and content.
|
98
|
+
def ==(buffer)
|
99
|
+
to_s == buffer.to_s
|
100
|
+
end
|
100
101
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
102
|
+
# Returns +true+ if the buffer contains no data (e.g., it is of zero length).
|
103
|
+
def empty?
|
104
|
+
@content.empty?
|
105
|
+
end
|
105
106
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
107
|
+
# Resets the pointer to the start of the buffer. Subsequent reads will
|
108
|
+
# begin at position 0.
|
109
|
+
def reset!
|
110
|
+
@position = 0
|
111
|
+
end
|
111
112
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
113
|
+
# Returns true if the pointer is at the end of the buffer. Subsequent
|
114
|
+
# reads will return nil, in this case.
|
115
|
+
def eof?
|
116
|
+
@position >= length
|
117
|
+
end
|
117
118
|
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
119
|
+
# Resets the buffer, making it empty. Also, resets the read position to
|
120
|
+
# 0.
|
121
|
+
def clear!
|
122
|
+
@content = ""
|
123
|
+
@position = 0
|
124
|
+
end
|
124
125
|
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
126
|
+
# Consumes n bytes from the buffer, where n is the current position
|
127
|
+
# unless otherwise specified. This is useful for removing data from the
|
128
|
+
# buffer that has previously been read, when you are expecting more data
|
129
|
+
# to be appended. It helps to keep the size of buffers down when they
|
130
|
+
# would otherwise tend to grow without bound.
|
131
|
+
#
|
132
|
+
# Returns the buffer object itself.
|
133
|
+
def consume!(n=position)
|
134
|
+
if n >= length
|
135
|
+
# optimize for a fairly common case
|
136
|
+
clear!
|
137
|
+
elsif n > 0
|
138
|
+
@content = @content[n..-1] || ""
|
139
|
+
@position -= n
|
140
|
+
@position = 0 if @position < 0
|
141
|
+
end
|
142
|
+
self
|
143
|
+
end
|
143
144
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
145
|
+
# Appends the given text to the end of the buffer. Does not alter the
|
146
|
+
# read position. Returns the buffer object itself.
|
147
|
+
def append(text)
|
148
|
+
@content << text
|
149
|
+
self
|
150
|
+
end
|
150
151
|
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
152
|
+
# Returns all text from the current pointer to the end of the buffer as
|
153
|
+
# a new Net::SSH::Buffer object.
|
154
|
+
def remainder_as_buffer
|
155
|
+
Buffer.new(@content[@position..-1])
|
156
|
+
end
|
156
157
|
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
158
|
+
# Reads all data up to and including the given pattern, which may be a
|
159
|
+
# String, Fixnum, or Regexp and is interpreted exactly as String#index
|
160
|
+
# does. Returns nil if nothing matches. Increments the position to point
|
161
|
+
# immediately after the pattern, if it does match. Returns all data up to
|
162
|
+
# and including the text that matched the pattern.
|
163
|
+
def read_to(pattern)
|
164
|
+
index = @content.index(pattern, @position) or return nil
|
165
|
+
length = case pattern
|
166
|
+
when String then pattern.length
|
167
|
+
when Integer then 1
|
168
|
+
when Regexp then $&.length
|
169
|
+
end
|
170
|
+
index && read(index + length)
|
171
|
+
end
|
171
172
|
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
173
|
+
# Reads and returns the next +count+ bytes from the buffer, starting from
|
174
|
+
# the read position. If +count+ is +nil+, this will return all remaining
|
175
|
+
# text in the buffer. This method will increment the pointer.
|
176
|
+
def read(count=nil)
|
177
|
+
count ||= length
|
178
|
+
count = length - @position if @position + count > length
|
179
|
+
@position += count
|
180
|
+
@content[@position - count, count]
|
181
|
+
end
|
181
182
|
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
183
|
+
# Reads (as #read) and returns the given number of bytes from the buffer,
|
184
|
+
# and then consumes (as #consume!) all data up to the new read position.
|
185
|
+
def read!(count=nil)
|
186
|
+
data = read(count)
|
187
|
+
consume!
|
188
|
+
data
|
189
|
+
end
|
189
190
|
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
191
|
+
# Calls block(self) until the buffer is empty, and returns all results.
|
192
|
+
def read_all(&block)
|
193
|
+
Enumerator.new { |e| e << yield(self) until eof? }.to_a
|
194
|
+
end
|
194
195
|
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
196
|
+
# Return the next 8 bytes as a 64-bit integer (in network byte order).
|
197
|
+
# Returns nil if there are less than 8 bytes remaining to be read in the
|
198
|
+
# buffer.
|
199
|
+
def read_int64
|
200
|
+
hi = read_long or return nil
|
201
|
+
lo = read_long or return nil
|
202
|
+
return (hi << 32) + lo
|
203
|
+
end
|
203
204
|
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
205
|
+
# Return the next four bytes as a long integer (in network byte order).
|
206
|
+
# Returns nil if there are less than 4 bytes remaining to be read in the
|
207
|
+
# buffer.
|
208
|
+
def read_long
|
209
|
+
b = read(4) or return nil
|
210
|
+
b.unpack("N").first
|
211
|
+
end
|
211
212
|
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
213
|
+
# Read and return the next byte in the buffer. Returns nil if called at
|
214
|
+
# the end of the buffer.
|
215
|
+
def read_byte
|
216
|
+
b = read(1) or return nil
|
217
|
+
b.getbyte(0)
|
218
|
+
end
|
218
219
|
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
220
|
+
# Read and return an SSH2-encoded string. The string starts with a long
|
221
|
+
# integer that describes the number of bytes remaining in the string.
|
222
|
+
# Returns nil if there are not enough bytes to satisfy the request.
|
223
|
+
def read_string
|
224
|
+
length = read_long or return nil
|
225
|
+
read(length)
|
226
|
+
end
|
226
227
|
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
228
|
+
# Read a single byte and convert it into a boolean, using 'C' rules
|
229
|
+
# (i.e., zero is false, non-zero is true).
|
230
|
+
def read_bool
|
231
|
+
b = read_byte or return nil
|
232
|
+
b != 0
|
233
|
+
end
|
233
234
|
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
235
|
+
# Read a bignum (OpenSSL::BN) from the buffer, in SSH2 format. It is
|
236
|
+
# essentially just a string, which is reinterpreted to be a bignum in
|
237
|
+
# binary format.
|
238
|
+
def read_bignum
|
239
|
+
data = read_string
|
240
|
+
return unless data
|
241
|
+
OpenSSL::BN.new(data, 2)
|
242
|
+
end
|
242
243
|
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
244
|
+
# Read a key from the buffer. The key will start with a string
|
245
|
+
# describing its type. The remainder of the key is defined by the
|
246
|
+
# type that was read.
|
247
|
+
def read_key
|
248
|
+
type = read_string
|
249
|
+
return (type ? read_keyblob(type) : nil)
|
250
|
+
end
|
250
251
|
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
252
|
+
# Read a keyblob of the given type from the buffer, and return it as
|
253
|
+
# a key. Only RSA, DSA, and ECDSA keys are supported.
|
254
|
+
def read_keyblob(type)
|
255
|
+
case type
|
255
256
|
when /^(.*)-cert-v01@openssh\.com$/
|
256
257
|
key = Net::SSH::Authentication::Certificate.read_certblob(self, $1)
|
257
258
|
when /^ssh-dss$/
|
@@ -293,104 +294,105 @@ module Net; module SSH
|
|
293
294
|
end
|
294
295
|
else
|
295
296
|
raise NotImplementedError, "unsupported key type `#{type}'"
|
296
|
-
|
297
|
+
end
|
297
298
|
|
298
|
-
|
299
|
-
|
299
|
+
return key
|
300
|
+
end
|
300
301
|
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
302
|
+
# Reads the next string from the buffer, and returns a new Buffer
|
303
|
+
# object that wraps it.
|
304
|
+
def read_buffer
|
305
|
+
Buffer.new(read_string)
|
306
|
+
end
|
306
307
|
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
308
|
+
# Writes the given data literally into the string. Does not alter the
|
309
|
+
# read position. Returns the buffer object.
|
310
|
+
def write(*data)
|
311
|
+
data.each { |datum| @content << datum.dup.force_encoding('BINARY') }
|
312
|
+
self
|
313
|
+
end
|
313
314
|
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
315
|
+
# Optimized version of write where the caller gives up ownership of string
|
316
|
+
# to the method. This way we can mutate the string.
|
317
|
+
def write_moved(string)
|
318
|
+
@content << string.force_encoding('BINARY')
|
319
|
+
self
|
320
|
+
end
|
320
321
|
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
322
|
+
# Writes each argument to the buffer as a network-byte-order-encoded
|
323
|
+
# 64-bit integer (8 bytes). Does not alter the read position. Returns the
|
324
|
+
# buffer object.
|
325
|
+
def write_int64(*n)
|
326
|
+
n.each do |i|
|
327
|
+
hi = (i >> 32) & 0xFFFFFFFF
|
328
|
+
lo = i & 0xFFFFFFFF
|
329
|
+
@content << [hi, lo].pack("N2")
|
330
|
+
end
|
331
|
+
self
|
329
332
|
end
|
330
|
-
self
|
331
|
-
end
|
332
333
|
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
334
|
+
# Writes each argument to the buffer as a network-byte-order-encoded
|
335
|
+
# long (4-byte) integer. Does not alter the read position. Returns the
|
336
|
+
# buffer object.
|
337
|
+
def write_long(*n)
|
338
|
+
@content << n.pack("N*")
|
339
|
+
self
|
340
|
+
end
|
340
341
|
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
342
|
+
# Writes each argument to the buffer as a byte. Does not alter the read
|
343
|
+
# position. Returns the buffer object.
|
344
|
+
def write_byte(*n)
|
345
|
+
n.each { |b| @content << b.chr }
|
346
|
+
self
|
347
|
+
end
|
347
348
|
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
349
|
+
# Writes each argument to the buffer as an SSH2-encoded string. Each
|
350
|
+
# string is prefixed by its length, encoded as a 4-byte long integer.
|
351
|
+
# Does not alter the read position. Returns the buffer object.
|
352
|
+
def write_string(*text)
|
353
|
+
text.each do |string|
|
354
|
+
s = string.to_s
|
355
|
+
write_long(s.bytesize)
|
356
|
+
write(s)
|
357
|
+
end
|
358
|
+
self
|
356
359
|
end
|
357
|
-
self
|
358
|
-
end
|
359
360
|
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
361
|
+
# Writes each argument to the buffer as an SSH2-encoded string. Each
|
362
|
+
# string is prefixed by its length, encoded as a 4-byte long integer.
|
363
|
+
# Does not alter the read position. Returns the buffer object.
|
364
|
+
# Might alter arguments see write_moved
|
365
|
+
def write_mstring(*text)
|
366
|
+
text.each do |string|
|
367
|
+
s = string.to_s
|
368
|
+
write_long(s.bytesize)
|
369
|
+
write_moved(s)
|
370
|
+
end
|
371
|
+
self
|
372
|
+
end
|
372
373
|
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
374
|
+
# Writes each argument to the buffer as a (C-style) boolean, with 1
|
375
|
+
# meaning true, and 0 meaning false. Does not alter the read position.
|
376
|
+
# Returns the buffer object.
|
377
|
+
def write_bool(*b)
|
378
|
+
b.each { |v| @content << (v ? "\1" : "\0") }
|
379
|
+
self
|
380
|
+
end
|
380
381
|
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
382
|
+
# Writes each argument to the buffer as a bignum (SSH2-style). No
|
383
|
+
# checking is done to ensure that the arguments are, in fact, bignums.
|
384
|
+
# Does not alter the read position. Returns the buffer object.
|
385
|
+
def write_bignum(*n)
|
386
|
+
@content << n.map { |b| b.to_ssh }.join
|
387
|
+
self
|
388
|
+
end
|
388
389
|
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
390
|
+
# Writes the given arguments to the buffer as SSH2-encoded keys. Does not
|
391
|
+
# alter the read position. Returns the buffer object.
|
392
|
+
def write_key(*key)
|
393
|
+
key.each { |k| append(k.to_blob) }
|
394
|
+
self
|
395
|
+
end
|
394
396
|
end
|
395
397
|
end
|
396
|
-
end;
|
398
|
+
end;
|