net-ssh 3.2.0.rc2 → 7.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (204) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +2 -2
  3. data/.dockerignore +6 -0
  4. data/.github/config/rubocop_linter_action.yml +4 -0
  5. data/.github/workflows/ci-with-docker.yml +44 -0
  6. data/.github/workflows/ci.yml +87 -0
  7. data/.github/workflows/rubocop.yml +16 -0
  8. data/.gitignore +13 -0
  9. data/.rubocop.yml +22 -0
  10. data/.rubocop_todo.yml +1081 -0
  11. data/CHANGES.txt +228 -7
  12. data/Dockerfile +27 -0
  13. data/Dockerfile.openssl3 +17 -0
  14. data/Gemfile +13 -0
  15. data/Gemfile.noed25519 +12 -0
  16. data/ISSUE_TEMPLATE.md +30 -0
  17. data/Manifest +4 -5
  18. data/README.md +297 -0
  19. data/Rakefile +125 -74
  20. data/SECURITY.md +4 -0
  21. data/appveyor.yml +58 -0
  22. data/docker-compose.yml +23 -0
  23. data/lib/net/ssh/authentication/agent.rb +279 -18
  24. data/lib/net/ssh/authentication/certificate.rb +183 -0
  25. data/lib/net/ssh/authentication/constants.rb +17 -15
  26. data/lib/net/ssh/authentication/ed25519.rb +186 -0
  27. data/lib/net/ssh/authentication/ed25519_loader.rb +31 -0
  28. data/lib/net/ssh/authentication/key_manager.rb +86 -39
  29. data/lib/net/ssh/authentication/methods/abstract.rb +67 -48
  30. data/lib/net/ssh/authentication/methods/hostbased.rb +34 -37
  31. data/lib/net/ssh/authentication/methods/keyboard_interactive.rb +13 -13
  32. data/lib/net/ssh/authentication/methods/none.rb +16 -19
  33. data/lib/net/ssh/authentication/methods/password.rb +27 -17
  34. data/lib/net/ssh/authentication/methods/publickey.rb +96 -55
  35. data/lib/net/ssh/authentication/pageant.rb +471 -367
  36. data/lib/net/ssh/authentication/pub_key_fingerprint.rb +43 -0
  37. data/lib/net/ssh/authentication/session.rb +131 -121
  38. data/lib/net/ssh/buffer.rb +399 -300
  39. data/lib/net/ssh/buffered_io.rb +154 -150
  40. data/lib/net/ssh/config.rb +308 -185
  41. data/lib/net/ssh/connection/channel.rb +635 -613
  42. data/lib/net/ssh/connection/constants.rb +29 -29
  43. data/lib/net/ssh/connection/event_loop.rb +123 -0
  44. data/lib/net/ssh/connection/keepalive.rb +55 -51
  45. data/lib/net/ssh/connection/session.rb +620 -551
  46. data/lib/net/ssh/connection/term.rb +125 -123
  47. data/lib/net/ssh/errors.rb +101 -99
  48. data/lib/net/ssh/key_factory.rb +197 -105
  49. data/lib/net/ssh/known_hosts.rb +214 -127
  50. data/lib/net/ssh/loggable.rb +50 -49
  51. data/lib/net/ssh/packet.rb +83 -79
  52. data/lib/net/ssh/prompt.rb +50 -81
  53. data/lib/net/ssh/proxy/command.rb +105 -90
  54. data/lib/net/ssh/proxy/errors.rb +12 -10
  55. data/lib/net/ssh/proxy/http.rb +82 -79
  56. data/lib/net/ssh/proxy/https.rb +50 -0
  57. data/lib/net/ssh/proxy/jump.rb +54 -0
  58. data/lib/net/ssh/proxy/socks4.rb +2 -6
  59. data/lib/net/ssh/proxy/socks5.rb +14 -17
  60. data/lib/net/ssh/service/forward.rb +370 -317
  61. data/lib/net/ssh/test/channel.rb +145 -136
  62. data/lib/net/ssh/test/extensions.rb +131 -110
  63. data/lib/net/ssh/test/kex.rb +34 -32
  64. data/lib/net/ssh/test/local_packet.rb +46 -44
  65. data/lib/net/ssh/test/packet.rb +89 -70
  66. data/lib/net/ssh/test/remote_packet.rb +32 -30
  67. data/lib/net/ssh/test/script.rb +156 -142
  68. data/lib/net/ssh/test/socket.rb +49 -48
  69. data/lib/net/ssh/test.rb +82 -77
  70. data/lib/net/ssh/transport/algorithms.rb +441 -360
  71. data/lib/net/ssh/transport/cipher_factory.rb +96 -98
  72. data/lib/net/ssh/transport/constants.rb +32 -24
  73. data/lib/net/ssh/transport/ctr.rb +42 -22
  74. data/lib/net/ssh/transport/hmac/abstract.rb +81 -63
  75. data/lib/net/ssh/transport/hmac/md5.rb +0 -2
  76. data/lib/net/ssh/transport/hmac/md5_96.rb +0 -2
  77. data/lib/net/ssh/transport/hmac/none.rb +0 -2
  78. data/lib/net/ssh/transport/hmac/ripemd160.rb +0 -2
  79. data/lib/net/ssh/transport/hmac/sha1.rb +0 -2
  80. data/lib/net/ssh/transport/hmac/sha1_96.rb +0 -2
  81. data/lib/net/ssh/transport/hmac/sha2_256.rb +7 -11
  82. data/lib/net/ssh/transport/hmac/sha2_256_96.rb +4 -8
  83. data/lib/net/ssh/transport/hmac/sha2_256_etm.rb +12 -0
  84. data/lib/net/ssh/transport/hmac/sha2_512.rb +6 -9
  85. data/lib/net/ssh/transport/hmac/sha2_512_96.rb +4 -8
  86. data/lib/net/ssh/transport/hmac/sha2_512_etm.rb +12 -0
  87. data/lib/net/ssh/transport/hmac.rb +14 -12
  88. data/lib/net/ssh/transport/identity_cipher.rb +54 -52
  89. data/lib/net/ssh/transport/kex/abstract.rb +130 -0
  90. data/lib/net/ssh/transport/kex/abstract5656.rb +72 -0
  91. data/lib/net/ssh/transport/kex/curve25519_sha256.rb +39 -0
  92. data/lib/net/ssh/transport/kex/curve25519_sha256_loader.rb +30 -0
  93. data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha1.rb +33 -40
  94. data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha256.rb +11 -0
  95. data/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb +119 -213
  96. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb +53 -61
  97. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha256.rb +5 -9
  98. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp256.rb +36 -90
  99. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp384.rb +18 -10
  100. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp521.rb +18 -10
  101. data/lib/net/ssh/transport/kex.rb +15 -12
  102. data/lib/net/ssh/transport/key_expander.rb +24 -20
  103. data/lib/net/ssh/transport/openssl.rb +161 -124
  104. data/lib/net/ssh/transport/packet_stream.rb +225 -185
  105. data/lib/net/ssh/transport/server_version.rb +55 -56
  106. data/lib/net/ssh/transport/session.rb +306 -255
  107. data/lib/net/ssh/transport/state.rb +178 -176
  108. data/lib/net/ssh/verifiers/accept_new.rb +33 -0
  109. data/lib/net/ssh/verifiers/accept_new_or_local_tunnel.rb +33 -0
  110. data/lib/net/ssh/verifiers/always.rb +58 -0
  111. data/lib/net/ssh/verifiers/never.rb +19 -0
  112. data/lib/net/ssh/version.rb +55 -53
  113. data/lib/net/ssh.rb +110 -47
  114. data/net-ssh-public_cert.pem +18 -18
  115. data/net-ssh.gemspec +36 -205
  116. data/support/ssh_tunnel_bug.rb +5 -5
  117. data.tar.gz.sig +0 -0
  118. metadata +153 -118
  119. metadata.gz.sig +0 -0
  120. data/.travis.yml +0 -18
  121. data/README.rdoc +0 -182
  122. data/lib/net/ssh/authentication/agent/java_pageant.rb +0 -85
  123. data/lib/net/ssh/authentication/agent/socket.rb +0 -178
  124. data/lib/net/ssh/ruby_compat.rb +0 -46
  125. data/lib/net/ssh/verifiers/lenient.rb +0 -30
  126. data/lib/net/ssh/verifiers/null.rb +0 -12
  127. data/lib/net/ssh/verifiers/secure.rb +0 -52
  128. data/lib/net/ssh/verifiers/strict.rb +0 -24
  129. data/setup.rb +0 -1585
  130. data/support/arcfour_check.rb +0 -20
  131. data/test/README.txt +0 -18
  132. data/test/authentication/methods/common.rb +0 -28
  133. data/test/authentication/methods/test_abstract.rb +0 -51
  134. data/test/authentication/methods/test_hostbased.rb +0 -114
  135. data/test/authentication/methods/test_keyboard_interactive.rb +0 -121
  136. data/test/authentication/methods/test_none.rb +0 -41
  137. data/test/authentication/methods/test_password.rb +0 -95
  138. data/test/authentication/methods/test_publickey.rb +0 -148
  139. data/test/authentication/test_agent.rb +0 -232
  140. data/test/authentication/test_key_manager.rb +0 -240
  141. data/test/authentication/test_session.rb +0 -107
  142. data/test/common.rb +0 -125
  143. data/test/configs/auth_off +0 -5
  144. data/test/configs/auth_on +0 -4
  145. data/test/configs/empty +0 -0
  146. data/test/configs/eqsign +0 -3
  147. data/test/configs/exact_match +0 -8
  148. data/test/configs/host_plus +0 -10
  149. data/test/configs/multihost +0 -4
  150. data/test/configs/negative_match +0 -6
  151. data/test/configs/nohost +0 -19
  152. data/test/configs/numeric_host +0 -4
  153. data/test/configs/proxy_remote_user +0 -2
  154. data/test/configs/send_env +0 -2
  155. data/test/configs/substitutes +0 -8
  156. data/test/configs/wild_cards +0 -14
  157. data/test/connection/test_channel.rb +0 -487
  158. data/test/connection/test_session.rb +0 -564
  159. data/test/integration/README.txt +0 -17
  160. data/test/integration/Vagrantfile +0 -12
  161. data/test/integration/common.rb +0 -63
  162. data/test/integration/playbook.yml +0 -56
  163. data/test/integration/test_forward.rb +0 -637
  164. data/test/integration/test_id_rsa_keys.rb +0 -96
  165. data/test/integration/test_proxy.rb +0 -93
  166. data/test/known_hosts/github +0 -1
  167. data/test/known_hosts/github_hash +0 -1
  168. data/test/manual/test_pageant.rb +0 -37
  169. data/test/start/test_connection.rb +0 -53
  170. data/test/start/test_options.rb +0 -57
  171. data/test/start/test_transport.rb +0 -28
  172. data/test/start/test_user_nil.rb +0 -27
  173. data/test/test_all.rb +0 -12
  174. data/test/test_buffer.rb +0 -433
  175. data/test/test_buffered_io.rb +0 -63
  176. data/test/test_config.rb +0 -268
  177. data/test/test_key_factory.rb +0 -191
  178. data/test/test_known_hosts.rb +0 -66
  179. data/test/transport/hmac/test_md5.rb +0 -41
  180. data/test/transport/hmac/test_md5_96.rb +0 -27
  181. data/test/transport/hmac/test_none.rb +0 -34
  182. data/test/transport/hmac/test_ripemd160.rb +0 -36
  183. data/test/transport/hmac/test_sha1.rb +0 -36
  184. data/test/transport/hmac/test_sha1_96.rb +0 -27
  185. data/test/transport/hmac/test_sha2_256.rb +0 -37
  186. data/test/transport/hmac/test_sha2_256_96.rb +0 -27
  187. data/test/transport/hmac/test_sha2_512.rb +0 -37
  188. data/test/transport/hmac/test_sha2_512_96.rb +0 -27
  189. data/test/transport/kex/test_diffie_hellman_group14_sha1.rb +0 -13
  190. data/test/transport/kex/test_diffie_hellman_group1_sha1.rb +0 -150
  191. data/test/transport/kex/test_diffie_hellman_group_exchange_sha1.rb +0 -96
  192. data/test/transport/kex/test_diffie_hellman_group_exchange_sha256.rb +0 -19
  193. data/test/transport/kex/test_ecdh_sha2_nistp256.rb +0 -161
  194. data/test/transport/kex/test_ecdh_sha2_nistp384.rb +0 -38
  195. data/test/transport/kex/test_ecdh_sha2_nistp521.rb +0 -38
  196. data/test/transport/test_algorithms.rb +0 -328
  197. data/test/transport/test_cipher_factory.rb +0 -443
  198. data/test/transport/test_hmac.rb +0 -34
  199. data/test/transport/test_identity_cipher.rb +0 -40
  200. data/test/transport/test_packet_stream.rb +0 -1762
  201. data/test/transport/test_server_version.rb +0 -74
  202. data/test/transport/test_session.rb +0 -331
  203. data/test/transport/test_state.rb +0 -181
  204. data/test/verifiers/test_secure.rb +0 -40
@@ -2,671 +2,693 @@ require 'net/ssh/loggable'
2
2
  require 'net/ssh/connection/constants'
3
3
  require 'net/ssh/connection/term'
4
4
 
5
- module Net; module SSH; module Connection
6
-
7
- # The channel abstraction. Multiple "channels" can be multiplexed onto a
8
- # single SSH channel, each operating independently and seemingly in parallel.
9
- # This class represents a single such channel. Most operations performed
10
- # with the Net::SSH library will involve using one or more channels.
11
- #
12
- # Channels are intended to be used asynchronously. You request that one be
13
- # opened (via Connection::Session#open_channel), and when it is opened, your
14
- # callback is invoked. Then, you set various other callbacks on the newly
15
- # opened channel, which are called in response to the corresponding events.
16
- # Programming with Net::SSH works best if you think of your programs as
17
- # state machines. Complex programs are best implemented as objects that
18
- # wrap a channel. See Net::SCP and Net::SFTP for examples of how complex
19
- # state machines can be built on top of the SSH protocol.
20
- #
21
- # ssh.open_channel do |channel|
22
- # channel.exec("/invoke/some/command") do |ch, success|
23
- # abort "could not execute command" unless success
24
- #
25
- # channel.on_data do |ch, data|
26
- # puts "got stdout: #{data}"
27
- # channel.send_data "something for stdin\n"
28
- # end
29
- #
30
- # channel.on_extended_data do |ch, type, data|
31
- # puts "got stderr: #{data}"
32
- # end
33
- #
34
- # channel.on_close do |ch|
35
- # puts "channel is closing!"
36
- # end
37
- # end
38
- # end
39
- #
40
- # ssh.loop
41
- #
42
- # Channels also have a basic hash-like interface, that allows programs to
43
- # store arbitrary state information on a channel object. This helps simplify
44
- # the writing of state machines, especially when you may be juggling
45
- # multiple open channels at the same time.
46
- #
47
- # Note that data sent across SSH channels are governed by maximum packet
48
- # sizes and maximum window sizes. These details are managed internally
49
- # by Net::SSH::Connection::Channel, so you may remain blissfully ignorant
50
- # if you so desire, but you can always inspect the current maximums, as
51
- # well as the remaining window size, using the reader attributes for those
52
- # values.
53
- class Channel
54
- include Constants, Loggable
55
-
56
- # The local id for this channel, assigned by the Net::SSH::Connection::Session instance.
57
- attr_reader :local_id
58
-
59
- # The remote id for this channel, assigned by the remote host.
60
- attr_reader :remote_id
61
-
62
- # The type of this channel, usually "session".
63
- attr_reader :type
64
-
65
- # The underlying Net::SSH::Connection::Session instance that supports this channel.
66
- attr_reader :connection
67
-
68
- # The maximum packet size that the local host can receive.
69
- attr_reader :local_maximum_packet_size
70
-
71
- # The maximum amount of data that the local end of this channel can
72
- # receive. This is a total, not per-packet.
73
- attr_reader :local_maximum_window_size
74
-
75
- # The maximum packet size that the remote host can receive.
76
- attr_reader :remote_maximum_packet_size
77
-
78
- # The maximum amount of data that the remote end of this channel can
79
- # receive. This is a total, not per-packet.
80
- attr_reader :remote_maximum_window_size
81
-
82
- # This is the remaining window size on the local end of this channel. When
83
- # this reaches zero, no more data can be received.
84
- attr_reader :local_window_size
85
-
86
- # This is the remaining window size on the remote end of this channel. When
87
- # this reaches zero, no more data can be sent.
88
- attr_reader :remote_window_size
89
-
90
- # A hash of properties for this channel. These can be used to store state
91
- # information about this channel. See also #[] and #[]=.
92
- attr_reader :properties
93
-
94
- # The output buffer for this channel. Data written to the channel is
95
- # enqueued here, to be written as CHANNEL_DATA packets during each pass of
96
- # the event loop. See Connection::Session#process and #enqueue_pending_output.
97
- attr_reader :output #:nodoc:
98
-
99
- # The list of pending requests. Each time a request is sent which requires
100
- # a reply, the corresponding callback is pushed onto this queue. As responses
101
- # arrive, they are shifted off the front and handled.
102
- attr_reader :pending_requests #:nodoc:
103
-
104
- # Instantiates a new channel on the given connection, of the given type,
105
- # and with the given id. If a block is given, it will be remembered until
106
- # the channel is confirmed open by the server, and will be invoked at
107
- # that time (see #do_open_confirmation).
108
- #
109
- # This also sets the default maximum packet size and maximum window size.
110
- def initialize(connection, type, local_id, max_pkt_size = 0x8000, max_win_size = 0x20000, &on_confirm_open)
111
- self.logger = connection.logger
112
-
113
- @connection = connection
114
- @type = type
115
- @local_id = local_id
116
-
117
- @local_maximum_packet_size = max_pkt_size
118
- @local_window_size = @local_maximum_window_size = max_win_size
119
-
120
- @on_confirm_open = on_confirm_open
121
-
122
- @output = Buffer.new
123
-
124
- @properties = {}
125
-
126
- @pending_requests = []
127
- @on_open_failed = @on_data = @on_extended_data = @on_process = @on_close = @on_eof = nil
128
- @on_request = {}
129
- @closing = @eof = @sent_eof = @local_closed = @remote_closed = false
130
- end
5
+ module Net
6
+ module SSH
7
+ module Connection
8
+ # The channel abstraction. Multiple "channels" can be multiplexed onto a
9
+ # single SSH channel, each operating independently and seemingly in parallel.
10
+ # This class represents a single such channel. Most operations performed
11
+ # with the Net::SSH library will involve using one or more channels.
12
+ #
13
+ # Channels are intended to be used asynchronously. You request that one be
14
+ # opened (via Connection::Session#open_channel), and when it is opened, your
15
+ # callback is invoked. Then, you set various other callbacks on the newly
16
+ # opened channel, which are called in response to the corresponding events.
17
+ # Programming with Net::SSH works best if you think of your programs as
18
+ # state machines. Complex programs are best implemented as objects that
19
+ # wrap a channel. See Net::SCP and Net::SFTP for examples of how complex
20
+ # state machines can be built on top of the SSH protocol.
21
+ #
22
+ # ssh.open_channel do |channel|
23
+ # channel.exec("/invoke/some/command") do |ch, success|
24
+ # abort "could not execute command" unless success
25
+ #
26
+ # channel.on_data do |ch, data|
27
+ # puts "got stdout: #{data}"
28
+ # channel.send_data "something for stdin\n"
29
+ # end
30
+ #
31
+ # channel.on_extended_data do |ch, type, data|
32
+ # puts "got stderr: #{data}"
33
+ # end
34
+ #
35
+ # channel.on_close do |ch|
36
+ # puts "channel is closing!"
37
+ # end
38
+ # end
39
+ # end
40
+ #
41
+ # ssh.loop
42
+ #
43
+ # Channels also have a basic hash-like interface, that allows programs to
44
+ # store arbitrary state information on a channel object. This helps simplify
45
+ # the writing of state machines, especially when you may be juggling
46
+ # multiple open channels at the same time.
47
+ #
48
+ # Note that data sent across SSH channels are governed by maximum packet
49
+ # sizes and maximum window sizes. These details are managed internally
50
+ # by Net::SSH::Connection::Channel, so you may remain blissfully ignorant
51
+ # if you so desire, but you can always inspect the current maximums, as
52
+ # well as the remaining window size, using the reader attributes for those
53
+ # values.
54
+ class Channel
55
+ include Loggable
56
+ include Constants
57
+
58
+ # The local id for this channel, assigned by the Net::SSH::Connection::Session instance.
59
+ attr_reader :local_id
60
+
61
+ # The remote id for this channel, assigned by the remote host.
62
+ attr_reader :remote_id
63
+
64
+ # The type of this channel, usually "session".
65
+ attr_reader :type
66
+
67
+ # The underlying Net::SSH::Connection::Session instance that supports this channel.
68
+ attr_reader :connection
69
+
70
+ # The maximum packet size that the local host can receive.
71
+ attr_reader :local_maximum_packet_size
72
+
73
+ # The maximum amount of data that the local end of this channel can
74
+ # receive. This is a total, not per-packet.
75
+ attr_reader :local_maximum_window_size
76
+
77
+ # The maximum packet size that the remote host can receive.
78
+ attr_reader :remote_maximum_packet_size
79
+
80
+ # The maximum amount of data that the remote end of this channel can
81
+ # receive. This is a total, not per-packet.
82
+ attr_reader :remote_maximum_window_size
83
+
84
+ # This is the remaining window size on the local end of this channel. When
85
+ # this reaches zero, no more data can be received.
86
+ attr_reader :local_window_size
87
+
88
+ # This is the remaining window size on the remote end of this channel. When
89
+ # this reaches zero, no more data can be sent.
90
+ attr_reader :remote_window_size
91
+
92
+ # A hash of properties for this channel. These can be used to store state
93
+ # information about this channel. See also #[] and #[]=.
94
+ attr_reader :properties
95
+
96
+ # The output buffer for this channel. Data written to the channel is
97
+ # enqueued here, to be written as CHANNEL_DATA packets during each pass of
98
+ # the event loop. See Connection::Session#process and #enqueue_pending_output.
99
+ attr_reader :output # :nodoc:
100
+
101
+ # The list of pending requests. Each time a request is sent which requires
102
+ # a reply, the corresponding callback is pushed onto this queue. As responses
103
+ # arrive, they are shifted off the front and handled.
104
+ attr_reader :pending_requests # :nodoc:
105
+
106
+ # Instantiates a new channel on the given connection, of the given type,
107
+ # and with the given id. If a block is given, it will be remembered until
108
+ # the channel is confirmed open by the server, and will be invoked at
109
+ # that time (see #do_open_confirmation).
110
+ #
111
+ # This also sets the default maximum packet size and maximum window size.
112
+ def initialize(connection, type, local_id, max_pkt_size = 0x8000, max_win_size = 0x20000, &on_confirm_open)
113
+ self.logger = connection.logger
114
+
115
+ @connection = connection
116
+ @type = type
117
+ @local_id = local_id
118
+
119
+ @local_maximum_packet_size = max_pkt_size
120
+ @local_window_size = @local_maximum_window_size = max_win_size
121
+
122
+ @on_confirm_open = on_confirm_open
123
+
124
+ @output = Buffer.new
125
+
126
+ @properties = {}
127
+
128
+ @pending_requests = []
129
+ @on_open_failed = @on_data = @on_extended_data = @on_process = @on_close = @on_eof = nil
130
+ @on_request = {}
131
+ @closing = @eof = @sent_eof = @local_closed = @remote_closed = false
132
+ end
131
133
 
132
- # A shortcut for accessing properties of the channel (see #properties).
133
- def [](name)
134
- @properties[name]
135
- end
134
+ # A shortcut for accessing properties of the channel (see #properties).
135
+ def [](name)
136
+ @properties[name]
137
+ end
136
138
 
137
- # A shortcut for setting properties of the channel (see #properties).
138
- def []=(name, value)
139
- @properties[name] = value
140
- end
139
+ # A shortcut for setting properties of the channel (see #properties).
140
+ def []=(name, value)
141
+ @properties[name] = value
142
+ end
141
143
 
142
- # Syntactic sugar for executing a command. Sends a channel request asking
143
- # that the given command be invoked. If the block is given, it will be
144
- # called when the server responds. The first parameter will be the
145
- # channel, and the second will be true or false, indicating whether the
146
- # request succeeded or not. In this case, success means that the command
147
- # is being executed, not that it has completed, and failure means that the
148
- # command altogether failed to be executed.
149
- #
150
- # channel.exec "ls -l /home" do |ch, success|
151
- # if success
152
- # puts "command has begun executing..."
153
- # # this is a good place to hang callbacks like #on_data...
154
- # else
155
- # puts "alas! the command could not be invoked!"
156
- # end
157
- # end
158
- def exec(command, &block)
159
- send_channel_request("exec", :string, command, &block)
160
- end
144
+ # Syntactic sugar for executing a command. Sends a channel request asking
145
+ # that the given command be invoked. If the block is given, it will be
146
+ # called when the server responds. The first parameter will be the
147
+ # channel, and the second will be true or false, indicating whether the
148
+ # request succeeded or not. In this case, success means that the command
149
+ # is being executed, not that it has completed, and failure means that the
150
+ # command altogether failed to be executed.
151
+ #
152
+ # channel.exec "ls -l /home" do |ch, success|
153
+ # if success
154
+ # puts "command has begun executing..."
155
+ # # this is a good place to hang callbacks like #on_data...
156
+ # else
157
+ # puts "alas! the command could not be invoked!"
158
+ # end
159
+ # end
160
+ def exec(command, &block)
161
+ send_channel_request("exec", :string, command, &block)
162
+ end
161
163
 
162
- # Syntactic sugar for requesting that a subsystem be started. Subsystems
163
- # are a way for other protocols (like SFTP) to be run, using SSH as
164
- # the transport. Generally, you'll never need to call this directly unless
165
- # you are the implementor of something that consumes an SSH subsystem, like
166
- # SFTP.
167
- #
168
- # channel.subsystem("sftp") do |ch, success|
169
- # if success
170
- # puts "subsystem successfully started"
171
- # else
172
- # puts "subsystem could not be started"
173
- # end
174
- # end
175
- def subsystem(subsystem, &block)
176
- send_channel_request("subsystem", :string, subsystem, &block)
177
- end
164
+ # Syntactic sugar for requesting that a subsystem be started. Subsystems
165
+ # are a way for other protocols (like SFTP) to be run, using SSH as
166
+ # the transport. Generally, you'll never need to call this directly unless
167
+ # you are the implementor of something that consumes an SSH subsystem, like
168
+ # SFTP.
169
+ #
170
+ # channel.subsystem("sftp") do |ch, success|
171
+ # if success
172
+ # puts "subsystem successfully started"
173
+ # else
174
+ # puts "subsystem could not be started"
175
+ # end
176
+ # end
177
+ def subsystem(subsystem, &block)
178
+ send_channel_request("subsystem", :string, subsystem, &block)
179
+ end
178
180
 
179
- # Syntactic sugar for setting an environment variable in the remote
180
- # process' environment. Note that for security reasons, the server may
181
- # refuse to set certain environment variables, or all, at the server's
182
- # discretion. If you are connecting to an OpenSSH server, you will
183
- # need to update the AcceptEnv setting in the sshd_config to include the
184
- # environment variables you want to send.
185
- #
186
- # channel.env "PATH", "/usr/local/bin"
187
- def env(variable_name, variable_value, &block)
188
- send_channel_request("env", :string, variable_name, :string, variable_value, &block)
189
- end
181
+ # Syntactic sugar for setting an environment variable in the remote
182
+ # process' environment. Note that for security reasons, the server may
183
+ # refuse to set certain environment variables, or all, at the server's
184
+ # discretion. If you are connecting to an OpenSSH server, you will
185
+ # need to update the AcceptEnv setting in the sshd_config to include the
186
+ # environment variables you want to send.
187
+ #
188
+ # channel.env "PATH", "/usr/local/bin"
189
+ def env(variable_name, variable_value, &block)
190
+ send_channel_request("env", :string, variable_name, :string, variable_value, &block)
191
+ end
190
192
 
191
- # A hash of the valid PTY options (see #request_pty).
192
- VALID_PTY_OPTIONS = { :term => "xterm",
193
- :chars_wide => 80,
194
- :chars_high => 24,
195
- :pixels_wide => 640,
196
- :pixels_high => 480,
197
- :modes => {} }
198
-
199
- # Requests that a pseudo-tty (or "pty") be made available for this channel.
200
- # This is useful when you want to invoke and interact with some kind of
201
- # screen-based program (e.g., vim, or some menuing system).
202
- #
203
- # Note, that without a pty some programs (e.g. sudo, or subversion) on
204
- # some systems, will not be able to run interactively, and will error
205
- # instead of prompt if they ever need some user interaction.
206
- #
207
- # Note, too, that when a pty is requested, user's shell configuration
208
- # scripts (.bashrc and such) are not run by default, whereas they are
209
- # run when a pty is not present.
210
- #
211
- # channel.request_pty do |ch, success|
212
- # if success
213
- # puts "pty successfully obtained"
214
- # else
215
- # puts "could not obtain pty"
216
- # end
217
- # end
218
- def request_pty(opts={}, &block)
219
- extra = opts.keys - VALID_PTY_OPTIONS.keys
220
- raise ArgumentError, "invalid option(s) to request_pty: #{extra.inspect}" if extra.any?
221
-
222
- opts = VALID_PTY_OPTIONS.merge(opts)
223
-
224
- modes = opts[:modes].inject(Buffer.new) do |memo, (mode, data)|
225
- memo.write_byte(mode).write_long(data)
226
- end
227
- # mark the end of the mode opcode list with a 0 byte
228
- modes.write_byte(0)
193
+ # A hash of the valid PTY options (see #request_pty).
194
+ VALID_PTY_OPTIONS = { term: "xterm",
195
+ chars_wide: 80,
196
+ chars_high: 24,
197
+ pixels_wide: 640,
198
+ pixels_high: 480,
199
+ modes: {} }
200
+
201
+ # Requests that a pseudo-tty (or "pty") be made available for this channel.
202
+ # This is useful when you want to invoke and interact with some kind of
203
+ # screen-based program (e.g., vim, or some menuing system).
204
+ #
205
+ # Note, that without a pty some programs (e.g. sudo, or subversion) on
206
+ # some systems, will not be able to run interactively, and will error
207
+ # instead of prompt if they ever need some user interaction.
208
+ #
209
+ # Note, too, that when a pty is requested, user's shell configuration
210
+ # scripts (.bashrc and such) are not run by default, whereas they are
211
+ # run when a pty is not present.
212
+ #
213
+ # channel.request_pty do |ch, success|
214
+ # if success
215
+ # puts "pty successfully obtained"
216
+ # else
217
+ # puts "could not obtain pty"
218
+ # end
219
+ # end
220
+ def request_pty(opts = {}, &block)
221
+ extra = opts.keys - VALID_PTY_OPTIONS.keys
222
+ raise ArgumentError, "invalid option(s) to request_pty: #{extra.inspect}" if extra.any?
223
+
224
+ opts = VALID_PTY_OPTIONS.merge(opts)
225
+
226
+ modes = opts[:modes].inject(Buffer.new) do |memo, (mode, data)|
227
+ memo.write_byte(mode).write_long(data)
228
+ end
229
+ # mark the end of the mode opcode list with a 0 byte
230
+ modes.write_byte(0)
229
231
 
230
- send_channel_request("pty-req", :string, opts[:term],
231
- :long, opts[:chars_wide], :long, opts[:chars_high],
232
- :long, opts[:pixels_wide], :long, opts[:pixels_high],
233
- :string, modes.to_s, &block)
234
- end
232
+ send_channel_request("pty-req", :string, opts[:term],
233
+ :long, opts[:chars_wide], :long, opts[:chars_high],
234
+ :long, opts[:pixels_wide], :long, opts[:pixels_high],
235
+ :string, modes.to_s, &block)
236
+ end
235
237
 
236
- # Sends data to the channel's remote endpoint. This usually has the
237
- # effect of sending the given string to the remote process' stdin stream.
238
- # Note that it does not immediately send the data across the channel,
239
- # but instead merely appends the given data to the channel's output buffer,
240
- # preparatory to being packaged up and sent out the next time the connection
241
- # is accepting data. (A connection might not be accepting data if, for
242
- # instance, it has filled its data window and has not yet been resized by
243
- # the remote end-point.)
244
- #
245
- # This will raise an exception if the channel has previously declared
246
- # that no more data will be sent (see #eof!).
247
- #
248
- # channel.send_data("the password\n")
249
- def send_data(data)
250
- raise EOFError, "cannot send data if channel has declared eof" if eof?
251
- output.append(data.to_s)
252
- end
238
+ # Sends data to the channel's remote endpoint. This usually has the
239
+ # effect of sending the given string to the remote process' stdin stream.
240
+ # Note that it does not immediately send the data across the channel,
241
+ # but instead merely appends the given data to the channel's output buffer,
242
+ # preparatory to being packaged up and sent out the next time the connection
243
+ # is accepting data. (A connection might not be accepting data if, for
244
+ # instance, it has filled its data window and has not yet been resized by
245
+ # the remote end-point.)
246
+ #
247
+ # This will raise an exception if the channel has previously declared
248
+ # that no more data will be sent (see #eof!).
249
+ #
250
+ # channel.send_data("the password\n")
251
+ def send_data(data)
252
+ raise EOFError, "cannot send data if channel has declared eof" if eof?
253
+
254
+ output.append(data.to_s)
255
+ end
253
256
 
254
- # Returns true if the channel exists in the channel list of the session,
255
- # and false otherwise. This can be used to determine whether a channel has
256
- # been closed or not.
257
- #
258
- # ssh.loop { channel.active? }
259
- def active?
260
- connection.channels.key?(local_id)
261
- end
257
+ # Returns true if the channel exists in the channel list of the session,
258
+ # and false otherwise. This can be used to determine whether a channel has
259
+ # been closed or not.
260
+ #
261
+ # ssh.loop { channel.active? }
262
+ def active?
263
+ connection.channels.key?(local_id)
264
+ end
262
265
 
263
- # Runs the SSH event loop until the channel is no longer active. This is
264
- # handy for blocking while you wait for some channel to finish.
265
- #
266
- # channel.exec("grep ...") { ... }
267
- # channel.wait
268
- def wait
269
- connection.loop { active? }
270
- end
266
+ # Runs the SSH event loop until the channel is no longer active. This is
267
+ # handy for blocking while you wait for some channel to finish.
268
+ #
269
+ # channel.exec("grep ...") { ... }
270
+ # channel.wait
271
+ def wait
272
+ connection.loop { active? }
273
+ end
271
274
 
272
- # True if close() has been called; NOTE: if the channel has data waiting to
273
- # be sent then the channel will close after all the data is sent. See
274
- # closed?() to determine if we have actually sent CHANNEL_CLOSE to server.
275
- # This may be true for awhile before closed? returns true if we are still
276
- # sending buffered output to server.
277
- def closing?
278
- @closing
279
- end
275
+ # True if close() has been called; NOTE: if the channel has data waiting to
276
+ # be sent then the channel will close after all the data is sent. See
277
+ # closed?() to determine if we have actually sent CHANNEL_CLOSE to server.
278
+ # This may be true for awhile before closed? returns true if we are still
279
+ # sending buffered output to server.
280
+ def closing?
281
+ @closing
282
+ end
280
283
 
281
- # True if we have sent CHANNEL_CLOSE to the remote server.
282
- def local_closed?
283
- @local_closed
284
- end
284
+ # True if we have sent CHANNEL_CLOSE to the remote server.
285
+ def local_closed?
286
+ @local_closed
287
+ end
285
288
 
286
- def remote_closed?
287
- @remote_closed
288
- end
289
+ def remote_closed?
290
+ @remote_closed
291
+ end
289
292
 
290
- def remote_closed!
291
- @remote_closed = true
292
- end
293
+ def remote_closed!
294
+ @remote_closed = true
295
+ end
293
296
 
294
- # Requests that the channel be closed. It only marks the channel to be closed
295
- # the CHANNEL_CLOSE message will be sent from event loop
296
- def close
297
- return if @closing
298
- @closing = true
299
- end
297
+ # Requests that the channel be closed. It only marks the channel to be closed
298
+ # the CHANNEL_CLOSE message will be sent from event loop
299
+ def close
300
+ return if @closing
300
301
 
301
- # Returns true if the local end of the channel has declared that no more
302
- # data is forthcoming (see #eof!). Trying to send data via #send_data when
303
- # this is true will result in an exception being raised.
304
- def eof?
305
- @eof
306
- end
302
+ @closing = true
303
+ end
307
304
 
308
- # Tells the remote end of the channel that no more data is forthcoming
309
- # from this end of the channel. The remote end may still send data.
310
- # The CHANNEL_EOF packet will be sent once the output buffer is empty.
311
- def eof!
312
- return if eof?
313
- @eof = true
314
- end
305
+ # Returns true if the local end of the channel has declared that no more
306
+ # data is forthcoming (see #eof!). Trying to send data via #send_data when
307
+ # this is true will result in an exception being raised.
308
+ def eof?
309
+ @eof
310
+ end
315
311
 
316
- # If an #on_process handler has been set up, this will cause it to be
317
- # invoked (passing the channel itself as an argument). It also causes all
318
- # pending output to be enqueued as CHANNEL_DATA packets (see #enqueue_pending_output).
319
- def process
320
- @on_process.call(self) if @on_process
321
- enqueue_pending_output
312
+ # Tells the remote end of the channel that no more data is forthcoming
313
+ # from this end of the channel. The remote end may still send data.
314
+ # The CHANNEL_EOF packet will be sent once the output buffer is empty.
315
+ def eof!
316
+ return if eof?
322
317
 
323
- if @eof and not @sent_eof and output.empty? and remote_id and not @local_closed
324
- connection.send_message(Buffer.from(:byte, CHANNEL_EOF, :long, remote_id))
325
- @sent_eof = true
326
- end
318
+ @eof = true
319
+ end
327
320
 
328
- if @closing and not @local_closed and output.empty? and remote_id
329
- connection.send_message(Buffer.from(:byte, CHANNEL_CLOSE, :long, remote_id))
330
- @local_closed = true
331
- connection.cleanup_channel(self)
332
- end
333
- end
321
+ # If an #on_process handler has been set up, this will cause it to be
322
+ # invoked (passing the channel itself as an argument). It also causes all
323
+ # pending output to be enqueued as CHANNEL_DATA packets (see #enqueue_pending_output).
324
+ def process
325
+ @on_process.call(self) if @on_process
326
+ enqueue_pending_output
334
327
 
335
- # Registers a callback to be invoked when data packets are received by the
336
- # channel. The callback is called with the channel as the first argument,
337
- # and the data as the second.
338
- #
339
- # channel.on_data do |ch, data|
340
- # puts "got data: #{data.inspect}"
341
- # end
342
- #
343
- # Data received this way is typically the data written by the remote
344
- # process to its +stdout+ stream.
345
- def on_data(&block)
346
- old, @on_data = @on_data, block
347
- old
348
- end
328
+ if @eof and not @sent_eof and output.empty? and remote_id and not @local_closed
329
+ connection.send_message(Buffer.from(:byte, CHANNEL_EOF, :long, remote_id))
330
+ @sent_eof = true
331
+ end
349
332
 
350
- # Registers a callback to be invoked when extended data packets are received
351
- # by the channel. The callback is called with the channel as the first
352
- # argument, the data type (as an integer) as the second, and the data as
353
- # the third. Extended data is almost exclusively used to send +stderr+ data
354
- # (+type+ == 1). Other extended data types are not defined by the SSH
355
- # protocol.
356
- #
357
- # channel.on_extended_data do |ch, type, data|
358
- # puts "got stderr: #{data.inspect}"
359
- # end
360
- def on_extended_data(&block)
361
- old, @on_extended_data = @on_extended_data, block
362
- old
363
- end
333
+ if @closing and not @local_closed and output.empty? and remote_id
334
+ connection.send_message(Buffer.from(:byte, CHANNEL_CLOSE, :long, remote_id))
335
+ @local_closed = true
336
+ connection.cleanup_channel(self)
337
+ end
338
+ end
364
339
 
365
- # Registers a callback to be invoked for each pass of the event loop for
366
- # this channel. There are no guarantees on timeliness in the event loop,
367
- # but it will be called roughly once for each packet received by the
368
- # connection (not the channel). This callback is invoked with the channel
369
- # as the sole argument.
370
- #
371
- # Here's an example that accumulates the channel data into a variable on
372
- # the channel itself, and displays individual lines in the input one
373
- # at a time when the channel is processed:
374
- #
375
- # channel[:data] = ""
376
- #
377
- # channel.on_data do |ch, data|
378
- # channel[:data] << data
379
- # end
380
- #
381
- # channel.on_process do |ch|
382
- # if channel[:data] =~ /^.*?\n/
383
- # puts $&
384
- # channel[:data] = $'
385
- # end
386
- # end
387
- def on_process(&block)
388
- old, @on_process = @on_process, block
389
- old
390
- end
340
+ # Registers a callback to be invoked when data packets are received by the
341
+ # channel. The callback is called with the channel as the first argument,
342
+ # and the data as the second.
343
+ #
344
+ # channel.on_data do |ch, data|
345
+ # puts "got data: #{data.inspect}"
346
+ # end
347
+ #
348
+ # Data received this way is typically the data written by the remote
349
+ # process to its +stdout+ stream.
350
+ def on_data(&block)
351
+ old, @on_data = @on_data, block
352
+ old
353
+ end
391
354
 
392
- # Registers a callback to be invoked when the server acknowledges that a
393
- # channel is closed. This is invoked with the channel as the sole argument.
394
- #
395
- # channel.on_close do |ch|
396
- # puts "remote end is closing!"
397
- # end
398
- def on_close(&block)
399
- old, @on_close = @on_close, block
400
- old
401
- end
355
+ # Registers a callback to be invoked when extended data packets are received
356
+ # by the channel. The callback is called with the channel as the first
357
+ # argument, the data type (as an integer) as the second, and the data as
358
+ # the third. Extended data is almost exclusively used to send +stderr+ data
359
+ # (+type+ == 1). Other extended data types are not defined by the SSH
360
+ # protocol.
361
+ #
362
+ # channel.on_extended_data do |ch, type, data|
363
+ # puts "got stderr: #{data.inspect}"
364
+ # end
365
+ def on_extended_data(&block)
366
+ old, @on_extended_data = @on_extended_data, block
367
+ old
368
+ end
402
369
 
403
- # Registers a callback to be invoked when the server indicates that no more
404
- # data will be sent to the channel (although the channel can still send
405
- # data to the server). The channel is the sole argument to the callback.
406
- #
407
- # channel.on_eof do |ch|
408
- # puts "remote end is done sending data"
409
- # end
410
- def on_eof(&block)
411
- old, @on_eof = @on_eof, block
412
- old
413
- end
370
+ # Registers a callback to be invoked for each pass of the event loop for
371
+ # this channel. There are no guarantees on timeliness in the event loop,
372
+ # but it will be called roughly once for each packet received by the
373
+ # connection (not the channel). This callback is invoked with the channel
374
+ # as the sole argument.
375
+ #
376
+ # Here's an example that accumulates the channel data into a variable on
377
+ # the channel itself, and displays individual lines in the input one
378
+ # at a time when the channel is processed:
379
+ #
380
+ # channel[:data] = ""
381
+ #
382
+ # channel.on_data do |ch, data|
383
+ # channel[:data] << data
384
+ # end
385
+ #
386
+ # channel.on_process do |ch|
387
+ # if channel[:data] =~ /^.*?\n/
388
+ # puts $&
389
+ # channel[:data] = $'
390
+ # end
391
+ # end
392
+ def on_process(&block)
393
+ old, @on_process = @on_process, block
394
+ old
395
+ end
414
396
 
415
- # Registers a callback to be invoked when the server was unable to open
416
- # the requested channel. The channel itself will be passed to the block,
417
- # along with the integer "reason code" for the failure, and a textual
418
- # description of the failure from the server.
419
- #
420
- # channel = session.open_channel do |ch|
421
- # # ..
422
- # end
423
- #
424
- # channel.on_open_failed { |ch, code, desc| ... }
425
- def on_open_failed(&block)
426
- old, @on_open_failed = @on_open_failed, block
427
- old
428
- end
397
+ # Registers a callback to be invoked when the server acknowledges that a
398
+ # channel is closed. This is invoked with the channel as the sole argument.
399
+ #
400
+ # channel.on_close do |ch|
401
+ # puts "remote end is closing!"
402
+ # end
403
+ def on_close(&block)
404
+ old, @on_close = @on_close, block
405
+ old
406
+ end
429
407
 
430
- # Registers a callback to be invoked when a channel request of the given
431
- # type is received. The callback will receive the channel as the first
432
- # argument, and the associated (unparsed) data as the second. The data
433
- # will be a Net::SSH::Buffer that you will need to parse, yourself,
434
- # according to the kind of request you are watching.
435
- #
436
- # By default, if the request wants a reply, Net::SSH will send a
437
- # CHANNEL_SUCCESS response for any request that was handled by a registered
438
- # callback, and CHANNEL_FAILURE for any that wasn't, but if you want your
439
- # registered callback to result in a CHANNEL_FAILURE response, just raise
440
- # Net::SSH::ChannelRequestFailed.
441
- #
442
- # Some common channel requests that your programs might want to listen
443
- # for are:
444
- #
445
- # * "exit-status" : the exit status of the remote process will be reported
446
- # as a long integer in the data buffer, which you can grab via
447
- # data.read_long.
448
- # * "exit-signal" : if the remote process died as a result of a signal
449
- # being sent to it, the signal will be reported as a string in the
450
- # data, via data.read_string. (Not all SSH servers support this channel
451
- # request type.)
452
- #
453
- # channel.on_request "exit-status" do |ch, data|
454
- # puts "process terminated with exit status: #{data.read_long}"
455
- # end
456
- def on_request(type, &block)
457
- old, @on_request[type] = @on_request[type], block
458
- old
459
- end
408
+ # Registers a callback to be invoked when the server indicates that no more
409
+ # data will be sent to the channel (although the channel can still send
410
+ # data to the server). The channel is the sole argument to the callback.
411
+ #
412
+ # channel.on_eof do |ch|
413
+ # puts "remote end is done sending data"
414
+ # end
415
+ def on_eof(&block)
416
+ old, @on_eof = @on_eof, block
417
+ old
418
+ end
460
419
 
461
- # Sends a new channel request with the given name. The extra +data+
462
- # parameter must either be empty, or consist of an even number of
463
- # arguments. See Net::SSH::Buffer.from for a description of their format.
464
- # If a block is given, it is registered as a callback for a pending
465
- # request, and the packet will be flagged so that the server knows a
466
- # reply is required. If no block is given, the server will send no
467
- # response to this request. Responses, where required, will cause the
468
- # callback to be invoked with the channel as the first argument, and
469
- # either true or false as the second, depending on whether the request
470
- # succeeded or not. The meaning of "success" and "failure" in this context
471
- # is dependent on the specific request that was sent.
472
- #
473
- # channel.send_channel_request "shell" do |ch, success|
474
- # if success
475
- # puts "user shell started successfully"
476
- # else
477
- # puts "could not start user shell"
478
- # end
479
- # end
480
- #
481
- # Most channel requests you'll want to send are already wrapped in more
482
- # convenient helper methods (see #exec and #subsystem).
483
- def send_channel_request(request_name, *data, &callback)
484
- info { "sending channel request #{request_name.inspect}" }
485
- fail "Channel open not yet confirmed, please call send_channel_request(or exec) from block of open_channel" unless remote_id
486
- msg = Buffer.from(:byte, CHANNEL_REQUEST,
487
- :long, remote_id, :string, request_name,
488
- :bool, !callback.nil?, *data)
489
- connection.send_message(msg)
490
- pending_requests << callback if callback
491
- end
420
+ # Registers a callback to be invoked when the server was unable to open
421
+ # the requested channel. The channel itself will be passed to the block,
422
+ # along with the integer "reason code" for the failure, and a textual
423
+ # description of the failure from the server.
424
+ #
425
+ # channel = session.open_channel do |ch|
426
+ # # ..
427
+ # end
428
+ #
429
+ # channel.on_open_failed { |ch, code, desc| ... }
430
+ def on_open_failed(&block)
431
+ old, @on_open_failed = @on_open_failed, block
432
+ old
433
+ end
492
434
 
493
- public # these methods are public, but for Net::SSH internal use only
494
-
495
- # Enqueues pending output at the connection as CHANNEL_DATA packets. This
496
- # does nothing if the channel has not yet been confirmed open (see
497
- # #do_open_confirmation). This is called automatically by #process, which
498
- # is called from the event loop (Connection::Session#process). You will
499
- # generally not need to invoke it directly.
500
- def enqueue_pending_output #:nodoc:
501
- return unless remote_id
502
-
503
- while output.length > 0
504
- length = output.length
505
- length = remote_window_size if length > remote_window_size
506
- length = remote_maximum_packet_size if length > remote_maximum_packet_size
507
-
508
- if length > 0
509
- connection.send_message(Buffer.from(:byte, CHANNEL_DATA, :long, remote_id, :string, output.read(length)))
510
- output.consume!
511
- @remote_window_size -= length
512
- else
513
- break
514
- end
435
+ # Registers a callback to be invoked when a channel request of the given
436
+ # type is received. The callback will receive the channel as the first
437
+ # argument, and the associated (unparsed) data as the second. The data
438
+ # will be a Net::SSH::Buffer that you will need to parse, yourself,
439
+ # according to the kind of request you are watching.
440
+ #
441
+ # By default, if the request wants a reply, Net::SSH will send a
442
+ # CHANNEL_SUCCESS response for any request that was handled by a registered
443
+ # callback, and CHANNEL_FAILURE for any that wasn't, but if you want your
444
+ # registered callback to result in a CHANNEL_FAILURE response, just raise
445
+ # Net::SSH::ChannelRequestFailed.
446
+ #
447
+ # Some common channel requests that your programs might want to listen
448
+ # for are:
449
+ #
450
+ # * "exit-status" : the exit status of the remote process will be reported
451
+ # as a long integer in the data buffer, which you can grab via
452
+ # data.read_long.
453
+ # * "exit-signal" : if the remote process died as a result of a signal
454
+ # being sent to it, the signal will be reported as a string in the
455
+ # data, via data.read_string. (Not all SSH servers support this channel
456
+ # request type.)
457
+ #
458
+ # channel.on_request "exit-status" do |ch, data|
459
+ # puts "process terminated with exit status: #{data.read_long}"
460
+ # end
461
+ def on_request(type, &block)
462
+ old, @on_request[type] = @on_request[type], block
463
+ old
515
464
  end
516
- end
517
465
 
518
- # Invoked when the server confirms that a channel has been opened.
519
- # The remote_id is the id of the channel as assigned by the remote host,
520
- # and max_window and max_packet are the maximum window and maximum
521
- # packet sizes, respectively. If an open-confirmation callback was
522
- # given when the channel was created, it is invoked at this time with
523
- # the channel itself as the sole argument.
524
- def do_open_confirmation(remote_id, max_window, max_packet) #:nodoc:
525
- @remote_id = remote_id
526
- @remote_window_size = @remote_maximum_window_size = max_window
527
- @remote_maximum_packet_size = max_packet
528
- connection.forward.agent(self) if connection.options[:forward_agent] && type == "session"
529
- forward_local_env(connection.options[:send_env]) if connection.options[:send_env]
530
- @on_confirm_open.call(self) if @on_confirm_open
531
- end
466
+ # Sends a new channel request with the given name. The extra +data+
467
+ # parameter must either be empty, or consist of an even number of
468
+ # arguments. See Net::SSH::Buffer.from for a description of their format.
469
+ # If a block is given, it is registered as a callback for a pending
470
+ # request, and the packet will be flagged so that the server knows a
471
+ # reply is required. If no block is given, the server will send no
472
+ # response to this request. Responses, where required, will cause the
473
+ # callback to be invoked with the channel as the first argument, and
474
+ # either true or false as the second, depending on whether the request
475
+ # succeeded or not. The meaning of "success" and "failure" in this context
476
+ # is dependent on the specific request that was sent.
477
+ #
478
+ # channel.send_channel_request "shell" do |ch, success|
479
+ # if success
480
+ # puts "user shell started successfully"
481
+ # else
482
+ # puts "could not start user shell"
483
+ # end
484
+ # end
485
+ #
486
+ # Most channel requests you'll want to send are already wrapped in more
487
+ # convenient helper methods (see #exec and #subsystem).
488
+ def send_channel_request(request_name, *data, &callback)
489
+ info { "sending channel request #{request_name.inspect}" }
490
+ fail "Channel open not yet confirmed, please call send_channel_request(or exec) from block of open_channel" unless remote_id
491
+
492
+ msg = Buffer.from(:byte, CHANNEL_REQUEST,
493
+ :long, remote_id, :string, request_name,
494
+ :bool, !callback.nil?, *data)
495
+ connection.send_message(msg)
496
+ pending_requests << callback if callback
497
+ end
532
498
 
533
- # Invoked when the server failed to open the channel. If an #on_open_failed
534
- # callback was specified, it will be invoked with the channel, reason code,
535
- # and description as arguments. Otherwise, a ChannelOpenFailed exception
536
- # will be raised.
537
- def do_open_failed(reason_code, description)
538
- if @on_open_failed
539
- @on_open_failed.call(self, reason_code, description)
540
- else
541
- raise ChannelOpenFailed.new(reason_code, description)
499
+ public # these methods are public, but for Net::SSH internal use only
500
+
501
+ # Enqueues pending output at the connection as CHANNEL_DATA packets. This
502
+ # does nothing if the channel has not yet been confirmed open (see
503
+ # #do_open_confirmation). This is called automatically by #process, which
504
+ # is called from the event loop (Connection::Session#process). You will
505
+ # generally not need to invoke it directly.
506
+ def enqueue_pending_output # :nodoc:
507
+ return unless remote_id
508
+
509
+ while output.length > 0
510
+ length = output.length
511
+ length = remote_window_size if length > remote_window_size
512
+ length = remote_maximum_packet_size if length > remote_maximum_packet_size
513
+
514
+ if length > 0
515
+ connection.send_message(Buffer.from(:byte, CHANNEL_DATA, :long, remote_id, :string, output.read(length)))
516
+ output.consume!
517
+ @remote_window_size -= length
518
+ else
519
+ break
520
+ end
521
+ end
542
522
  end
543
- end
544
523
 
545
- # Invoked when the server sends a CHANNEL_WINDOW_ADJUST packet, and
546
- # causes the remote window size to be adjusted upwards by the given
547
- # number of bytes. This has the effect of allowing more data to be sent
548
- # from the local end to the remote end of the channel.
549
- def do_window_adjust(bytes) #:nodoc:
550
- @remote_maximum_window_size += bytes
551
- @remote_window_size += bytes
552
- end
524
+ # Invoked when the server confirms that a channel has been opened.
525
+ # The remote_id is the id of the channel as assigned by the remote host,
526
+ # and max_window and max_packet are the maximum window and maximum
527
+ # packet sizes, respectively. If an open-confirmation callback was
528
+ # given when the channel was created, it is invoked at this time with
529
+ # the channel itself as the sole argument.
530
+ def do_open_confirmation(remote_id, max_window, max_packet) # :nodoc:
531
+ @remote_id = remote_id
532
+ @remote_window_size = @remote_maximum_window_size = max_window
533
+ @remote_maximum_packet_size = max_packet
534
+ connection.forward.agent(self) if connection.options[:forward_agent] && type == "session"
535
+ forward_local_env(connection.options[:send_env]) if connection.options[:send_env]
536
+ set_remote_env(connection.options[:set_env]) if connection.options[:set_env]
537
+ @on_confirm_open.call(self) if @on_confirm_open
538
+ end
553
539
 
554
- # Invoked when the server sends a channel request. If any #on_request
555
- # callback has been registered for the specific type of this request,
556
- # it is invoked. If +want_reply+ is true, a packet will be sent of
557
- # either CHANNEL_SUCCESS or CHANNEL_FAILURE type. If there was no callback
558
- # to handle the request, CHANNEL_FAILURE will be sent. Otherwise,
559
- # CHANNEL_SUCCESS, unless the callback raised ChannelRequestFailed. The
560
- # callback should accept the channel as the first argument, and the
561
- # request-specific data as the second.
562
- def do_request(request, want_reply, data) #:nodoc:
563
- result = true
564
-
565
- begin
566
- callback = @on_request[request] or raise ChannelRequestFailed
567
- callback.call(self, data)
568
- rescue ChannelRequestFailed
569
- result = false
570
- end
571
-
572
- if want_reply
573
- msg = Buffer.from(:byte, result ? CHANNEL_SUCCESS : CHANNEL_FAILURE, :long, remote_id)
574
- connection.send_message(msg)
540
+ # Invoked when the server failed to open the channel. If an #on_open_failed
541
+ # callback was specified, it will be invoked with the channel, reason code,
542
+ # and description as arguments. Otherwise, a ChannelOpenFailed exception
543
+ # will be raised.
544
+ def do_open_failed(reason_code, description)
545
+ if @on_open_failed
546
+ @on_open_failed.call(self, reason_code, description)
547
+ else
548
+ raise ChannelOpenFailed.new(reason_code, description)
549
+ end
575
550
  end
576
- end
577
551
 
578
- # Invokes the #on_data callback when the server sends data to the
579
- # channel. This will reduce the available window size on the local end,
580
- # but does not actually throttle requests that come in illegally when
581
- # the window size is too small. The callback is invoked with the channel
582
- # as the first argument, and the data as the second.
583
- def do_data(data) #:nodoc:
584
- update_local_window_size(data.length)
585
- @on_data.call(self, data) if @on_data
586
- end
552
+ # Invoked when the server sends a CHANNEL_WINDOW_ADJUST packet, and
553
+ # causes the remote window size to be adjusted upwards by the given
554
+ # number of bytes. This has the effect of allowing more data to be sent
555
+ # from the local end to the remote end of the channel.
556
+ def do_window_adjust(bytes) # :nodoc:
557
+ @remote_maximum_window_size += bytes
558
+ @remote_window_size += bytes
559
+ end
587
560
 
588
- # Invokes the #on_extended_data callback when the server sends
589
- # extended data to the channel. This will reduce the available window
590
- # size on the local end. The callback is invoked with the channel,
591
- # type, and data.
592
- def do_extended_data(type, data)
593
- update_local_window_size(data.length)
594
- @on_extended_data.call(self, type, data) if @on_extended_data
595
- end
561
+ # Invoked when the server sends a channel request. If any #on_request
562
+ # callback has been registered for the specific type of this request,
563
+ # it is invoked. If +want_reply+ is true, a packet will be sent of
564
+ # either CHANNEL_SUCCESS or CHANNEL_FAILURE type. If there was no callback
565
+ # to handle the request, CHANNEL_FAILURE will be sent. Otherwise,
566
+ # CHANNEL_SUCCESS, unless the callback raised ChannelRequestFailed. The
567
+ # callback should accept the channel as the first argument, and the
568
+ # request-specific data as the second.
569
+ def do_request(request, want_reply, data) # :nodoc:
570
+ result = true
571
+
572
+ begin
573
+ callback = @on_request[request] or raise ChannelRequestFailed
574
+ callback.call(self, data)
575
+ rescue ChannelRequestFailed
576
+ result = false
577
+ end
596
578
 
597
- # Invokes the #on_eof callback when the server indicates that no
598
- # further data is forthcoming. The callback is invoked with the channel
599
- # as the argument.
600
- def do_eof
601
- @on_eof.call(self) if @on_eof
602
- end
579
+ if want_reply
580
+ msg = Buffer.from(:byte, result ? CHANNEL_SUCCESS : CHANNEL_FAILURE, :long, remote_id)
581
+ connection.send_message(msg)
582
+ end
583
+ end
603
584
 
604
- # Invokes the #on_close callback when the server closes a channel.
605
- # The channel is the only argument.
606
- def do_close
607
- @on_close.call(self) if @on_close
608
- end
585
+ # Invokes the #on_data callback when the server sends data to the
586
+ # channel. This will reduce the available window size on the local end,
587
+ # but does not actually throttle requests that come in illegally when
588
+ # the window size is too small. The callback is invoked with the channel
589
+ # as the first argument, and the data as the second.
590
+ def do_data(data) # :nodoc:
591
+ update_local_window_size(data.length)
592
+ @on_data.call(self, data) if @on_data
593
+ end
609
594
 
610
- # Invokes the next pending request callback with +false+ as the second
611
- # argument.
612
- def do_failure
613
- if callback = pending_requests.shift
614
- callback.call(self, false)
615
- else
616
- error { "channel failure recieved with no pending request to handle it (bug?)" }
595
+ # Invokes the #on_extended_data callback when the server sends
596
+ # extended data to the channel. This will reduce the available window
597
+ # size on the local end. The callback is invoked with the channel,
598
+ # type, and data.
599
+ def do_extended_data(type, data)
600
+ update_local_window_size(data.length)
601
+ @on_extended_data.call(self, type, data) if @on_extended_data
617
602
  end
618
- end
619
603
 
620
- # Invokes the next pending request callback with +true+ as the second
621
- # argument.
622
- def do_success
623
- if callback = pending_requests.shift
624
- callback.call(self, true)
625
- else
626
- error { "channel success recieved with no pending request to handle it (bug?)" }
604
+ # Invokes the #on_eof callback when the server indicates that no
605
+ # further data is forthcoming. The callback is invoked with the channel
606
+ # as the argument.
607
+ def do_eof
608
+ @on_eof.call(self) if @on_eof
627
609
  end
628
- end
629
610
 
630
- private
611
+ # Invokes the #on_close callback when the server closes a channel.
612
+ # The channel is the only argument.
613
+ def do_close
614
+ @on_close.call(self) if @on_close
615
+ end
631
616
 
632
- # Runs the SSH event loop until the remote confirmed channel open
633
- # experimental api
634
- def wait_until_open_confirmed
635
- connection.loop { !remote_id }
636
- end
617
+ # Invokes the next pending request callback with +false+ as the second
618
+ # argument.
619
+ def do_failure
620
+ if callback = pending_requests.shift
621
+ callback.call(self, false)
622
+ else
623
+ error { "channel failure received with no pending request to handle it (bug?)" }
624
+ end
625
+ end
637
626
 
638
- # Updates the local window size by the given amount. If the window
639
- # size drops to less than half of the local maximum (an arbitrary
640
- # threshold), a CHANNEL_WINDOW_ADJUST message will be sent to the
641
- # server telling it that the window size has grown.
642
- def update_local_window_size(size)
643
- @local_window_size -= size
644
- if local_window_size < local_maximum_window_size/2
645
- connection.send_message(Buffer.from(:byte, CHANNEL_WINDOW_ADJUST,
646
- :long, remote_id, :long, 0x20000))
647
- @local_window_size += 0x20000
648
- @local_maximum_window_size += 0x20000
627
+ # Invokes the next pending request callback with +true+ as the second
628
+ # argument.
629
+ def do_success
630
+ if callback = pending_requests.shift
631
+ callback.call(self, true)
632
+ else
633
+ error { "channel success received with no pending request to handle it (bug?)" }
634
+ end
649
635
  end
650
- end
651
636
 
652
- # Gets an +Array+ of local environment variables in the remote process'
653
- # environment.
654
- # A variable name can either be described by a +Regexp+ or +String+.
655
- #
656
- # channel.forward_local_env [/^GIT_.*$/, "LANG"]
657
- def forward_local_env(env_variable_patterns)
658
- Array(env_variable_patterns).each do |env_variable_pattern|
659
- matched_variables = ENV.find_all do |env_name, _|
660
- case env_variable_pattern
661
- when Regexp then env_name =~ env_variable_pattern
662
- when String then env_name == env_variable_pattern
637
+ private
638
+
639
+ # Runs the SSH event loop until the remote confirmed channel open
640
+ # experimental api
641
+ def wait_until_open_confirmed
642
+ connection.loop { !remote_id }
643
+ end
644
+
645
+ LOCAL_WINDOW_SIZE_INCREMENT = 0x20000
646
+ GOOD_LOCAL_MAXIUMUM_WINDOW_SIZE = 10 * LOCAL_WINDOW_SIZE_INCREMENT
647
+
648
+ # Updates the local window size by the given amount. If the window
649
+ # size drops to less than half of the local maximum (an arbitrary
650
+ # threshold), a CHANNEL_WINDOW_ADJUST message will be sent to the
651
+ # server telling it that the window size has grown.
652
+ def update_local_window_size(size)
653
+ @local_window_size -= size
654
+ if local_window_size < local_maximum_window_size / 2
655
+ connection.send_message(
656
+ Buffer.from(:byte, CHANNEL_WINDOW_ADJUST, :long, remote_id, :long, LOCAL_WINDOW_SIZE_INCREMENT)
657
+ )
658
+ @local_window_size += LOCAL_WINDOW_SIZE_INCREMENT
659
+
660
+ if @local_maximum_window_size < @local_window_size || @local_maximum_window_size < GOOD_LOCAL_MAXIUMUM_WINDOW_SIZE
661
+ @local_maximum_window_size += LOCAL_WINDOW_SIZE_INCREMENT
663
662
  end
664
663
  end
665
- matched_variables.each do |env_name, env_value|
666
- self.env(env_name, env_value)
664
+ end
665
+
666
+ # Gets an +Array+ of local environment variables in the remote process'
667
+ # environment.
668
+ # A variable name can either be described by a +Regexp+ or +String+.
669
+ #
670
+ # channel.forward_local_env [/^GIT_.*$/, "LANG"]
671
+ def forward_local_env(env_variable_patterns)
672
+ Array(env_variable_patterns).each do |env_variable_pattern|
673
+ matched_variables = ENV.find_all do |env_name, _|
674
+ case env_variable_pattern
675
+ when Regexp then env_name =~ env_variable_pattern
676
+ when String then env_name == env_variable_pattern
677
+ end
678
+ end
679
+ matched_variables.each do |env_name, env_value|
680
+ self.env(env_name, env_value)
681
+ end
667
682
  end
668
683
  end
684
+
685
+ # Set a +Hash+ of environment variables in the remote process' environment.
686
+ #
687
+ # channel.set_remote_env foo: 'bar', baz: 'whale'
688
+ def set_remote_env(env)
689
+ env.each { |key, value| self.env(key, value) }
690
+ end
669
691
  end
692
+ end
670
693
  end
671
-
672
- end; end; end
694
+ end