net-ssh 2.7.0 → 7.3.0

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 (199) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +0 -0
  3. data/.dockerignore +6 -0
  4. data/.github/FUNDING.yml +1 -0
  5. data/.github/config/rubocop_linter_action.yml +4 -0
  6. data/.github/workflows/ci-with-docker.yml +44 -0
  7. data/.github/workflows/ci.yml +94 -0
  8. data/.github/workflows/rubocop.yml +16 -0
  9. data/.gitignore +15 -0
  10. data/.rubocop.yml +22 -0
  11. data/.rubocop_todo.yml +1081 -0
  12. data/CHANGES.txt +387 -0
  13. data/DEVELOPMENT.md +23 -0
  14. data/Dockerfile +29 -0
  15. data/Dockerfile.openssl3 +17 -0
  16. data/Gemfile +13 -0
  17. data/Gemfile.noed25519 +12 -0
  18. data/Gemfile.norbnacl +12 -0
  19. data/ISSUE_TEMPLATE.md +30 -0
  20. data/Manifest +4 -5
  21. data/README.md +303 -0
  22. data/Rakefile +174 -40
  23. data/SECURITY.md +4 -0
  24. data/THANKS.txt +25 -0
  25. data/appveyor.yml +58 -0
  26. data/docker-compose.yml +25 -0
  27. data/lib/net/ssh/authentication/agent.rb +279 -18
  28. data/lib/net/ssh/authentication/certificate.rb +183 -0
  29. data/lib/net/ssh/authentication/constants.rb +17 -15
  30. data/lib/net/ssh/authentication/ed25519.rb +184 -0
  31. data/lib/net/ssh/authentication/ed25519_loader.rb +31 -0
  32. data/lib/net/ssh/authentication/key_manager.rb +125 -54
  33. data/lib/net/ssh/authentication/methods/abstract.rb +67 -48
  34. data/lib/net/ssh/authentication/methods/hostbased.rb +34 -37
  35. data/lib/net/ssh/authentication/methods/keyboard_interactive.rb +19 -12
  36. data/lib/net/ssh/authentication/methods/none.rb +16 -19
  37. data/lib/net/ssh/authentication/methods/password.rb +56 -19
  38. data/lib/net/ssh/authentication/methods/publickey.rb +96 -55
  39. data/lib/net/ssh/authentication/pageant.rb +483 -246
  40. data/lib/net/ssh/authentication/pub_key_fingerprint.rb +43 -0
  41. data/lib/net/ssh/authentication/session.rb +138 -120
  42. data/lib/net/ssh/buffer.rb +399 -300
  43. data/lib/net/ssh/buffered_io.rb +154 -150
  44. data/lib/net/ssh/config.rb +361 -166
  45. data/lib/net/ssh/connection/channel.rb +640 -596
  46. data/lib/net/ssh/connection/constants.rb +29 -29
  47. data/lib/net/ssh/connection/event_loop.rb +123 -0
  48. data/lib/net/ssh/connection/keepalive.rb +59 -0
  49. data/lib/net/ssh/connection/session.rb +628 -548
  50. data/lib/net/ssh/connection/term.rb +125 -123
  51. data/lib/net/ssh/errors.rb +101 -95
  52. data/lib/net/ssh/key_factory.rb +198 -100
  53. data/lib/net/ssh/known_hosts.rb +221 -98
  54. data/lib/net/ssh/loggable.rb +50 -49
  55. data/lib/net/ssh/packet.rb +83 -79
  56. data/lib/net/ssh/prompt.rb +50 -81
  57. data/lib/net/ssh/proxy/command.rb +108 -60
  58. data/lib/net/ssh/proxy/errors.rb +12 -10
  59. data/lib/net/ssh/proxy/http.rb +82 -78
  60. data/lib/net/ssh/proxy/https.rb +50 -0
  61. data/lib/net/ssh/proxy/jump.rb +54 -0
  62. data/lib/net/ssh/proxy/socks4.rb +5 -8
  63. data/lib/net/ssh/proxy/socks5.rb +18 -20
  64. data/lib/net/ssh/service/forward.rb +383 -255
  65. data/lib/net/ssh/test/channel.rb +145 -136
  66. data/lib/net/ssh/test/extensions.rb +131 -110
  67. data/lib/net/ssh/test/kex.rb +34 -32
  68. data/lib/net/ssh/test/local_packet.rb +46 -44
  69. data/lib/net/ssh/test/packet.rb +89 -70
  70. data/lib/net/ssh/test/remote_packet.rb +32 -30
  71. data/lib/net/ssh/test/script.rb +156 -142
  72. data/lib/net/ssh/test/socket.rb +49 -48
  73. data/lib/net/ssh/test.rb +82 -77
  74. data/lib/net/ssh/transport/aes128_gcm.rb +40 -0
  75. data/lib/net/ssh/transport/aes256_gcm.rb +40 -0
  76. data/lib/net/ssh/transport/algorithms.rb +472 -348
  77. data/lib/net/ssh/transport/chacha20_poly1305_cipher.rb +117 -0
  78. data/lib/net/ssh/transport/chacha20_poly1305_cipher_loader.rb +17 -0
  79. data/lib/net/ssh/transport/cipher_factory.rb +124 -100
  80. data/lib/net/ssh/transport/constants.rb +32 -24
  81. data/lib/net/ssh/transport/ctr.rb +42 -22
  82. data/lib/net/ssh/transport/gcm_cipher.rb +207 -0
  83. data/lib/net/ssh/transport/hmac/abstract.rb +97 -63
  84. data/lib/net/ssh/transport/hmac/md5.rb +0 -2
  85. data/lib/net/ssh/transport/hmac/md5_96.rb +0 -2
  86. data/lib/net/ssh/transport/hmac/none.rb +0 -2
  87. data/lib/net/ssh/transport/hmac/ripemd160.rb +0 -2
  88. data/lib/net/ssh/transport/hmac/sha1.rb +0 -2
  89. data/lib/net/ssh/transport/hmac/sha1_96.rb +0 -2
  90. data/lib/net/ssh/transport/hmac/sha2_256.rb +7 -11
  91. data/lib/net/ssh/transport/hmac/sha2_256_96.rb +4 -8
  92. data/lib/net/ssh/transport/hmac/sha2_256_etm.rb +12 -0
  93. data/lib/net/ssh/transport/hmac/sha2_512.rb +6 -9
  94. data/lib/net/ssh/transport/hmac/sha2_512_96.rb +4 -8
  95. data/lib/net/ssh/transport/hmac/sha2_512_etm.rb +12 -0
  96. data/lib/net/ssh/transport/hmac.rb +14 -12
  97. data/lib/net/ssh/transport/identity_cipher.rb +54 -44
  98. data/lib/net/ssh/transport/kex/abstract.rb +130 -0
  99. data/lib/net/ssh/transport/kex/abstract5656.rb +72 -0
  100. data/lib/net/ssh/transport/kex/curve25519_sha256.rb +39 -0
  101. data/lib/net/ssh/transport/kex/curve25519_sha256_loader.rb +30 -0
  102. data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha1.rb +33 -40
  103. data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha256.rb +11 -0
  104. data/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb +119 -213
  105. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb +53 -61
  106. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha256.rb +5 -9
  107. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp256.rb +36 -90
  108. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp384.rb +18 -10
  109. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp521.rb +18 -10
  110. data/lib/net/ssh/transport/kex.rb +15 -12
  111. data/lib/net/ssh/transport/key_expander.rb +24 -20
  112. data/lib/net/ssh/transport/openssl.rb +161 -124
  113. data/lib/net/ssh/transport/openssl_cipher_extensions.rb +8 -0
  114. data/lib/net/ssh/transport/packet_stream.rb +246 -183
  115. data/lib/net/ssh/transport/server_version.rb +57 -51
  116. data/lib/net/ssh/transport/session.rb +307 -235
  117. data/lib/net/ssh/transport/state.rb +178 -176
  118. data/lib/net/ssh/verifiers/accept_new.rb +33 -0
  119. data/lib/net/ssh/verifiers/accept_new_or_local_tunnel.rb +33 -0
  120. data/lib/net/ssh/verifiers/always.rb +58 -0
  121. data/lib/net/ssh/verifiers/never.rb +19 -0
  122. data/lib/net/ssh/version.rb +57 -51
  123. data/lib/net/ssh.rb +140 -40
  124. data/net-ssh-public_cert.pem +21 -0
  125. data/net-ssh.gemspec +39 -184
  126. data/support/ssh_tunnel_bug.rb +5 -5
  127. data.tar.gz.sig +0 -0
  128. metadata +205 -99
  129. metadata.gz.sig +0 -0
  130. data/README.rdoc +0 -219
  131. data/Rudyfile +0 -96
  132. data/gem-public_cert.pem +0 -20
  133. data/lib/net/ssh/authentication/agent/java_pageant.rb +0 -85
  134. data/lib/net/ssh/authentication/agent/socket.rb +0 -170
  135. data/lib/net/ssh/ruby_compat.rb +0 -51
  136. data/lib/net/ssh/verifiers/lenient.rb +0 -30
  137. data/lib/net/ssh/verifiers/null.rb +0 -12
  138. data/lib/net/ssh/verifiers/secure.rb +0 -54
  139. data/lib/net/ssh/verifiers/strict.rb +0 -24
  140. data/setup.rb +0 -1585
  141. data/support/arcfour_check.rb +0 -20
  142. data/test/README.txt +0 -47
  143. data/test/authentication/methods/common.rb +0 -28
  144. data/test/authentication/methods/test_abstract.rb +0 -51
  145. data/test/authentication/methods/test_hostbased.rb +0 -114
  146. data/test/authentication/methods/test_keyboard_interactive.rb +0 -100
  147. data/test/authentication/methods/test_none.rb +0 -41
  148. data/test/authentication/methods/test_password.rb +0 -52
  149. data/test/authentication/methods/test_publickey.rb +0 -148
  150. data/test/authentication/test_agent.rb +0 -205
  151. data/test/authentication/test_key_manager.rb +0 -218
  152. data/test/authentication/test_session.rb +0 -108
  153. data/test/common.rb +0 -108
  154. data/test/configs/eqsign +0 -3
  155. data/test/configs/exact_match +0 -8
  156. data/test/configs/host_plus +0 -10
  157. data/test/configs/multihost +0 -4
  158. data/test/configs/nohost +0 -19
  159. data/test/configs/numeric_host +0 -4
  160. data/test/configs/send_env +0 -2
  161. data/test/configs/substitutes +0 -8
  162. data/test/configs/wild_cards +0 -14
  163. data/test/connection/test_channel.rb +0 -467
  164. data/test/connection/test_session.rb +0 -526
  165. data/test/known_hosts/github +0 -1
  166. data/test/manual/test_forward.rb +0 -223
  167. data/test/start/test_options.rb +0 -36
  168. data/test/start/test_transport.rb +0 -28
  169. data/test/test_all.rb +0 -11
  170. data/test/test_buffer.rb +0 -433
  171. data/test/test_buffered_io.rb +0 -63
  172. data/test/test_config.rb +0 -151
  173. data/test/test_key_factory.rb +0 -173
  174. data/test/test_known_hosts.rb +0 -13
  175. data/test/transport/hmac/test_md5.rb +0 -41
  176. data/test/transport/hmac/test_md5_96.rb +0 -27
  177. data/test/transport/hmac/test_none.rb +0 -34
  178. data/test/transport/hmac/test_ripemd160.rb +0 -36
  179. data/test/transport/hmac/test_sha1.rb +0 -36
  180. data/test/transport/hmac/test_sha1_96.rb +0 -27
  181. data/test/transport/hmac/test_sha2_256.rb +0 -37
  182. data/test/transport/hmac/test_sha2_256_96.rb +0 -27
  183. data/test/transport/hmac/test_sha2_512.rb +0 -37
  184. data/test/transport/hmac/test_sha2_512_96.rb +0 -27
  185. data/test/transport/kex/test_diffie_hellman_group14_sha1.rb +0 -13
  186. data/test/transport/kex/test_diffie_hellman_group1_sha1.rb +0 -146
  187. data/test/transport/kex/test_diffie_hellman_group_exchange_sha1.rb +0 -92
  188. data/test/transport/kex/test_diffie_hellman_group_exchange_sha256.rb +0 -34
  189. data/test/transport/kex/test_ecdh_sha2_nistp256.rb +0 -161
  190. data/test/transport/kex/test_ecdh_sha2_nistp384.rb +0 -38
  191. data/test/transport/kex/test_ecdh_sha2_nistp521.rb +0 -38
  192. data/test/transport/test_algorithms.rb +0 -330
  193. data/test/transport/test_cipher_factory.rb +0 -443
  194. data/test/transport/test_hmac.rb +0 -34
  195. data/test/transport/test_identity_cipher.rb +0 -40
  196. data/test/transport/test_packet_stream.rb +0 -1755
  197. data/test/transport/test_server_version.rb +0 -78
  198. data/test/transport/test_session.rb +0 -319
  199. data/test/transport/test_state.rb +0 -181
@@ -1,632 +1,712 @@
1
1
  require 'net/ssh/loggable'
2
- require 'net/ssh/ruby_compat'
3
2
  require 'net/ssh/connection/channel'
4
3
  require 'net/ssh/connection/constants'
5
4
  require 'net/ssh/service/forward'
5
+ require 'net/ssh/connection/keepalive'
6
+ require 'net/ssh/connection/event_loop'
7
+
8
+ module Net
9
+ module SSH
10
+ module Connection
11
+ # A session class representing the connection service running on top of
12
+ # the SSH transport layer. It manages the creation of channels (see
13
+ # #open_channel), and the dispatching of messages to the various channels.
14
+ # It also encapsulates the SSH event loop (via #loop and #process),
15
+ # and serves as a central point-of-reference for all SSH-related services (e.g.
16
+ # port forwarding, SFTP, SCP, etc.).
17
+ #
18
+ # You will rarely (if ever) need to instantiate this class directly; rather,
19
+ # you'll almost always use Net::SSH.start to initialize a new network
20
+ # connection, authenticate a user, and return a new connection session,
21
+ # all in one call.
22
+ #
23
+ # Net::SSH.start("localhost", "user") do |ssh|
24
+ # # 'ssh' is an instance of Net::SSH::Connection::Session
25
+ # ssh.exec! "/etc/init.d/some_process start"
26
+ # end
27
+ class Session
28
+ include Loggable
29
+ include Constants
30
+
31
+ # Default IO.select timeout threshold
32
+ DEFAULT_IO_SELECT_TIMEOUT = 300
33
+
34
+ # The underlying transport layer abstraction (see Net::SSH::Transport::Session).
35
+ attr_reader :transport
36
+
37
+ # The map of options that were used to initialize this instance.
38
+ attr_reader :options
39
+
40
+ # The collection of custom properties for this instance. (See #[] and #[]=).
41
+ attr_reader :properties
42
+
43
+ # The map of channels, each key being the local-id for the channel.
44
+ attr_reader :channels # :nodoc:
45
+
46
+ # The map of listeners that the event loop knows about. See #listen_to.
47
+ attr_reader :listeners # :nodoc:
48
+
49
+ # The map of specialized handlers for opening specific channel types. See
50
+ # #on_open_channel.
51
+ attr_reader :channel_open_handlers # :nodoc:
52
+
53
+ # The list of callbacks for pending requests. See #send_global_request.
54
+ attr_reader :pending_requests # :nodoc:
55
+
56
+ class NilChannel
57
+ def initialize(session)
58
+ @session = session
59
+ end
6
60
 
7
- module Net; module SSH; module Connection
8
-
9
- # A session class representing the connection service running on top of
10
- # the SSH transport layer. It manages the creation of channels (see
11
- # #open_channel), and the dispatching of messages to the various channels.
12
- # It also encapsulates the SSH event loop (via #loop and #process),
13
- # and serves as a central point-of-reference for all SSH-related services (e.g.
14
- # port forwarding, SFTP, SCP, etc.).
15
- #
16
- # You will rarely (if ever) need to instantiate this class directly; rather,
17
- # you'll almost always use Net::SSH.start to initialize a new network
18
- # connection, authenticate a user, and return a new connection session,
19
- # all in one call.
20
- #
21
- # Net::SSH.start("localhost", "user") do |ssh|
22
- # # 'ssh' is an instance of Net::SSH::Connection::Session
23
- # ssh.exec! "/etc/init.d/some_process start"
24
- # end
25
- class Session
26
- include Constants, Loggable
27
-
28
- # Default IO.select timeout threshold
29
- DEFAULT_IO_SELECT_TIMEOUT = 300
30
-
31
- # The underlying transport layer abstraction (see Net::SSH::Transport::Session).
32
- attr_reader :transport
33
-
34
- # The map of options that were used to initialize this instance.
35
- attr_reader :options
36
-
37
- # The collection of custom properties for this instance. (See #[] and #[]=).
38
- attr_reader :properties
39
-
40
- # The map of channels, each key being the local-id for the channel.
41
- attr_reader :channels #:nodoc:
42
-
43
- # The map of listeners that the event loop knows about. See #listen_to.
44
- attr_reader :listeners #:nodoc:
45
-
46
- # The map of specialized handlers for opening specific channel types. See
47
- # #on_open_channel.
48
- attr_reader :channel_open_handlers #:nodoc:
49
-
50
- # The list of callbacks for pending requests. See #send_global_request.
51
- attr_reader :pending_requests #:nodoc:
52
-
53
- class NilChannel
54
- def initialize(session)
55
- @session = session
56
- end
57
-
58
- def method_missing(sym, *args)
59
- @session.lwarn { "ignoring request #{sym.inspect} for non-existent (closed?) channel; probably ssh server bug" }
60
- end
61
- end
62
-
63
- # Create a new connection service instance atop the given transport
64
- # layer. Initializes the listeners to be only the underlying socket object.
65
- def initialize(transport, options={})
66
- self.logger = transport.logger
67
-
68
- @transport = transport
69
- @options = options
61
+ def method_missing(sym, *args)
62
+ @session.lwarn { "ignoring request #{sym.inspect} for non-existent (closed?) channel; probably ssh server bug" }
63
+ end
64
+ end
70
65
 
71
- @channel_id_counter = -1
72
- @channels = Hash.new(NilChannel.new(self))
73
- @listeners = { transport.socket => nil }
74
- @pending_requests = []
75
- @channel_open_handlers = {}
76
- @on_global_request = {}
77
- @properties = (options[:properties] || {}).dup
66
+ # Create a new connection service instance atop the given transport
67
+ # layer. Initializes the listeners to be only the underlying socket object.
68
+ def initialize(transport, options = {})
69
+ self.logger = transport.logger
78
70
 
79
- @max_pkt_size = (options.has_key?(:max_pkt_size) ? options[:max_pkt_size] : 0x8000)
80
- @max_win_size = (options.has_key?(:max_win_size) ? options[:max_win_size] : 0x20000)
71
+ @transport = transport
72
+ @options = options
81
73
 
82
- @last_keepalive_sent_at = nil
83
- end
74
+ @channel_id_counter = -1
75
+ @channels = Hash.new(NilChannel.new(self))
76
+ @listeners = { transport.socket => nil }
77
+ @pending_requests = []
78
+ @channel_open_handlers = {}
79
+ @on_global_request = {}
80
+ @properties = (options[:properties] || {}).dup
84
81
 
85
- # Retrieves a custom property from this instance. This can be used to
86
- # store additional state in applications that must manage multiple
87
- # SSH connections.
88
- def [](key)
89
- @properties[key]
90
- end
82
+ @max_pkt_size = (options.key?(:max_pkt_size) ? options[:max_pkt_size] : 0x8000)
83
+ @max_win_size = (options.key?(:max_win_size) ? options[:max_win_size] : 0x20000)
91
84
 
92
- # Sets a custom property for this instance.
93
- def []=(key, value)
94
- @properties[key] = value
95
- end
85
+ @keepalive = Keepalive.new(self)
96
86
 
97
- # Returns the name of the host that was given to the transport layer to
98
- # connect to.
99
- def host
100
- transport.host
101
- end
87
+ @event_loop = options[:event_loop] || SingleSessionEventLoop.new
88
+ @event_loop.register(self)
89
+ end
102
90
 
103
- # Returns true if the underlying transport has been closed. Note that
104
- # this can be a little misleading, since if the remote server has
105
- # closed the connection, the local end will still think it is open
106
- # until the next operation on the socket. Nevertheless, this method can
107
- # be useful if you just want to know if _you_ have closed the connection.
108
- def closed?
109
- transport.closed?
110
- end
91
+ # Retrieves a custom property from this instance. This can be used to
92
+ # store additional state in applications that must manage multiple
93
+ # SSH connections.
94
+ def [](key)
95
+ @properties[key]
96
+ end
111
97
 
112
- # Closes the session gracefully, blocking until all channels have
113
- # successfully closed, and then closes the underlying transport layer
114
- # connection.
115
- def close
116
- info { "closing remaining channels (#{channels.length} open)" }
117
- channels.each { |id, channel| channel.close }
118
- loop(0.1) { channels.any? }
119
- transport.close
120
- end
98
+ # Sets a custom property for this instance.
99
+ def []=(key, value)
100
+ @properties[key] = value
101
+ end
121
102
 
122
- # Performs a "hard" shutdown of the connection. In general, this should
123
- # never be done, but it might be necessary (in a rescue clause, for instance,
124
- # when the connection needs to close but you don't know the status of the
125
- # underlying protocol's state).
126
- def shutdown!
127
- transport.shutdown!
128
- end
103
+ # Returns the name of the host that was given to the transport layer to
104
+ # connect to.
105
+ def host
106
+ transport.host
107
+ end
129
108
 
130
- # preserve a reference to Kernel#loop
131
- alias :loop_forever :loop
132
-
133
- # Returns +true+ if there are any channels currently active on this
134
- # session. By default, this will not include "invisible" channels
135
- # (such as those created by forwarding ports and such), but if you pass
136
- # a +true+ value for +include_invisible+, then those will be counted.
137
- #
138
- # This can be useful for determining whether the event loop should continue
139
- # to be run.
140
- #
141
- # ssh.loop { ssh.busy? }
142
- def busy?(include_invisible=false)
143
- if include_invisible
144
- channels.any?
145
- else
146
- channels.any? { |id, ch| !ch[:invisible] }
147
- end
148
- end
109
+ # Returns true if the underlying transport has been closed. Note that
110
+ # this can be a little misleading, since if the remote server has
111
+ # closed the connection, the local end will still think it is open
112
+ # until the next operation on the socket. Nevertheless, this method can
113
+ # be useful if you just want to know if _you_ have closed the connection.
114
+ def closed?
115
+ transport.closed?
116
+ end
149
117
 
150
- # The main event loop. Calls #process until #process returns false. If a
151
- # block is given, it is passed to #process, otherwise a default proc is
152
- # used that just returns true if there are any channels active (see #busy?).
153
- # The # +wait+ parameter is also passed through to #process (where it is
154
- # interpreted as the maximum number of seconds to wait for IO.select to return).
155
- #
156
- # # loop for as long as there are any channels active
157
- # ssh.loop
158
- #
159
- # # loop for as long as there are any channels active, but make sure
160
- # # the event loop runs at least once per 0.1 second
161
- # ssh.loop(0.1)
162
- #
163
- # # loop until ctrl-C is pressed
164
- # int_pressed = false
165
- # trap("INT") { int_pressed = true }
166
- # ssh.loop(0.1) { not int_pressed }
167
- def loop(wait=nil, &block)
168
- running = block || Proc.new { busy? }
169
- loop_forever { break unless process(wait, &running) }
170
- end
118
+ # Closes the session gracefully, blocking until all channels have
119
+ # successfully closed, and then closes the underlying transport layer
120
+ # connection.
121
+ def close
122
+ info { "closing remaining channels (#{channels.length} open)" }
123
+ channels.each { |id, channel| channel.close }
124
+ begin
125
+ loop(0.1) { channels.any? }
126
+ rescue Net::SSH::Disconnect
127
+ raise unless channels.empty?
128
+ end
129
+ transport.close
130
+ end
171
131
 
172
- # The core of the event loop. It processes a single iteration of the event
173
- # loop. If a block is given, it should return false when the processing
174
- # should abort, which causes #process to return false. Otherwise,
175
- # #process returns true. The session itself is yielded to the block as its
176
- # only argument.
177
- #
178
- # If +wait+ is nil (the default), this method will block until any of the
179
- # monitored IO objects are ready to be read from or written to. If you want
180
- # it to not block, you can pass 0, or you can pass any other numeric value
181
- # to indicate that it should block for no more than that many seconds.
182
- # Passing 0 is a good way to poll the connection, but if you do it too
183
- # frequently it can make your CPU quite busy!
184
- #
185
- # This will also cause all active channels to be processed once each (see
186
- # Net::SSH::Connection::Channel#on_process).
187
- #
188
- # # process multiple Net::SSH connections in parallel
189
- # connections = [
190
- # Net::SSH.start("host1", ...),
191
- # Net::SSH.start("host2", ...)
192
- # ]
193
- #
194
- # connections.each do |ssh|
195
- # ssh.exec "grep something /in/some/files"
196
- # end
197
- #
198
- # condition = Proc.new { |s| s.busy? }
199
- #
200
- # loop do
201
- # connections.delete_if { |ssh| !ssh.process(0.1, &condition) }
202
- # break if connections.empty?
203
- # end
204
- def process(wait=nil, &block)
205
- return false unless preprocess(&block)
206
-
207
- r = listeners.keys
208
- w = r.select { |w2| w2.respond_to?(:pending_write?) && w2.pending_write? }
209
- readers, writers, = Net::SSH::Compat.io_select(r, w, nil, io_select_wait(wait))
210
-
211
- postprocess(readers, writers)
212
- end
132
+ # Performs a "hard" shutdown of the connection. In general, this should
133
+ # never be done, but it might be necessary (in a rescue clause, for instance,
134
+ # when the connection needs to close but you don't know the status of the
135
+ # underlying protocol's state).
136
+ def shutdown!
137
+ transport.shutdown!
138
+ end
213
139
 
214
- # This is called internally as part of #process. It dispatches any
215
- # available incoming packets, and then runs Net::SSH::Connection::Channel#process
216
- # for any active channels. If a block is given, it is invoked at the
217
- # start of the method and again at the end, and if the block ever returns
218
- # false, this method returns false. Otherwise, it returns true.
219
- def preprocess
220
- return false if block_given? && !yield(self)
221
- dispatch_incoming_packets
222
- channels.each { |id, channel| channel.process unless channel.closing? }
223
- return false if block_given? && !yield(self)
224
- return true
225
- end
140
+ # preserve a reference to Kernel#loop
141
+ alias :loop_forever :loop
142
+
143
+ # Returns +true+ if there are any channels currently active on this
144
+ # session. By default, this will not include "invisible" channels
145
+ # (such as those created by forwarding ports and such), but if you pass
146
+ # a +true+ value for +include_invisible+, then those will be counted.
147
+ #
148
+ # This can be useful for determining whether the event loop should continue
149
+ # to be run.
150
+ #
151
+ # ssh.loop { ssh.busy? }
152
+ def busy?(include_invisible = false)
153
+ if include_invisible
154
+ channels.any?
155
+ else
156
+ channels.any? { |id, ch| !ch[:invisible] }
157
+ end
158
+ end
226
159
 
227
- # This is called internally as part of #process. It loops over the given
228
- # arrays of reader IO's and writer IO's, processing them as needed, and
229
- # then calls Net::SSH::Transport::Session#rekey_as_needed to allow the
230
- # transport layer to rekey. Then returns true.
231
- def postprocess(readers, writers)
232
- Array(readers).each do |reader|
233
- if listeners[reader]
234
- listeners[reader].call(reader)
235
- else
236
- if reader.fill.zero?
237
- reader.close
238
- stop_listening_to(reader)
160
+ # The main event loop. Calls #process until #process returns false. If a
161
+ # block is given, it is passed to #process, otherwise a default proc is
162
+ # used that just returns true if there are any channels active (see #busy?).
163
+ # The # +wait+ parameter is also passed through to #process (where it is
164
+ # interpreted as the maximum number of seconds to wait for IO.select to return).
165
+ #
166
+ # # loop for as long as there are any channels active
167
+ # ssh.loop
168
+ #
169
+ # # loop for as long as there are any channels active, but make sure
170
+ # # the event loop runs at least once per 0.1 second
171
+ # ssh.loop(0.1)
172
+ #
173
+ # # loop until ctrl-C is pressed
174
+ # int_pressed = false
175
+ # trap("INT") { int_pressed = true }
176
+ # ssh.loop(0.1) { not int_pressed }
177
+ def loop(wait = nil, &block)
178
+ running = block || Proc.new { busy? }
179
+ loop_forever { break unless process(wait, &running) }
180
+ begin
181
+ process(0)
182
+ rescue IOError => e
183
+ if e.message =~ /closed/
184
+ debug { "stream was closed after loop => shallowing exception so it will be re-raised in next loop" }
185
+ else
186
+ raise
187
+ end
239
188
  end
240
189
  end
241
- end
242
190
 
243
- Array(writers).each do |writer|
244
- writer.send_pending
245
- end
191
+ # The core of the event loop. It processes a single iteration of the event
192
+ # loop. If a block is given, it should return false when the processing
193
+ # should abort, which causes #process to return false. Otherwise,
194
+ # #process returns true. The session itself is yielded to the block as its
195
+ # only argument.
196
+ #
197
+ # If +wait+ is nil (the default), this method will block until any of the
198
+ # monitored IO objects are ready to be read from or written to. If you want
199
+ # it to not block, you can pass 0, or you can pass any other numeric value
200
+ # to indicate that it should block for no more than that many seconds.
201
+ # Passing 0 is a good way to poll the connection, but if you do it too
202
+ # frequently it can make your CPU quite busy!
203
+ #
204
+ # This will also cause all active channels to be processed once each (see
205
+ # Net::SSH::Connection::Channel#on_process).
206
+ #
207
+ # TODO revise example
208
+ #
209
+ # # process multiple Net::SSH connections in parallel
210
+ # connections = [
211
+ # Net::SSH.start("host1", ...),
212
+ # Net::SSH.start("host2", ...)
213
+ # ]
214
+ #
215
+ # connections.each do |ssh|
216
+ # ssh.exec "grep something /in/some/files"
217
+ # end
218
+ #
219
+ # condition = Proc.new { |s| s.busy? }
220
+ #
221
+ # loop do
222
+ # connections.delete_if { |ssh| !ssh.process(0.1, &condition) }
223
+ # break if connections.empty?
224
+ # end
225
+ def process(wait = nil, &block)
226
+ @event_loop.process(wait, &block)
227
+ rescue StandardError
228
+ force_channel_cleanup_on_close if closed?
229
+ raise
230
+ end
246
231
 
247
- send_keepalive_as_needed(readers, writers)
248
- transport.rekey_as_needed
232
+ # This is called internally as part of #process. It dispatches any
233
+ # available incoming packets, and then runs Net::SSH::Connection::Channel#process
234
+ # for any active channels. If a block is given, it is invoked at the
235
+ # start of the method and again at the end, and if the block ever returns
236
+ # false, this method returns false. Otherwise, it returns true.
237
+ def preprocess(&block)
238
+ return false if block_given? && !yield(self)
249
239
 
250
- return true
251
- end
240
+ ev_preprocess(&block)
241
+ return false if block_given? && !yield(self)
252
242
 
253
- # Send a global request of the given type. The +extra+ parameters must
254
- # be even in number, and conform to the same format as described for
255
- # Net::SSH::Buffer.from. If a callback is not specified, the request will
256
- # not require a response from the server, otherwise the server is required
257
- # to respond and indicate whether the request was successful or not. This
258
- # success or failure is indicated by the callback being invoked, with the
259
- # first parameter being true or false (success, or failure), and the second
260
- # being the packet itself.
261
- #
262
- # Generally, Net::SSH will manage global requests that need to be sent
263
- # (e.g. port forward requests and such are handled in the Net::SSH::Service::Forward
264
- # class, for instance). However, there may be times when you need to
265
- # send a global request that isn't explicitly handled by Net::SSH, and so
266
- # this method is available to you.
267
- #
268
- # ssh.send_global_request("keep-alive@openssh.com")
269
- def send_global_request(type, *extra, &callback)
270
- info { "sending global request #{type}" }
271
- msg = Buffer.from(:byte, GLOBAL_REQUEST, :string, type.to_s, :bool, !callback.nil?, *extra)
272
- send_message(msg)
273
- pending_requests << callback if callback
274
- self
275
- end
243
+ return true
244
+ end
276
245
 
277
- # Requests that a new channel be opened. By default, the channel will be
278
- # of type "session", but if you know what you're doing you can select any
279
- # of the channel types supported by the SSH protocol. The +extra+ parameters
280
- # must be even in number and conform to the same format as described for
281
- # Net::SSH::Buffer.from. If a callback is given, it will be invoked when
282
- # the server confirms that the channel opened successfully. The sole parameter
283
- # for the callback is the channel object itself.
284
- #
285
- # In general, you'll use #open_channel without any arguments; the only
286
- # time you'd want to set the channel type or pass additional initialization
287
- # data is if you were implementing an SSH extension.
288
- #
289
- # channel = ssh.open_channel do |ch|
290
- # ch.exec "grep something /some/files" do |ch, success|
291
- # ...
292
- # end
293
- # end
294
- #
295
- # channel.wait
296
- def open_channel(type="session", *extra, &on_confirm)
297
- local_id = get_next_channel_id
298
-
299
- channel = Channel.new(self, type, local_id, @max_pkt_size, @max_win_size, &on_confirm)
300
- msg = Buffer.from(:byte, CHANNEL_OPEN, :string, type, :long, local_id,
301
- :long, channel.local_maximum_window_size,
302
- :long, channel.local_maximum_packet_size, *extra)
303
- send_message(msg)
304
-
305
- channels[local_id] = channel
306
- end
246
+ # Called by event loop to process available data before going to
247
+ # event multiplexing
248
+ def ev_preprocess(&block)
249
+ dispatch_incoming_packets(raise_disconnect_errors: false)
250
+ each_channel { |id, channel| channel.process unless channel.local_closed? }
251
+ end
252
+
253
+ # Returns the file descriptors the event loop should wait for read/write events,
254
+ # we also return the max wait
255
+ def ev_do_calculate_rw_wait(wait)
256
+ r = listeners.keys
257
+ w = r.select { |w2| w2.respond_to?(:pending_write?) && w2.pending_write? }
258
+ [r, w, io_select_wait(wait)]
259
+ end
260
+
261
+ # This is called internally as part of #process.
262
+ def postprocess(readers, writers)
263
+ ev_do_handle_events(readers, writers)
264
+ end
307
265
 
308
- # A convenience method for executing a command and interacting with it. If
309
- # no block is given, all output is printed via $stdout and $stderr. Otherwise,
310
- # the block is called for each data and extended data packet, with three
311
- # arguments: the channel object, a symbol indicating the data type
312
- # (:stdout or :stderr), and the data (as a string).
313
- #
314
- # Note that this method returns immediately, and requires an event loop
315
- # (see Session#loop) in order for the command to actually execute.
316
- #
317
- # This is effectively identical to calling #open_channel, and then
318
- # Net::SSH::Connection::Channel#exec, and then setting up the channel
319
- # callbacks. However, for most uses, this will be sufficient.
320
- #
321
- # ssh.exec "grep something /some/files" do |ch, stream, data|
322
- # if stream == :stderr
323
- # puts "ERROR: #{data}"
324
- # else
325
- # puts data
326
- # end
327
- # end
328
- def exec(command, &block)
329
- open_channel do |channel|
330
- channel.exec(command) do |ch, success|
331
- raise "could not execute command: #{command.inspect}" unless success
332
-
333
- channel.on_data do |ch2, data|
334
- if block
335
- block.call(ch2, :stdout, data)
266
+ # It loops over the given arrays of reader IO's and writer IO's,
267
+ # processing them as needed, and
268
+ # then calls Net::SSH::Transport::Session#rekey_as_needed to allow the
269
+ # transport layer to rekey. Then returns true.
270
+ def ev_do_handle_events(readers, writers)
271
+ Array(readers).each do |reader|
272
+ if listeners[reader]
273
+ listeners[reader].call(reader)
336
274
  else
337
- $stdout.print(data)
275
+ if reader.fill.zero?
276
+ reader.close
277
+ stop_listening_to(reader)
278
+ end
338
279
  end
339
280
  end
340
281
 
341
- channel.on_extended_data do |ch2, type, data|
342
- if block
343
- block.call(ch2, :stderr, data)
344
- else
345
- $stderr.print(data)
282
+ Array(writers).each do |writer|
283
+ writer.send_pending
284
+ end
285
+ end
286
+
287
+ # calls Net::SSH::Transport::Session#rekey_as_needed to allow the
288
+ # transport layer to rekey
289
+ def ev_do_postprocess(was_events)
290
+ @keepalive.send_as_needed(was_events)
291
+ transport.rekey_as_needed
292
+ true
293
+ end
294
+
295
+ # Send a global request of the given type. The +extra+ parameters must
296
+ # be even in number, and conform to the same format as described for
297
+ # Net::SSH::Buffer.from. If a callback is not specified, the request will
298
+ # not require a response from the server, otherwise the server is required
299
+ # to respond and indicate whether the request was successful or not. This
300
+ # success or failure is indicated by the callback being invoked, with the
301
+ # first parameter being true or false (success, or failure), and the second
302
+ # being the packet itself.
303
+ #
304
+ # Generally, Net::SSH will manage global requests that need to be sent
305
+ # (e.g. port forward requests and such are handled in the Net::SSH::Service::Forward
306
+ # class, for instance). However, there may be times when you need to
307
+ # send a global request that isn't explicitly handled by Net::SSH, and so
308
+ # this method is available to you.
309
+ #
310
+ # ssh.send_global_request("keep-alive@openssh.com")
311
+ def send_global_request(type, *extra, &callback)
312
+ info { "sending global request #{type}" }
313
+ msg = Buffer.from(:byte, GLOBAL_REQUEST, :string, type.to_s, :bool, !callback.nil?, *extra)
314
+ send_message(msg)
315
+ pending_requests << callback if callback
316
+ self
317
+ end
318
+
319
+ # Requests that a new channel be opened. By default, the channel will be
320
+ # of type "session", but if you know what you're doing you can select any
321
+ # of the channel types supported by the SSH protocol. The +extra+ parameters
322
+ # must be even in number and conform to the same format as described for
323
+ # Net::SSH::Buffer.from. If a callback is given, it will be invoked when
324
+ # the server confirms that the channel opened successfully. The sole parameter
325
+ # for the callback is the channel object itself.
326
+ #
327
+ # In general, you'll use #open_channel without any arguments; the only
328
+ # time you'd want to set the channel type or pass additional initialization
329
+ # data is if you were implementing an SSH extension.
330
+ #
331
+ # channel = ssh.open_channel do |ch|
332
+ # ch.exec "grep something /some/files" do |ch, success|
333
+ # ...
334
+ # end
335
+ # end
336
+ #
337
+ # channel.wait
338
+ def open_channel(type = "session", *extra, &on_confirm)
339
+ local_id = get_next_channel_id
340
+
341
+ channel = Channel.new(self, type, local_id, @max_pkt_size, @max_win_size, &on_confirm)
342
+ msg = Buffer.from(:byte, CHANNEL_OPEN, :string, type, :long, local_id,
343
+ :long, channel.local_maximum_window_size,
344
+ :long, channel.local_maximum_packet_size, *extra)
345
+ send_message(msg)
346
+
347
+ channels[local_id] = channel
348
+ end
349
+
350
+ class StringWithExitstatus < String
351
+ def initialize(str, exitstatus)
352
+ super(str)
353
+ @exitstatus = exitstatus
354
+ end
355
+
356
+ attr_reader :exitstatus
357
+ end
358
+
359
+ # A convenience method for executing a command and interacting with it. If
360
+ # no block is given, all output is printed via $stdout and $stderr. Otherwise,
361
+ # the block is called for each data and extended data packet, with three
362
+ # arguments: the channel object, a symbol indicating the data type
363
+ # (:stdout or :stderr), and the data (as a string).
364
+ #
365
+ # Note that this method returns immediately, and requires an event loop
366
+ # (see Session#loop) in order for the command to actually execute.
367
+ #
368
+ # This is effectively identical to calling #open_channel, and then
369
+ # Net::SSH::Connection::Channel#exec, and then setting up the channel
370
+ # callbacks. However, for most uses, this will be sufficient.
371
+ #
372
+ # ssh.exec "grep something /some/files" do |ch, stream, data|
373
+ # if stream == :stderr
374
+ # puts "ERROR: #{data}"
375
+ # else
376
+ # puts data
377
+ # end
378
+ # end
379
+ def exec(command, status: nil, &block)
380
+ open_channel do |channel|
381
+ channel.exec(command) do |ch, success|
382
+ raise "could not execute command: #{command.inspect}" unless success
383
+
384
+ if status
385
+ channel.on_request("exit-status") do |ch2, data|
386
+ status[:exit_code] = data.read_long
387
+ end
388
+
389
+ channel.on_request("exit-signal") do |ch2, data|
390
+ status[:exit_signal] = data.read_long
391
+ end
392
+ end
393
+
394
+ channel.on_data do |ch2, data|
395
+ if block
396
+ block.call(ch2, :stdout, data)
397
+ else
398
+ $stdout.print(data)
399
+ end
400
+ end
401
+
402
+ channel.on_extended_data do |ch2, type, data|
403
+ if block
404
+ block.call(ch2, :stderr, data)
405
+ else
406
+ $stderr.print(data)
407
+ end
408
+ end
346
409
  end
347
410
  end
348
411
  end
349
- end
350
- end
351
412
 
352
- # Same as #exec, except this will block until the command finishes. Also,
353
- # if a block is not given, this will return all output (stdout and stderr)
354
- # as a single string.
355
- #
356
- # matches = ssh.exec!("grep something /some/files")
357
- def exec!(command, &block)
358
- block ||= Proc.new do |ch, type, data|
359
- ch[:result] ||= ""
360
- ch[:result] << data
361
- end
413
+ # Same as #exec, except this will block until the command finishes. Also,
414
+ # if no block is given, this will return all output (stdout and stderr)
415
+ # as a single string.
416
+ #
417
+ # matches = ssh.exec!("grep something /some/files")
418
+ #
419
+ # the returned string has an exitstatus method to query its exit status
420
+ def exec!(command, status: nil, &block)
421
+ block_or_concat = block || Proc.new do |ch, type, data|
422
+ ch[:result] ||= String.new
423
+ ch[:result] << data
424
+ end
362
425
 
363
- channel = exec(command, &block)
364
- channel.wait
426
+ status ||= {}
427
+ channel = exec(command, status: status, &block_or_concat)
428
+ channel.wait
365
429
 
366
- return channel[:result]
367
- end
430
+ channel[:result] ||= String.new unless block
431
+ channel[:result] &&= channel[:result].force_encoding("UTF-8") unless block
368
432
 
369
- # Enqueues a message to be sent to the server as soon as the socket is
370
- # available for writing. Most programs will never need to call this, but
371
- # if you are implementing an extension to the SSH protocol, or if you
372
- # need to send a packet that Net::SSH does not directly support, you can
373
- # use this to send it.
374
- #
375
- # ssh.send_message(Buffer.from(:byte, REQUEST_SUCCESS).to_s)
376
- def send_message(message)
377
- transport.enqueue_message(message)
378
- end
433
+ StringWithExitstatus.new(channel[:result], status[:exit_code]) if channel[:result]
434
+ end
379
435
 
380
- # Adds an IO object for the event loop to listen to. If a callback
381
- # is given, it will be invoked when the io is ready to be read, otherwise,
382
- # the io will merely have its #fill method invoked.
383
- #
384
- # Any +io+ value passed to this method _must_ have mixed into it the
385
- # Net::SSH::BufferedIo functionality, typically by calling #extend on the
386
- # object.
387
- #
388
- # The following example executes a process on the remote server, opens
389
- # a socket to somewhere, and then pipes data from that socket to the
390
- # remote process' stdin stream:
391
- #
392
- # channel = ssh.open_channel do |ch|
393
- # ch.exec "/some/process/that/wants/input" do |ch, success|
394
- # abort "can't execute!" unless success
395
- #
396
- # io = TCPSocket.new(somewhere, port)
397
- # io.extend(Net::SSH::BufferedIo)
398
- # ssh.listen_to(io)
399
- #
400
- # ch.on_process do
401
- # if io.available > 0
402
- # ch.send_data(io.read_available)
403
- # end
404
- # end
405
- #
406
- # ch.on_close do
407
- # ssh.stop_listening_to(io)
408
- # io.close
409
- # end
410
- # end
411
- # end
412
- #
413
- # channel.wait
414
- def listen_to(io, &callback)
415
- listeners[io] = callback
416
- end
436
+ # Enqueues a message to be sent to the server as soon as the socket is
437
+ # available for writing. Most programs will never need to call this, but
438
+ # if you are implementing an extension to the SSH protocol, or if you
439
+ # need to send a packet that Net::SSH does not directly support, you can
440
+ # use this to send it.
441
+ #
442
+ # ssh.send_message(Buffer.from(:byte, REQUEST_SUCCESS).to_s)
443
+ def send_message(message)
444
+ transport.enqueue_message(message)
445
+ end
417
446
 
418
- # Removes the given io object from the listeners collection, so that the
419
- # event loop will no longer monitor it.
420
- def stop_listening_to(io)
421
- listeners.delete(io)
422
- end
447
+ # Adds an IO object for the event loop to listen to. If a callback
448
+ # is given, it will be invoked when the io is ready to be read, otherwise,
449
+ # the io will merely have its #fill method invoked.
450
+ #
451
+ # Any +io+ value passed to this method _must_ have mixed into it the
452
+ # Net::SSH::BufferedIo functionality, typically by calling #extend on the
453
+ # object.
454
+ #
455
+ # The following example executes a process on the remote server, opens
456
+ # a socket to somewhere, and then pipes data from that socket to the
457
+ # remote process' stdin stream:
458
+ #
459
+ # channel = ssh.open_channel do |ch|
460
+ # ch.exec "/some/process/that/wants/input" do |ch, success|
461
+ # abort "can't execute!" unless success
462
+ #
463
+ # io = TCPSocket.new(somewhere, port)
464
+ # io.extend(Net::SSH::BufferedIo)
465
+ # ssh.listen_to(io)
466
+ #
467
+ # ch.on_process do
468
+ # if io.available > 0
469
+ # ch.send_data(io.read_available)
470
+ # end
471
+ # end
472
+ #
473
+ # ch.on_close do
474
+ # ssh.stop_listening_to(io)
475
+ # io.close
476
+ # end
477
+ # end
478
+ # end
479
+ #
480
+ # channel.wait
481
+ def listen_to(io, &callback)
482
+ listeners[io] = callback
483
+ end
423
484
 
424
- # Returns a reference to the Net::SSH::Service::Forward service, which can
425
- # be used for forwarding ports over SSH.
426
- def forward
427
- @forward ||= Service::Forward.new(self)
428
- end
485
+ # Removes the given io object from the listeners collection, so that the
486
+ # event loop will no longer monitor it.
487
+ def stop_listening_to(io)
488
+ listeners.delete(io)
489
+ end
429
490
 
430
- # Registers a handler to be invoked when the server wants to open a
431
- # channel on the client. The callback receives the connection object,
432
- # the new channel object, and the packet itself as arguments, and should
433
- # raise ChannelOpenFailed if it is unable to open the channel for some
434
- # reason. Otherwise, the channel will be opened and a confirmation message
435
- # sent to the server.
436
- #
437
- # This is used by the Net::SSH::Service::Forward service to open a channel
438
- # when a remote forwarded port receives a connection. However, you are
439
- # welcome to register handlers for other channel types, as needed.
440
- def on_open_channel(type, &block)
441
- channel_open_handlers[type] = block
442
- end
491
+ # Returns a reference to the Net::SSH::Service::Forward service, which can
492
+ # be used for forwarding ports over SSH.
493
+ def forward
494
+ @forward ||= Service::Forward.new(self)
495
+ end
443
496
 
444
- # Registers a handler to be invoked when the server sends a global request
445
- # of the given type. The callback receives the request data as the first
446
- # parameter, and true/false as the second (indicating whether a response
447
- # is required). If the callback sends the response, it should return
448
- # :sent. Otherwise, if it returns true, REQUEST_SUCCESS will be sent, and
449
- # if it returns false, REQUEST_FAILURE will be sent.
450
- def on_global_request(type, &block)
451
- old, @on_global_request[type] = @on_global_request[type], block
452
- old
453
- end
497
+ # Registers a handler to be invoked when the server wants to open a
498
+ # channel on the client. The callback receives the connection object,
499
+ # the new channel object, and the packet itself as arguments, and should
500
+ # raise ChannelOpenFailed if it is unable to open the channel for some
501
+ # reason. Otherwise, the channel will be opened and a confirmation message
502
+ # sent to the server.
503
+ #
504
+ # This is used by the Net::SSH::Service::Forward service to open a channel
505
+ # when a remote forwarded port receives a connection. However, you are
506
+ # welcome to register handlers for other channel types, as needed.
507
+ def on_open_channel(type, &block)
508
+ channel_open_handlers[type] = block
509
+ end
454
510
 
455
- private
511
+ # Registers a handler to be invoked when the server sends a global request
512
+ # of the given type. The callback receives the request data as the first
513
+ # parameter, and true/false as the second (indicating whether a response
514
+ # is required). If the callback sends the response, it should return
515
+ # :sent. Otherwise, if it returns true, REQUEST_SUCCESS will be sent, and
516
+ # if it returns false, REQUEST_FAILURE will be sent.
517
+ def on_global_request(type, &block)
518
+ old, @on_global_request[type] = @on_global_request[type], block
519
+ old
520
+ end
456
521
 
457
- # Read all pending packets from the connection and dispatch them as
458
- # appropriate. Returns as soon as there are no more pending packets.
459
- def dispatch_incoming_packets
460
- while packet = transport.poll_message
461
- unless MAP.key?(packet.type)
462
- raise Net::SSH::Exception, "unexpected response #{packet.type} (#{packet.inspect})"
522
+ def cleanup_channel(channel)
523
+ if channel.local_closed? and channel.remote_closed?
524
+ info { "#{host} delete channel #{channel.local_id} which closed locally and remotely" }
525
+ channels.delete(channel.local_id)
463
526
  end
464
-
465
- send(MAP[packet.type], packet)
466
527
  end
467
- end
468
528
 
469
- # Returns the next available channel id to be assigned, and increments
470
- # the counter.
471
- def get_next_channel_id
472
- @channel_id_counter += 1
473
- end
529
+ # If the #preprocess and #postprocess callbacks for this session need to run
530
+ # periodically, this method returns the maximum number of seconds which may
531
+ # pass between callbacks.
532
+ def max_select_wait_time
533
+ @keepalive.interval if @keepalive.enabled?
534
+ end
474
535
 
475
- # Invoked when a global request is received. The registered global
476
- # request callback will be invoked, if one exists, and the necessary
477
- # reply returned.
478
- def global_request(packet)
479
- info { "global request received: #{packet[:request_type]} #{packet[:want_reply]}" }
480
- callback = @on_global_request[packet[:request_type]]
481
- result = callback ? callback.call(packet[:request_data], packet[:want_reply]) : false
536
+ private
482
537
 
483
- if result != :sent && result != true && result != false
484
- raise "expected global request handler for `#{packet[:request_type]}' to return true, false, or :sent, but got #{result.inspect}"
538
+ # iterate channels with the posibility of callbacks opening new channels during the iteration
539
+ def each_channel(&block)
540
+ channels.dup.each(&block)
485
541
  end
486
542
 
487
- if packet[:want_reply] && result != :sent
488
- msg = Buffer.from(:byte, result ? REQUEST_SUCCESS : REQUEST_FAILURE)
489
- send_message(msg)
543
+ # Read all pending packets from the connection and dispatch them as
544
+ # appropriate. Returns as soon as there are no more pending packets.
545
+ def dispatch_incoming_packets(raise_disconnect_errors: true)
546
+ while packet = transport.poll_message
547
+ raise Net::SSH::Exception, "unexpected response #{packet.type} (#{packet.inspect})" unless MAP.key?(packet.type)
548
+
549
+ send(MAP[packet.type], packet)
550
+ end
551
+ rescue StandardError
552
+ force_channel_cleanup_on_close if closed?
553
+ raise if raise_disconnect_errors || !$!.is_a?(Net::SSH::Disconnect)
490
554
  end
491
- end
492
555
 
493
- # Invokes the next pending request callback with +true+.
494
- def request_success(packet)
495
- info { "global request success" }
496
- callback = pending_requests.shift
497
- callback.call(true, packet) if callback
498
- end
556
+ # Returns the next available channel id to be assigned, and increments
557
+ # the counter.
558
+ def get_next_channel_id
559
+ @channel_id_counter += 1
560
+ end
499
561
 
500
- # Invokes the next pending request callback with +false+.
501
- def request_failure(packet)
502
- info { "global request failure" }
503
- callback = pending_requests.shift
504
- callback.call(false, packet) if callback
505
- end
562
+ def force_channel_cleanup_on_close
563
+ channels.each do |id, channel|
564
+ channel_closed(channel)
565
+ end
566
+ end
506
567
 
507
- # Called when the server wants to open a channel. If no registered
508
- # channel handler exists for the given channel type, CHANNEL_OPEN_FAILURE
509
- # is returned, otherwise the callback is invoked and everything proceeds
510
- # accordingly.
511
- def channel_open(packet)
512
- info { "channel open #{packet[:channel_type]}" }
568
+ def channel_closed(channel)
569
+ channel.remote_closed!
570
+ channel.close
513
571
 
514
- local_id = get_next_channel_id
572
+ cleanup_channel(channel)
573
+ channel.do_close
574
+ end
515
575
 
516
- channel = Channel.new(self, packet[:channel_type], local_id, @max_pkt_size, @max_win_size)
517
- channel.do_open_confirmation(packet[:remote_id], packet[:window_size], packet[:packet_size])
576
+ # Invoked when a global request is received. The registered global
577
+ # request callback will be invoked, if one exists, and the necessary
578
+ # reply returned.
579
+ def global_request(packet)
580
+ info { "global request received: #{packet[:request_type]} #{packet[:want_reply]}" }
581
+ callback = @on_global_request[packet[:request_type]]
582
+ result = callback ? callback.call(packet[:request_data], packet[:want_reply]) : false
518
583
 
519
- callback = channel_open_handlers[packet[:channel_type]]
584
+ if result != :sent && result != true && result != false
585
+ raise "expected global request handler for `#{packet[:request_type]}' to return true, false, or :sent, but got #{result.inspect}"
586
+ end
520
587
 
521
- if callback
522
- begin
523
- callback[self, channel, packet]
524
- rescue ChannelOpenFailed => err
525
- failure = [err.code, err.reason]
526
- else
527
- channels[local_id] = channel
528
- msg = Buffer.from(:byte, CHANNEL_OPEN_CONFIRMATION, :long, channel.remote_id, :long, channel.local_id, :long, channel.local_maximum_window_size, :long, channel.local_maximum_packet_size)
588
+ if packet[:want_reply] && result != :sent
589
+ msg = Buffer.from(:byte, result ? REQUEST_SUCCESS : REQUEST_FAILURE)
590
+ send_message(msg)
529
591
  end
530
- else
531
- failure = [3, "unknown channel type #{channel.type}"]
532
592
  end
533
593
 
534
- if failure
535
- error { failure.inspect }
536
- msg = Buffer.from(:byte, CHANNEL_OPEN_FAILURE, :long, channel.remote_id, :long, failure[0], :string, failure[1], :string, "")
594
+ # Invokes the next pending request callback with +true+.
595
+ def request_success(packet)
596
+ info { "global request success" }
597
+ callback = pending_requests.shift
598
+ callback.call(true, packet) if callback
537
599
  end
538
600
 
539
- send_message(msg)
540
- end
601
+ # Invokes the next pending request callback with +false+.
602
+ def request_failure(packet)
603
+ info { "global request failure" }
604
+ callback = pending_requests.shift
605
+ callback.call(false, packet) if callback
606
+ end
541
607
 
542
- def channel_open_confirmation(packet)
543
- info { "channel_open_confirmation: #{packet[:local_id]} #{packet[:remote_id]} #{packet[:window_size]} #{packet[:packet_size]}" }
544
- channel = channels[packet[:local_id]]
545
- channel.do_open_confirmation(packet[:remote_id], packet[:window_size], packet[:packet_size])
546
- end
608
+ # Called when the server wants to open a channel. If no registered
609
+ # channel handler exists for the given channel type, CHANNEL_OPEN_FAILURE
610
+ # is returned, otherwise the callback is invoked and everything proceeds
611
+ # accordingly.
612
+ def channel_open(packet)
613
+ info { "channel open #{packet[:channel_type]}" }
547
614
 
548
- def channel_open_failure(packet)
549
- error { "channel_open_failed: #{packet[:local_id]} #{packet[:reason_code]} #{packet[:description]}" }
550
- channel = channels.delete(packet[:local_id])
551
- channel.do_open_failed(packet[:reason_code], packet[:description])
552
- end
615
+ local_id = get_next_channel_id
553
616
 
554
- def channel_window_adjust(packet)
555
- info { "channel_window_adjust: #{packet[:local_id]} +#{packet[:extra_bytes]}" }
556
- channels[packet[:local_id]].do_window_adjust(packet[:extra_bytes])
557
- end
617
+ channel = Channel.new(self, packet[:channel_type], local_id, @max_pkt_size, @max_win_size)
618
+ channel.do_open_confirmation(packet[:remote_id], packet[:window_size], packet[:packet_size])
558
619
 
559
- def channel_request(packet)
560
- info { "channel_request: #{packet[:local_id]} #{packet[:request]} #{packet[:want_reply]}" }
561
- channels[packet[:local_id]].do_request(packet[:request], packet[:want_reply], packet[:request_data])
562
- end
620
+ callback = channel_open_handlers[packet[:channel_type]]
563
621
 
564
- def channel_data(packet)
565
- info { "channel_data: #{packet[:local_id]} #{packet[:data].length}b" }
566
- channels[packet[:local_id]].do_data(packet[:data])
567
- end
622
+ if callback
623
+ begin
624
+ callback[self, channel, packet]
625
+ rescue ChannelOpenFailed => err
626
+ failure = [err.code, err.reason]
627
+ else
628
+ channels[local_id] = channel
629
+ msg = Buffer.from(:byte, CHANNEL_OPEN_CONFIRMATION, :long, channel.remote_id, :long,
630
+ channel.local_id, :long, channel.local_maximum_window_size, :long,
631
+ channel.local_maximum_packet_size)
632
+ end
633
+ else
634
+ failure = [3, "unknown channel type #{channel.type}"]
635
+ end
568
636
 
569
- def channel_extended_data(packet)
570
- info { "channel_extended_data: #{packet[:local_id]} #{packet[:data_type]} #{packet[:data].length}b" }
571
- channels[packet[:local_id]].do_extended_data(packet[:data_type], packet[:data])
572
- end
637
+ if failure
638
+ error { failure.inspect }
639
+ msg = Buffer.from(:byte, CHANNEL_OPEN_FAILURE, :long, channel.remote_id, :long, failure[0], :string, failure[1], :string, "")
640
+ end
573
641
 
574
- def channel_eof(packet)
575
- info { "channel_eof: #{packet[:local_id]}" }
576
- channels[packet[:local_id]].do_eof
577
- end
642
+ send_message(msg)
643
+ end
578
644
 
579
- def channel_close(packet)
580
- info { "channel_close: #{packet[:local_id]}" }
645
+ def channel_open_confirmation(packet)
646
+ info { "channel_open_confirmation: #{packet[:local_id]} #{packet[:remote_id]} #{packet[:window_size]} #{packet[:packet_size]}" }
647
+ channel = channels[packet[:local_id]]
648
+ channel.do_open_confirmation(packet[:remote_id], packet[:window_size], packet[:packet_size])
649
+ end
581
650
 
582
- channel = channels[packet[:local_id]]
583
- channel.close
651
+ def channel_open_failure(packet)
652
+ error { "channel_open_failed: #{packet[:local_id]} #{packet[:reason_code]} #{packet[:description]}" }
653
+ channel = channels.delete(packet[:local_id])
654
+ channel.do_open_failed(packet[:reason_code], packet[:description])
655
+ end
584
656
 
585
- channels.delete(packet[:local_id])
586
- channel.do_close
587
- end
657
+ def channel_window_adjust(packet)
658
+ info { "channel_window_adjust: #{packet[:local_id]} +#{packet[:extra_bytes]}" }
659
+ channels[packet[:local_id]].do_window_adjust(packet[:extra_bytes])
660
+ end
588
661
 
589
- def channel_success(packet)
590
- info { "channel_success: #{packet[:local_id]}" }
591
- channels[packet[:local_id]].do_success
592
- end
662
+ def channel_request(packet)
663
+ info { "channel_request: #{packet[:local_id]} #{packet[:request]} #{packet[:want_reply]}" }
664
+ channels[packet[:local_id]].do_request(packet[:request], packet[:want_reply], packet[:request_data])
665
+ end
593
666
 
594
- def channel_failure(packet)
595
- info { "channel_failure: #{packet[:local_id]}" }
596
- channels[packet[:local_id]].do_failure
597
- end
667
+ def channel_data(packet)
668
+ info { "channel_data: #{packet[:local_id]} #{packet[:data].length}b" }
669
+ channels[packet[:local_id]].do_data(packet[:data])
670
+ end
598
671
 
599
- def io_select_wait(wait)
600
- return wait if wait
601
- return wait unless options[:keepalive]
602
- keepalive_interval
603
- end
672
+ def channel_extended_data(packet)
673
+ info { "channel_extended_data: #{packet[:local_id]} #{packet[:data_type]} #{packet[:data].length}b" }
674
+ channels[packet[:local_id]].do_extended_data(packet[:data_type], packet[:data])
675
+ end
604
676
 
605
- def keepalive_interval
606
- options[:keepalive_interval] || DEFAULT_IO_SELECT_TIMEOUT
607
- end
677
+ def channel_eof(packet)
678
+ info { "channel_eof: #{packet[:local_id]}" }
679
+ channels[packet[:local_id]].do_eof
680
+ end
608
681
 
609
- def should_send_keepalive?
610
- return false unless options[:keepalive]
611
- return true unless @last_keepalive_sent_at
612
- Time.now - @last_keepalive_sent_at >= keepalive_interval
613
- end
682
+ def channel_close(packet)
683
+ info { "channel_close: #{packet[:local_id]}" }
614
684
 
615
- def send_keepalive_as_needed(readers, writers)
616
- return unless readers.nil? && writers.nil?
617
- return unless should_send_keepalive?
618
- info { "sending keepalive" }
619
- msg = Net::SSH::Buffer.from(:byte, Packet::IGNORE, :string, "keepalive")
620
- send_message(msg)
621
- @last_keepalive_sent_at = Time.now
622
- end
685
+ channel = channels[packet[:local_id]]
686
+ channel_closed(channel)
687
+ end
688
+
689
+ def channel_success(packet)
690
+ info { "channel_success: #{packet[:local_id]}" }
691
+ channels[packet[:local_id]].do_success
692
+ end
623
693
 
624
- MAP = Constants.constants.inject({}) do |memo, name|
625
- value = const_get(name)
626
- next unless Integer === value
627
- memo[value] = name.downcase.to_sym
628
- memo
694
+ def channel_failure(packet)
695
+ info { "channel_failure: #{packet[:local_id]}" }
696
+ channels[packet[:local_id]].do_failure
697
+ end
698
+
699
+ def io_select_wait(wait)
700
+ [wait, max_select_wait_time].compact.min
701
+ end
702
+
703
+ MAP = Constants.constants.each_with_object({}) do |name, memo|
704
+ value = const_get(name)
705
+ next unless Integer === value
706
+
707
+ memo[value] = name.downcase.to_sym
708
+ end
629
709
  end
710
+ end
630
711
  end
631
-
632
- end; end; end
712
+ end