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,173 @@
|
|
1
|
+
require 'net/ssh/buffer'
|
2
|
+
require 'net/ssh/packet'
|
3
|
+
require 'net/ssh/buffered_io'
|
4
|
+
require 'net/ssh/connection/channel'
|
5
|
+
require 'net/ssh/connection/constants'
|
6
|
+
require 'net/ssh/transport/constants'
|
7
|
+
require 'net/ssh/transport/packet_stream'
|
8
|
+
|
9
|
+
module Net
|
10
|
+
module SSH
|
11
|
+
module Test
|
12
|
+
# A collection of modules used to extend/override the default behavior of
|
13
|
+
# Net::SSH internals for ease of testing. As a consumer of Net::SSH, you'll
|
14
|
+
# never need to use this directly--they're all used under the covers by
|
15
|
+
# the Net::SSH::Test system.
|
16
|
+
module Extensions
|
17
|
+
# An extension to Net::SSH::BufferedIo (assumes that the underlying IO
|
18
|
+
# is actually a StringIO). Facilitates unit testing.
|
19
|
+
module BufferedIo
|
20
|
+
# Returns +true+ if the position in the stream is less than the total
|
21
|
+
# length of the stream.
|
22
|
+
def select_for_read?
|
23
|
+
pos < size
|
24
|
+
end
|
25
|
+
|
26
|
+
# Set this to +true+ if you want the IO to pretend to be available for writing
|
27
|
+
attr_accessor :select_for_write
|
28
|
+
|
29
|
+
# Set this to +true+ if you want the IO to pretend to be in an error state
|
30
|
+
attr_accessor :select_for_error
|
31
|
+
|
32
|
+
alias select_for_write? select_for_write
|
33
|
+
alias select_for_error? select_for_error
|
34
|
+
end
|
35
|
+
|
36
|
+
# An extension to Net::SSH::Transport::PacketStream (assumes that the
|
37
|
+
# underlying IO is actually a StringIO). Facilitates unit testing.
|
38
|
+
module PacketStream
|
39
|
+
include BufferedIo # make sure we get the extensions here, too
|
40
|
+
|
41
|
+
def self.included(base) #:nodoc:
|
42
|
+
base.send :alias_method, :real_available_for_read?, :available_for_read?
|
43
|
+
base.send :alias_method, :available_for_read?, :test_available_for_read?
|
44
|
+
|
45
|
+
base.send :alias_method, :real_enqueue_packet, :enqueue_packet
|
46
|
+
base.send :alias_method, :enqueue_packet, :test_enqueue_packet
|
47
|
+
|
48
|
+
base.send :alias_method, :real_poll_next_packet, :poll_next_packet
|
49
|
+
base.send :alias_method, :poll_next_packet, :test_poll_next_packet
|
50
|
+
end
|
51
|
+
|
52
|
+
# Called when another packet should be inspected from the current
|
53
|
+
# script. If the next packet is a remote packet, it pops it off the
|
54
|
+
# script and shoves it onto this IO object, making it available to
|
55
|
+
# be read.
|
56
|
+
def idle!
|
57
|
+
return false unless script.next(:first)
|
58
|
+
|
59
|
+
if script.next(:first).remote?
|
60
|
+
self.string << script.next.to_s
|
61
|
+
self.pos = pos
|
62
|
+
end
|
63
|
+
|
64
|
+
return true
|
65
|
+
end
|
66
|
+
|
67
|
+
# The testing version of Net::SSH::Transport::PacketStream#available_for_read?.
|
68
|
+
# Returns true if there is data pending to be read. Otherwise calls #idle!.
|
69
|
+
def test_available_for_read?
|
70
|
+
return true if select_for_read?
|
71
|
+
|
72
|
+
idle!
|
73
|
+
false
|
74
|
+
end
|
75
|
+
|
76
|
+
# The testing version of Net::SSH::Transport::PacketStream#enqueued_packet.
|
77
|
+
# Simply calls Net::SSH::Test::Script#process on the packet.
|
78
|
+
def test_enqueue_packet(payload)
|
79
|
+
packet = Net::SSH::Buffer.new(payload.to_s)
|
80
|
+
script.process(packet)
|
81
|
+
end
|
82
|
+
|
83
|
+
# The testing version of Net::SSH::Transport::PacketStream#poll_next_packet.
|
84
|
+
# Reads the next available packet from the IO object and returns it.
|
85
|
+
def test_poll_next_packet
|
86
|
+
return nil if available <= 0
|
87
|
+
|
88
|
+
packet = Net::SSH::Buffer.new(read_available(4))
|
89
|
+
length = packet.read_long
|
90
|
+
Net::SSH::Packet.new(read_available(length))
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# An extension to Net::SSH::Connection::Channel. Facilitates unit testing.
|
95
|
+
module Channel
|
96
|
+
def self.included(base) #:nodoc:
|
97
|
+
base.send :alias_method, :send_data_for_real, :send_data
|
98
|
+
base.send :alias_method, :send_data, :send_data_for_test
|
99
|
+
end
|
100
|
+
|
101
|
+
# The testing version of Net::SSH::Connection::Channel#send_data. Calls
|
102
|
+
# the original implementation, and then immediately enqueues the data for
|
103
|
+
# output so that scripted sends are properly interpreted as discrete
|
104
|
+
# (rather than concatenated) data packets.
|
105
|
+
def send_data_for_test(data)
|
106
|
+
send_data_for_real(data)
|
107
|
+
enqueue_pending_output
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# An extension to the built-in ::IO class. Simply redefines IO.select
|
112
|
+
# so that it can be scripted in Net::SSH unit tests.
|
113
|
+
module IO
|
114
|
+
def self.included(base) #:nodoc:
|
115
|
+
base.extend(ClassMethods)
|
116
|
+
end
|
117
|
+
|
118
|
+
@extension_enabled = false
|
119
|
+
|
120
|
+
def self.with_test_extension(&block)
|
121
|
+
orig_value = @extension_enabled
|
122
|
+
@extension_enabled = true
|
123
|
+
begin
|
124
|
+
yield
|
125
|
+
ensure
|
126
|
+
@extension_enabled = orig_value
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def self.extension_enabled?
|
131
|
+
@extension_enabled
|
132
|
+
end
|
133
|
+
|
134
|
+
module ClassMethods
|
135
|
+
def self.extended(obj) #:nodoc:
|
136
|
+
class <<obj
|
137
|
+
alias_method :select_for_real, :select
|
138
|
+
alias_method :select, :select_for_test
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
# The testing version of ::IO.select. Assumes that all readers,
|
143
|
+
# writers, and errors arrays are either nil, or contain only objects
|
144
|
+
# that mix in Net::SSH::Test::Extensions::BufferedIo.
|
145
|
+
def select_for_test(readers=nil, writers=nil, errors=nil, wait=nil)
|
146
|
+
return select_for_real(readers, writers, errors, wait) unless Net::SSH::Test::Extensions::IO.extension_enabled?
|
147
|
+
|
148
|
+
ready_readers = Array(readers).select { |r| r.select_for_read? }
|
149
|
+
ready_writers = Array(writers).select { |r| r.select_for_write? }
|
150
|
+
ready_errors = Array(errors).select { |r| r.select_for_error? }
|
151
|
+
|
152
|
+
return [ready_readers, ready_writers, ready_errors] if ready_readers.any? || ready_writers.any? || ready_errors.any?
|
153
|
+
|
154
|
+
processed = 0
|
155
|
+
Array(readers).each do |reader|
|
156
|
+
processed += 1 if reader.idle!
|
157
|
+
end
|
158
|
+
|
159
|
+
raise "no readers were ready for reading, and none had any incoming packets" if processed == 0 && wait != 0
|
160
|
+
|
161
|
+
[[], [], []]
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
Net::SSH::BufferedIo.send(:include, Net::SSH::Test::Extensions::BufferedIo)
|
171
|
+
Net::SSH::Transport::PacketStream.send(:include, Net::SSH::Test::Extensions::PacketStream)
|
172
|
+
Net::SSH::Connection::Channel.send(:include, Net::SSH::Test::Extensions::Channel)
|
173
|
+
IO.send(:include, Net::SSH::Test::Extensions::IO)
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
|
3
|
+
require 'net/ssh/errors'
|
4
|
+
require 'net/ssh/transport/algorithms'
|
5
|
+
require 'net/ssh/transport/constants'
|
6
|
+
require 'net/ssh/transport/kex'
|
7
|
+
|
8
|
+
module Net
|
9
|
+
module SSH
|
10
|
+
module Test
|
11
|
+
# An implementation of a key-exchange strategy specifically for unit tests.
|
12
|
+
# (This strategy would never really work against a real SSH server--it makes
|
13
|
+
# too many assumptions about the server's response.)
|
14
|
+
#
|
15
|
+
# This registers itself with the transport key-exchange system as the
|
16
|
+
# "test" algorithm.
|
17
|
+
class Kex
|
18
|
+
include Net::SSH::Transport::Constants
|
19
|
+
|
20
|
+
# Creates a new instance of the testing key-exchange algorithm with the
|
21
|
+
# given arguments.
|
22
|
+
def initialize(algorithms, connection, data)
|
23
|
+
@connection = connection
|
24
|
+
end
|
25
|
+
|
26
|
+
# Exchange keys with the server. This returns a hash of constant values,
|
27
|
+
# and does not actually exchange keys.
|
28
|
+
def exchange_keys
|
29
|
+
result = Net::SSH::Buffer.from(:byte, NEWKEYS)
|
30
|
+
@connection.send_message(result)
|
31
|
+
|
32
|
+
buffer = @connection.next_message
|
33
|
+
raise Net::SSH::Exception, "expected NEWKEYS" unless buffer.type == NEWKEYS
|
34
|
+
|
35
|
+
{ session_id: "abc-xyz",
|
36
|
+
server_key: OpenSSL::PKey::RSA.new(512),
|
37
|
+
shared_secret: OpenSSL::BN.new("1234567890", 10),
|
38
|
+
hashing_algorithm: OpenSSL::Digest::SHA1 }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
Net::SSH::Transport::Algorithms::ALGORITHMS[:kex] << "test"
|
46
|
+
Net::SSH::Transport::Kex::MAP["test"] = Net::SSH::Test::Kex
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'net/ssh/packet'
|
2
|
+
require 'net/ssh/test/packet'
|
3
|
+
|
4
|
+
module Net
|
5
|
+
module SSH
|
6
|
+
module Test
|
7
|
+
# This is a specialization of Net::SSH::Test::Packet for representing mock
|
8
|
+
# packets that are sent from the local (client) host. These are created
|
9
|
+
# automatically by Net::SSH::Test::Script and Net::SSH::Test::Channel by any
|
10
|
+
# of the sends_* methods.
|
11
|
+
class LocalPacket < Packet
|
12
|
+
attr_reader :init
|
13
|
+
|
14
|
+
# Extend the default Net::SSH::Test::Packet constructor to also accept an
|
15
|
+
# optional block, which is used to finalize the initialization of the
|
16
|
+
# packet when #process is first called.
|
17
|
+
def initialize(type, *args, &block)
|
18
|
+
super(type, *args)
|
19
|
+
@init = block
|
20
|
+
end
|
21
|
+
|
22
|
+
# Returns +true+; this is a local packet.
|
23
|
+
def local?
|
24
|
+
true
|
25
|
+
end
|
26
|
+
|
27
|
+
# Called by Net::SSH::Test::Extensions::PacketStream#test_enqueue_packet
|
28
|
+
# to mimic remote processing of a locally-sent packet. It compares the
|
29
|
+
# packet it was given with the contents of this LocalPacket's data, to see
|
30
|
+
# if what was sent matches what was scripted. If it differs in any way,
|
31
|
+
# an exception is raised.
|
32
|
+
def process(packet)
|
33
|
+
@init.call(Net::SSH::Packet.new(packet.to_s)) if @init
|
34
|
+
type = packet.read_byte
|
35
|
+
raise "expected #{@type}, but got #{type}" if @type != type
|
36
|
+
|
37
|
+
@data.zip(types).each do |expected, _type|
|
38
|
+
_type ||= case expected
|
39
|
+
when nil then break
|
40
|
+
when Numeric then :long
|
41
|
+
when String then :string
|
42
|
+
when TrueClass, FalseClass then :bool
|
43
|
+
end
|
44
|
+
|
45
|
+
actual = packet.send("read_#{_type}")
|
46
|
+
next if expected.nil?
|
47
|
+
raise "expected #{_type} #{expected.inspect} but got #{actual.inspect}" unless expected == actual
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'net/ssh/connection/constants'
|
2
|
+
require 'net/ssh/transport/constants'
|
3
|
+
|
4
|
+
module Net
|
5
|
+
module SSH
|
6
|
+
module Test
|
7
|
+
# This is an abstract class, not to be instantiated directly, subclassed by
|
8
|
+
# Net::SSH::Test::LocalPacket and Net::SSH::Test::RemotePacket. It implements
|
9
|
+
# functionality common to those subclasses.
|
10
|
+
#
|
11
|
+
# These packets are not true packets, in that they don't represent what was
|
12
|
+
# actually sent between the hosst; rather, they represent what was expected
|
13
|
+
# to be sent, as dictated by the script (Net::SSH::Test::Script). Thus,
|
14
|
+
# though they are defined with data elements, these data elements are used
|
15
|
+
# to either validate data that was sent by the local host (Net::SSH::Test::LocalPacket)
|
16
|
+
# or to mimic the sending of data by the remote host (Net::SSH::Test::RemotePacket).
|
17
|
+
class Packet
|
18
|
+
include Net::SSH::Transport::Constants
|
19
|
+
include Net::SSH::Connection::Constants
|
20
|
+
|
21
|
+
# Register a custom channel request. extra_parts is an array of types
|
22
|
+
# of extra parameters
|
23
|
+
def self.register_channel_request(request, extra_parts)
|
24
|
+
@registered_requests ||= {}
|
25
|
+
@registered_requests[request] = { extra_parts: extra_parts }
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.registered_channel_requests(request)
|
29
|
+
@registered_requests && @registered_requests[request]
|
30
|
+
end
|
31
|
+
|
32
|
+
# Ceate a new packet of the given +type+, and with +args+ being a list of
|
33
|
+
# data elements in the order expected for packets of the given +type+
|
34
|
+
# (see #types).
|
35
|
+
def initialize(type, *args)
|
36
|
+
@type = self.class.const_get(type.to_s.upcase)
|
37
|
+
@data = args
|
38
|
+
end
|
39
|
+
|
40
|
+
# The default for +remote?+ is false. Subclasses should override as necessary.
|
41
|
+
def remote?
|
42
|
+
false
|
43
|
+
end
|
44
|
+
|
45
|
+
# The default for +local?+ is false. Subclasses should override as necessary.
|
46
|
+
def local?
|
47
|
+
false
|
48
|
+
end
|
49
|
+
|
50
|
+
# Instantiates the packets data elements. When the packet was first defined,
|
51
|
+
# some elements may not have been fully realized, and were described as
|
52
|
+
# Proc objects rather than atomic types. This invokes those Proc objects
|
53
|
+
# and replaces them with their returned values. This allows for values
|
54
|
+
# like Net::SSH::Test::Channel#remote_id to be used in scripts before
|
55
|
+
# the remote_id is known (since it is only known after a channel has been
|
56
|
+
# confirmed open).
|
57
|
+
def instantiate!
|
58
|
+
@data.map! { |i| i.respond_to?(:call) ? i.call : i }
|
59
|
+
end
|
60
|
+
|
61
|
+
# Returns an array of symbols describing the data elements for packets of
|
62
|
+
# the same type as this packet. These types are used to either validate
|
63
|
+
# sent packets (Net::SSH::Test::LocalPacket) or build received packets
|
64
|
+
# (Net::SSH::Test::RemotePacket).
|
65
|
+
#
|
66
|
+
# Not all packet types are defined here. As new packet types are required
|
67
|
+
# (e.g., a unit test needs to test that the remote host sent a packet that
|
68
|
+
# is not implemented here), the description of that packet should be
|
69
|
+
# added. Unsupported packet types will otherwise raise an exception.
|
70
|
+
def types
|
71
|
+
@types ||= case @type
|
72
|
+
when KEXINIT
|
73
|
+
%i[long long long long
|
74
|
+
string string string string string string string string string string
|
75
|
+
bool]
|
76
|
+
when NEWKEYS then []
|
77
|
+
when CHANNEL_OPEN then %i[string long long long]
|
78
|
+
when CHANNEL_OPEN_CONFIRMATION then %i[long long long long]
|
79
|
+
when CHANNEL_DATA then %i[long string]
|
80
|
+
when CHANNEL_EXTENDED_DATA then %i[long long string]
|
81
|
+
when CHANNEL_EOF, CHANNEL_CLOSE, CHANNEL_SUCCESS, CHANNEL_FAILURE then [:long]
|
82
|
+
when CHANNEL_REQUEST
|
83
|
+
parts = %i[long string bool]
|
84
|
+
case @data[1]
|
85
|
+
when "exec", "subsystem","shell" then parts << :string
|
86
|
+
when "exit-status" then parts << :long
|
87
|
+
when "pty-req" then parts.concat(%i[string long long long long string])
|
88
|
+
when "env" then parts.contact(%i[string string])
|
89
|
+
else
|
90
|
+
request = Packet.registered_channel_requests(@data[1])
|
91
|
+
raise "don't know what to do about #{@data[1]} channel request" unless request
|
92
|
+
|
93
|
+
parts.concat(request[:extra_parts])
|
94
|
+
end
|
95
|
+
else raise "don't know how to parse packet type #{@type}"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'net/ssh/buffer'
|
2
|
+
require 'net/ssh/test/packet'
|
3
|
+
|
4
|
+
module Net
|
5
|
+
module SSH
|
6
|
+
module Test
|
7
|
+
# This is a specialization of Net::SSH::Test::Packet for representing mock
|
8
|
+
# packets that are received by the local (client) host. These are created
|
9
|
+
# automatically by Net::SSH::Test::Script and Net::SSH::Test::Channel by any
|
10
|
+
# of the gets_* methods.
|
11
|
+
class RemotePacket < Packet
|
12
|
+
# Returns +true+; this is a remote packet.
|
13
|
+
def remote?
|
14
|
+
true
|
15
|
+
end
|
16
|
+
|
17
|
+
# The #process method should only be called on Net::SSH::Test::LocalPacket
|
18
|
+
# packets; if it is attempted on a remote packet, then it is an expectation
|
19
|
+
# mismatch (a remote packet was received when a local packet was expected
|
20
|
+
# to be sent). This will happen when either your test script
|
21
|
+
# (Net::SSH::Test::Script) or your program are wrong.
|
22
|
+
def process(packet)
|
23
|
+
raise "received packet type #{packet.read_byte} and was not expecting any packet"
|
24
|
+
end
|
25
|
+
|
26
|
+
# Returns this remote packet as a string, suitable for parsing by
|
27
|
+
# Net::SSH::Transport::PacketStream and friends. When a remote packet is
|
28
|
+
# received, this method is called and the result concatenated onto the
|
29
|
+
# input buffer for the packet stream.
|
30
|
+
def to_s
|
31
|
+
@to_s ||= begin
|
32
|
+
instantiate!
|
33
|
+
string = Net::SSH::Buffer.from(:byte, @type, *types.zip(@data).flatten).to_s
|
34
|
+
[string.length, string].pack("NA*")
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,180 @@
|
|
1
|
+
require 'net/ssh/test/channel'
|
2
|
+
require 'net/ssh/test/local_packet'
|
3
|
+
require 'net/ssh/test/remote_packet'
|
4
|
+
|
5
|
+
module Net
|
6
|
+
module SSH
|
7
|
+
module Test
|
8
|
+
# Represents a sequence of scripted events that identify the behavior that
|
9
|
+
# a test expects. Methods named "sends_*" create events for packets being
|
10
|
+
# sent from the local to the remote host, and methods named "gets_*" create
|
11
|
+
# events for packets being received by the local from the remote host.
|
12
|
+
#
|
13
|
+
# A reference to a script. is generally obtained in a unit test via the
|
14
|
+
# Net::SSH::Test#story helper method:
|
15
|
+
#
|
16
|
+
# story do |script|
|
17
|
+
# channel = script.opens_channel
|
18
|
+
# ...
|
19
|
+
# end
|
20
|
+
class Script
|
21
|
+
# The list of scripted events. These will be Net::SSH::Test::LocalPacket
|
22
|
+
# and Net::SSH::Test::RemotePacket instances.
|
23
|
+
attr_reader :events
|
24
|
+
|
25
|
+
# Create a new, empty script.
|
26
|
+
def initialize
|
27
|
+
@events = []
|
28
|
+
end
|
29
|
+
|
30
|
+
# Scripts the opening of a channel by adding a local packet sending the
|
31
|
+
# channel open request, and if +confirm+ is true (the default), also
|
32
|
+
# adding a remote packet confirming the new channel.
|
33
|
+
#
|
34
|
+
# A new Net::SSH::Test::Channel instance is returned, which can be used
|
35
|
+
# to script additional channel operations.
|
36
|
+
def opens_channel(confirm=true)
|
37
|
+
channel = Channel.new(self)
|
38
|
+
channel.remote_id = 5555
|
39
|
+
|
40
|
+
events << LocalPacket.new(:channel_open) { |p| channel.local_id = p[:remote_id] }
|
41
|
+
|
42
|
+
events << RemotePacket.new(:channel_open_confirmation, channel.local_id, channel.remote_id, 0x20000, 0x10000) if confirm
|
43
|
+
|
44
|
+
channel
|
45
|
+
end
|
46
|
+
|
47
|
+
# A convenience method for adding an arbitrary local packet to the events
|
48
|
+
# list.
|
49
|
+
def sends(type, *args, &block)
|
50
|
+
events << LocalPacket.new(type, *args, &block)
|
51
|
+
end
|
52
|
+
|
53
|
+
# A convenience method for adding an arbitrary remote packet to the events
|
54
|
+
# list.
|
55
|
+
def gets(type, *args)
|
56
|
+
events << RemotePacket.new(type, *args)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Scripts the sending of a new channel request packet to the remote host.
|
60
|
+
# +channel+ should be an instance of Net::SSH::Test::Channel. +request+
|
61
|
+
# is a string naming the request type to send, +reply+ is a boolean
|
62
|
+
# indicating whether a response to this packet is required , and +data+
|
63
|
+
# is any additional request-specific data that this packet should send.
|
64
|
+
# +success+ indicates whether the response (if one is required) should be
|
65
|
+
# success or failure. If +data+ is an array it will be treated as multiple
|
66
|
+
# data.
|
67
|
+
#
|
68
|
+
# If a reply is desired, a remote packet will also be queued, :channel_success
|
69
|
+
# if +success+ is true, or :channel_failure if +success+ is false.
|
70
|
+
#
|
71
|
+
# This will typically be called via Net::SSH::Test::Channel#sends_exec or
|
72
|
+
# Net::SSH::Test::Channel#sends_subsystem.
|
73
|
+
def sends_channel_request(channel, request, reply, data, success=true)
|
74
|
+
if data.is_a? Array
|
75
|
+
events << LocalPacket.new(:channel_request, channel.remote_id, request, reply, *data)
|
76
|
+
else
|
77
|
+
events << LocalPacket.new(:channel_request, channel.remote_id, request, reply, data)
|
78
|
+
end
|
79
|
+
if reply
|
80
|
+
if success
|
81
|
+
events << RemotePacket.new(:channel_success, channel.local_id)
|
82
|
+
else
|
83
|
+
events << RemotePacket.new(:channel_failure, channel.local_id)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Scripts the sending of a channel data packet. +channel+ must be a
|
89
|
+
# Net::SSH::Test::Channel object, and +data+ is the (string) data to
|
90
|
+
# expect will be sent.
|
91
|
+
#
|
92
|
+
# This will typically be called via Net::SSH::Test::Channel#sends_data.
|
93
|
+
def sends_channel_data(channel, data)
|
94
|
+
events << LocalPacket.new(:channel_data, channel.remote_id, data)
|
95
|
+
end
|
96
|
+
|
97
|
+
# Scripts the sending of a channel EOF packet from the given
|
98
|
+
# Net::SSH::Test::Channel +channel+. This will typically be called via
|
99
|
+
# Net::SSH::Test::Channel#sends_eof.
|
100
|
+
def sends_channel_eof(channel)
|
101
|
+
events << LocalPacket.new(:channel_eof, channel.remote_id)
|
102
|
+
end
|
103
|
+
|
104
|
+
# Scripts the sending of a channel close packet from the given
|
105
|
+
# Net::SSH::Test::Channel +channel+. This will typically be called via
|
106
|
+
# Net::SSH::Test::Channel#sends_close.
|
107
|
+
def sends_channel_close(channel)
|
108
|
+
events << LocalPacket.new(:channel_close, channel.remote_id)
|
109
|
+
end
|
110
|
+
|
111
|
+
# Scripts the sending of a channel request pty packets from the given
|
112
|
+
# Net::SSH::Test::Channel +channel+. This will typically be called via
|
113
|
+
# Net::SSH::Test::Channel#sends_request_pty.
|
114
|
+
def sends_channel_request_pty(channel)
|
115
|
+
data = ['pty-req', false]
|
116
|
+
data += Net::SSH::Connection::Channel::VALID_PTY_OPTIONS.merge(modes: "\0").values
|
117
|
+
events << LocalPacket.new(:channel_request, channel.remote_id, *data)
|
118
|
+
end
|
119
|
+
|
120
|
+
# Scripts the reception of a channel data packet from the remote host by
|
121
|
+
# the given Net::SSH::Test::Channel +channel+. This will typically be
|
122
|
+
# called via Net::SSH::Test::Channel#gets_data.
|
123
|
+
def gets_channel_data(channel, data)
|
124
|
+
events << RemotePacket.new(:channel_data, channel.local_id, data)
|
125
|
+
end
|
126
|
+
|
127
|
+
# Scripts the reception of a channel extended data packet from the remote
|
128
|
+
# host by the given Net::SSH::Test::Channel +channel+. This will typically
|
129
|
+
# be called via Net::SSH::Test::Channel#gets_extended_data.
|
130
|
+
#
|
131
|
+
# Currently the only extended data type is stderr == 1.
|
132
|
+
def gets_channel_extended_data(channel, data)
|
133
|
+
events << RemotePacket.new(:channel_extended_data, channel.local_id, 1, data)
|
134
|
+
end
|
135
|
+
|
136
|
+
# Scripts the reception of a channel request packet from the remote host by
|
137
|
+
# the given Net::SSH::Test::Channel +channel+. This will typically be
|
138
|
+
# called via Net::SSH::Test::Channel#gets_exit_status.
|
139
|
+
def gets_channel_request(channel, request, reply, data)
|
140
|
+
events << RemotePacket.new(:channel_request, channel.local_id, request, reply, data)
|
141
|
+
end
|
142
|
+
|
143
|
+
# Scripts the reception of a channel EOF packet from the remote host by
|
144
|
+
# the given Net::SSH::Test::Channel +channel+. This will typically be
|
145
|
+
# called via Net::SSH::Test::Channel#gets_eof.
|
146
|
+
def gets_channel_eof(channel)
|
147
|
+
events << RemotePacket.new(:channel_eof, channel.local_id)
|
148
|
+
end
|
149
|
+
|
150
|
+
# Scripts the reception of a channel close packet from the remote host by
|
151
|
+
# the given Net::SSH::Test::Channel +channel+. This will typically be
|
152
|
+
# called via Net::SSH::Test::Channel#gets_close.
|
153
|
+
def gets_channel_close(channel)
|
154
|
+
events << RemotePacket.new(:channel_close, channel.local_id)
|
155
|
+
end
|
156
|
+
|
157
|
+
# By default, removes the next event in the list and returns it. However,
|
158
|
+
# this can also be used to non-destructively peek at the next event in the
|
159
|
+
# list, by passing :first as the argument.
|
160
|
+
#
|
161
|
+
# # remove the next event and return it
|
162
|
+
# event = script.next
|
163
|
+
#
|
164
|
+
# # peek at the next event
|
165
|
+
# event = script.next(:first)
|
166
|
+
def next(mode=:shift)
|
167
|
+
events.send(mode)
|
168
|
+
end
|
169
|
+
|
170
|
+
# Compare the given packet against the next event in the list. If there is
|
171
|
+
# no next event, an exception will be raised. This is called by
|
172
|
+
# Net::SSH::Test::Extensions::PacketStream#test_enqueue_packet.
|
173
|
+
def process(packet)
|
174
|
+
event = events.shift or raise "end of script reached, but got a packet type #{packet.read_byte}"
|
175
|
+
event.process(packet)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'stringio'
|
3
|
+
require 'net/ssh/test/extensions'
|
4
|
+
require 'net/ssh/test/script'
|
5
|
+
|
6
|
+
module Net
|
7
|
+
module SSH
|
8
|
+
module Test
|
9
|
+
# A mock socket implementation for use in testing. It implements the minimum
|
10
|
+
# necessary interface for interacting with the rest of the Net::SSH::Test
|
11
|
+
# system.
|
12
|
+
class Socket < StringIO
|
13
|
+
attr_reader :host, :port
|
14
|
+
|
15
|
+
# The Net::SSH::Test::Script object in use by this socket. This is the
|
16
|
+
# canonical script instance that should be used for any test depending on
|
17
|
+
# this socket instance.
|
18
|
+
attr_reader :script
|
19
|
+
|
20
|
+
# Create a new test socket. This will also instantiate a new Net::SSH::Test::Script
|
21
|
+
# and seed it with the necessary events to power the initialization of the
|
22
|
+
# connection.
|
23
|
+
def initialize
|
24
|
+
extend(Net::SSH::Transport::PacketStream)
|
25
|
+
super "SSH-2.0-Test\r\n"
|
26
|
+
|
27
|
+
@script = Script.new
|
28
|
+
|
29
|
+
script.sends(:kexinit)
|
30
|
+
script.gets(:kexinit, 1, 2, 3, 4, "test", "ssh-rsa", "none", "none", "none", "none", "none", "none", "", "", false)
|
31
|
+
script.sends(:newkeys)
|
32
|
+
script.gets(:newkeys)
|
33
|
+
end
|
34
|
+
|
35
|
+
# This doesn't actually do anything, since we don't really care what gets
|
36
|
+
# written.
|
37
|
+
def write(data)
|
38
|
+
# black hole, because we don't actually care about what gets written
|
39
|
+
end
|
40
|
+
|
41
|
+
# Allows the socket to also mimic a socket factory, simply returning
|
42
|
+
# +self+.
|
43
|
+
def open(host, port, options={})
|
44
|
+
@host, @port = host, port
|
45
|
+
self
|
46
|
+
end
|
47
|
+
|
48
|
+
# Returns a sockaddr struct for the port and host that were used when the
|
49
|
+
# socket was instantiated.
|
50
|
+
def getpeername
|
51
|
+
::Socket.sockaddr_in(port, host)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Alias to #read, but never returns nil (returns an empty string instead).
|
55
|
+
def recv(n)
|
56
|
+
read(n) || ""
|
57
|
+
end
|
58
|
+
|
59
|
+
def readpartial(n)
|
60
|
+
recv(n)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|