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.
Files changed (111) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/ci.yml +93 -0
  3. data/.gitignore +13 -0
  4. data/.rubocop.yml +21 -0
  5. data/.rubocop_todo.yml +1074 -0
  6. data/.travis.yml +51 -0
  7. data/CHANGES.txt +698 -0
  8. data/Gemfile +13 -0
  9. data/Gemfile.noed25519 +12 -0
  10. data/ISSUE_TEMPLATE.md +30 -0
  11. data/LICENSE.txt +19 -0
  12. data/Manifest +132 -0
  13. data/README.md +287 -0
  14. data/Rakefile +105 -0
  15. data/THANKS.txt +110 -0
  16. data/appveyor.yml +58 -0
  17. data/lib/net/ssh/authentication/agent.rb +284 -0
  18. data/lib/net/ssh/authentication/certificate.rb +183 -0
  19. data/lib/net/ssh/authentication/constants.rb +20 -0
  20. data/lib/net/ssh/authentication/ed25519.rb +185 -0
  21. data/lib/net/ssh/authentication/ed25519_loader.rb +31 -0
  22. data/lib/net/ssh/authentication/key_manager.rb +297 -0
  23. data/lib/net/ssh/authentication/methods/abstract.rb +69 -0
  24. data/lib/net/ssh/authentication/methods/hostbased.rb +72 -0
  25. data/lib/net/ssh/authentication/methods/keyboard_interactive.rb +77 -0
  26. data/lib/net/ssh/authentication/methods/none.rb +34 -0
  27. data/lib/net/ssh/authentication/methods/password.rb +80 -0
  28. data/lib/net/ssh/authentication/methods/publickey.rb +95 -0
  29. data/lib/net/ssh/authentication/pageant.rb +497 -0
  30. data/lib/net/ssh/authentication/pub_key_fingerprint.rb +43 -0
  31. data/lib/net/ssh/authentication/session.rb +163 -0
  32. data/lib/net/ssh/buffer.rb +434 -0
  33. data/lib/net/ssh/buffered_io.rb +202 -0
  34. data/lib/net/ssh/config.rb +406 -0
  35. data/lib/net/ssh/connection/channel.rb +695 -0
  36. data/lib/net/ssh/connection/constants.rb +33 -0
  37. data/lib/net/ssh/connection/event_loop.rb +123 -0
  38. data/lib/net/ssh/connection/keepalive.rb +59 -0
  39. data/lib/net/ssh/connection/session.rb +712 -0
  40. data/lib/net/ssh/connection/term.rb +180 -0
  41. data/lib/net/ssh/errors.rb +106 -0
  42. data/lib/net/ssh/key_factory.rb +218 -0
  43. data/lib/net/ssh/known_hosts.rb +264 -0
  44. data/lib/net/ssh/loggable.rb +62 -0
  45. data/lib/net/ssh/packet.rb +106 -0
  46. data/lib/net/ssh/prompt.rb +62 -0
  47. data/lib/net/ssh/proxy/command.rb +123 -0
  48. data/lib/net/ssh/proxy/errors.rb +16 -0
  49. data/lib/net/ssh/proxy/http.rb +98 -0
  50. data/lib/net/ssh/proxy/https.rb +50 -0
  51. data/lib/net/ssh/proxy/jump.rb +54 -0
  52. data/lib/net/ssh/proxy/socks4.rb +67 -0
  53. data/lib/net/ssh/proxy/socks5.rb +140 -0
  54. data/lib/net/ssh/service/forward.rb +426 -0
  55. data/lib/net/ssh/test/channel.rb +147 -0
  56. data/lib/net/ssh/test/extensions.rb +173 -0
  57. data/lib/net/ssh/test/kex.rb +46 -0
  58. data/lib/net/ssh/test/local_packet.rb +53 -0
  59. data/lib/net/ssh/test/packet.rb +101 -0
  60. data/lib/net/ssh/test/remote_packet.rb +40 -0
  61. data/lib/net/ssh/test/script.rb +180 -0
  62. data/lib/net/ssh/test/socket.rb +65 -0
  63. data/lib/net/ssh/test.rb +94 -0
  64. data/lib/net/ssh/transport/algorithms.rb +502 -0
  65. data/lib/net/ssh/transport/cipher_factory.rb +103 -0
  66. data/lib/net/ssh/transport/constants.rb +40 -0
  67. data/lib/net/ssh/transport/ctr.rb +115 -0
  68. data/lib/net/ssh/transport/hmac/abstract.rb +97 -0
  69. data/lib/net/ssh/transport/hmac/md5.rb +10 -0
  70. data/lib/net/ssh/transport/hmac/md5_96.rb +9 -0
  71. data/lib/net/ssh/transport/hmac/none.rb +13 -0
  72. data/lib/net/ssh/transport/hmac/ripemd160.rb +11 -0
  73. data/lib/net/ssh/transport/hmac/sha1.rb +11 -0
  74. data/lib/net/ssh/transport/hmac/sha1_96.rb +9 -0
  75. data/lib/net/ssh/transport/hmac/sha2_256.rb +11 -0
  76. data/lib/net/ssh/transport/hmac/sha2_256_96.rb +9 -0
  77. data/lib/net/ssh/transport/hmac/sha2_256_etm.rb +12 -0
  78. data/lib/net/ssh/transport/hmac/sha2_512.rb +11 -0
  79. data/lib/net/ssh/transport/hmac/sha2_512_96.rb +9 -0
  80. data/lib/net/ssh/transport/hmac/sha2_512_etm.rb +12 -0
  81. data/lib/net/ssh/transport/hmac.rb +47 -0
  82. data/lib/net/ssh/transport/identity_cipher.rb +57 -0
  83. data/lib/net/ssh/transport/kex/abstract.rb +130 -0
  84. data/lib/net/ssh/transport/kex/abstract5656.rb +72 -0
  85. data/lib/net/ssh/transport/kex/curve25519_sha256.rb +39 -0
  86. data/lib/net/ssh/transport/kex/curve25519_sha256_loader.rb +30 -0
  87. data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha1.rb +37 -0
  88. data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha256.rb +11 -0
  89. data/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb +122 -0
  90. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb +72 -0
  91. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha256.rb +11 -0
  92. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp256.rb +39 -0
  93. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp384.rb +21 -0
  94. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp521.rb +21 -0
  95. data/lib/net/ssh/transport/kex.rb +31 -0
  96. data/lib/net/ssh/transport/key_expander.rb +30 -0
  97. data/lib/net/ssh/transport/openssl.rb +253 -0
  98. data/lib/net/ssh/transport/packet_stream.rb +280 -0
  99. data/lib/net/ssh/transport/server_version.rb +77 -0
  100. data/lib/net/ssh/transport/session.rb +354 -0
  101. data/lib/net/ssh/transport/state.rb +208 -0
  102. data/lib/net/ssh/verifiers/accept_new.rb +33 -0
  103. data/lib/net/ssh/verifiers/accept_new_or_local_tunnel.rb +33 -0
  104. data/lib/net/ssh/verifiers/always.rb +58 -0
  105. data/lib/net/ssh/verifiers/never.rb +19 -0
  106. data/lib/net/ssh/version.rb +68 -0
  107. data/lib/net/ssh.rb +330 -0
  108. data/net-ssh-public_cert.pem +20 -0
  109. data/net-ssh.gemspec +44 -0
  110. data/support/ssh_tunnel_bug.rb +65 -0
  111. 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