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
@@ -2,649 +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 = 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
- # Returns true if the channel is currently closing, but not actually
273
- # closed. A channel is closing when, for instance, #close has been
274
- # invoked, but the server has not yet responded with a CHANNEL_CLOSE
275
- # packet of its own.
276
- def closing?
277
- @closing
278
- 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
279
283
 
280
- # Requests that the channel be closed. If the channel is already closing,
281
- # this does nothing, nor does it do anything if the channel has not yet
282
- # been confirmed open (see #do_open_confirmation). Otherwise, it sends a
283
- # CHANNEL_CLOSE message and marks the channel as closing.
284
- def close
285
- return if @closing
286
- if remote_id
287
- @closing = true
288
- connection.send_message(Buffer.from(:byte, CHANNEL_CLOSE, :long, remote_id))
289
- end
290
- end
284
+ # True if we have sent CHANNEL_CLOSE to the remote server.
285
+ def local_closed?
286
+ @local_closed
287
+ end
291
288
 
292
- # Returns true if the local end of the channel has declared that no more
293
- # data is forthcoming (see #eof!). Trying to send data via #send_data when
294
- # this is true will result in an exception being raised.
295
- def eof?
296
- @eof
297
- end
289
+ def remote_closed?
290
+ @remote_closed
291
+ end
298
292
 
299
- # Tells the remote end of the channel that no more data is forthcoming
300
- # from this end of the channel. The remote end may still send data.
301
- # The CHANNEL_EOF packet will be sent once the output buffer is empty.
302
- def eof!
303
- return if eof?
304
- @eof = true
305
- end
293
+ def remote_closed!
294
+ @remote_closed = true
295
+ end
306
296
 
307
- # If an #on_process handler has been set up, this will cause it to be
308
- # invoked (passing the channel itself as an argument). It also causes all
309
- # pending output to be enqueued as CHANNEL_DATA packets (see #enqueue_pending_output).
310
- def process
311
- @on_process.call(self) if @on_process
312
- enqueue_pending_output
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
313
301
 
314
- if @eof and not @sent_eof and output.empty? and remote_id
315
- connection.send_message(Buffer.from(:byte, CHANNEL_EOF, :long, remote_id))
316
- @sent_eof = true
317
- end
318
- end
302
+ @closing = true
303
+ end
319
304
 
320
- # Registers a callback to be invoked when data packets are received by the
321
- # channel. The callback is called with the channel as the first argument,
322
- # and the data as the second.
323
- #
324
- # channel.on_data do |ch, data|
325
- # puts "got data: #{data.inspect}"
326
- # end
327
- #
328
- # Data received this way is typically the data written by the remote
329
- # process to its +stdout+ stream.
330
- def on_data(&block)
331
- old, @on_data = @on_data, block
332
- old
333
- 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
334
311
 
335
- # Registers a callback to be invoked when extended data packets are received
336
- # by the channel. The callback is called with the channel as the first
337
- # argument, the data type (as an integer) as the second, and the data as
338
- # the third. Extended data is almost exclusively used to send +stderr+ data
339
- # (+type+ == 1). Other extended data types are not defined by the SSH
340
- # protocol.
341
- #
342
- # channel.on_extended_data do |ch, type, data|
343
- # puts "got stderr: #{data.inspect}"
344
- # end
345
- def on_extended_data(&block)
346
- old, @on_extended_data = @on_extended_data, block
347
- old
348
- end
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?
349
317
 
350
- # Registers a callback to be invoked for each pass of the event loop for
351
- # this channel. There are no guarantees on timeliness in the event loop,
352
- # but it will be called roughly once for each packet received by the
353
- # connection (not the channel). This callback is invoked with the channel
354
- # as the sole argument.
355
- #
356
- # Here's an example that accumulates the channel data into a variable on
357
- # the channel itself, and displays individual lines in the input one
358
- # at a time when the channel is processed:
359
- #
360
- # channel[:data] = ""
361
- #
362
- # channel.on_data do |ch, data|
363
- # channel[:data] << data
364
- # end
365
- #
366
- # channel.on_process do |ch|
367
- # if channel[:data] =~ /^.*?\n/
368
- # puts $&
369
- # channel[:data] = $'
370
- # end
371
- # end
372
- def on_process(&block)
373
- old, @on_process = @on_process, block
374
- old
375
- end
318
+ @eof = true
319
+ end
376
320
 
377
- # Registers a callback to be invoked when the server acknowledges that a
378
- # channel is closed. This is invoked with the channel as the sole argument.
379
- #
380
- # channel.on_close do |ch|
381
- # puts "remote end is closing!"
382
- # end
383
- def on_close(&block)
384
- old, @on_close = @on_close, block
385
- old
386
- 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
387
327
 
388
- # Registers a callback to be invoked when the server indicates that no more
389
- # data will be sent to the channel (although the channel can still send
390
- # data to the server). The channel is the sole argument to the callback.
391
- #
392
- # channel.on_eof do |ch|
393
- # puts "remote end is done sending data"
394
- # end
395
- def on_eof(&block)
396
- old, @on_eof = @on_eof, block
397
- old
398
- 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
399
332
 
400
- # Registers a callback to be invoked when the server was unable to open
401
- # the requested channel. The channel itself will be passed to the block,
402
- # along with the integer "reason code" for the failure, and a textual
403
- # description of the failure from the server.
404
- #
405
- # channel = session.open_channel do |ch|
406
- # # ..
407
- # end
408
- #
409
- # channel.on_open_failed { |ch, code, desc| ... }
410
- def on_open_failed(&block)
411
- old, @on_open_failed = @on_open_failed, block
412
- old
413
- 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
414
339
 
415
- # Registers a callback to be invoked when a channel request of the given
416
- # type is received. The callback will receive the channel as the first
417
- # argument, and the associated (unparsed) data as the second. The data
418
- # will be a Net::SSH::Buffer that you will need to parse, yourself,
419
- # according to the kind of request you are watching.
420
- #
421
- # By default, if the request wants a reply, Net::SSH will send a
422
- # CHANNEL_SUCCESS response for any request that was handled by a registered
423
- # callback, and CHANNEL_FAILURE for any that wasn't, but if you want your
424
- # registered callback to result in a CHANNEL_FAILURE response, just raise
425
- # Net::SSH::ChannelRequestFailed.
426
- #
427
- # Some common channel requests that your programs might want to listen
428
- # for are:
429
- #
430
- # * "exit-status" : the exit status of the remote process will be reported
431
- # as a long integer in the data buffer, which you can grab via
432
- # data.read_long.
433
- # * "exit-signal" : if the remote process died as a result of a signal
434
- # being sent to it, the signal will be reported as a string in the
435
- # data, via data.read_string. (Not all SSH servers support this channel
436
- # request type.)
437
- #
438
- # channel.on_request "exit-status" do |ch, data|
439
- # puts "process terminated with exit status: #{data.read_long}"
440
- # end
441
- def on_request(type, &block)
442
- old, @on_request[type] = @on_request[type], block
443
- old
444
- 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
445
354
 
446
- # Sends a new channel request with the given name. The extra +data+
447
- # parameter must either be empty, or consist of an even number of
448
- # arguments. See Net::SSH::Buffer.from for a description of their format.
449
- # If a block is given, it is registered as a callback for a pending
450
- # request, and the packet will be flagged so that the server knows a
451
- # reply is required. If no block is given, the server will send no
452
- # response to this request. Responses, where required, will cause the
453
- # callback to be invoked with the channel as the first argument, and
454
- # either true or false as the second, depending on whether the request
455
- # succeeded or not. The meaning of "success" and "failure" in this context
456
- # is dependent on the specific request that was sent.
457
- #
458
- # channel.send_channel_request "shell" do |ch, success|
459
- # if success
460
- # puts "user shell started successfully"
461
- # else
462
- # puts "could not start user shell"
463
- # end
464
- # end
465
- #
466
- # Most channel requests you'll want to send are already wrapped in more
467
- # convenient helper methods (see #exec and #subsystem).
468
- def send_channel_request(request_name, *data, &callback)
469
- info { "sending channel request #{request_name.inspect}" }
470
- msg = Buffer.from(:byte, CHANNEL_REQUEST,
471
- :long, remote_id, :string, request_name,
472
- :bool, !callback.nil?, *data)
473
- connection.send_message(msg)
474
- pending_requests << callback if callback
475
- 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
476
369
 
477
- public # these methods are public, but for Net::SSH internal use only
478
-
479
- # Enqueues pending output at the connection as CHANNEL_DATA packets. This
480
- # does nothing if the channel has not yet been confirmed open (see
481
- # #do_open_confirmation). This is called automatically by #process, which
482
- # is called from the event loop (Connection::Session#process). You will
483
- # generally not need to invoke it directly.
484
- def enqueue_pending_output #:nodoc:
485
- return unless remote_id
486
-
487
- while output.length > 0
488
- length = output.length
489
- length = remote_window_size if length > remote_window_size
490
- length = remote_maximum_packet_size if length > remote_maximum_packet_size
491
-
492
- if length > 0
493
- connection.send_message(Buffer.from(:byte, CHANNEL_DATA, :long, remote_id, :string, output.read(length)))
494
- output.consume!
495
- @remote_window_size -= length
496
- else
497
- break
498
- 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
499
395
  end
500
- end
501
396
 
502
- # Invoked when the server confirms that a channel has been opened.
503
- # The remote_id is the id of the channel as assigned by the remote host,
504
- # and max_window and max_packet are the maximum window and maximum
505
- # packet sizes, respectively. If an open-confirmation callback was
506
- # given when the channel was created, it is invoked at this time with
507
- # the channel itself as the sole argument.
508
- def do_open_confirmation(remote_id, max_window, max_packet) #:nodoc:
509
- @remote_id = remote_id
510
- @remote_window_size = @remote_maximum_window_size = max_window
511
- @remote_maximum_packet_size = max_packet
512
- connection.forward.agent(self) if connection.options[:forward_agent] && type == "session"
513
- forward_local_env(connection.options[:send_env]) if connection.options[:send_env]
514
- @on_confirm_open.call(self) if @on_confirm_open
515
- 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
516
407
 
517
- # Invoked when the server failed to open the channel. If an #on_open_failed
518
- # callback was specified, it will be invoked with the channel, reason code,
519
- # and description as arguments. Otherwise, a ChannelOpenFailed exception
520
- # will be raised.
521
- def do_open_failed(reason_code, description)
522
- if @on_open_failed
523
- @on_open_failed.call(self, reason_code, description)
524
- else
525
- raise ChannelOpenFailed.new(reason_code, description)
526
- end
527
- 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
528
419
 
529
- # Invoked when the server sends a CHANNEL_WINDOW_ADJUST packet, and
530
- # causes the remote window size to be adjusted upwards by the given
531
- # number of bytes. This has the effect of allowing more data to be sent
532
- # from the local end to the remote end of the channel.
533
- def do_window_adjust(bytes) #:nodoc:
534
- @remote_maximum_window_size += bytes
535
- @remote_window_size += bytes
536
- 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
537
434
 
538
- # Invoked when the server sends a channel request. If any #on_request
539
- # callback has been registered for the specific type of this request,
540
- # it is invoked. If +want_reply+ is true, a packet will be sent of
541
- # either CHANNEL_SUCCESS or CHANNEL_FAILURE type. If there was no callback
542
- # to handle the request, CHANNEL_FAILURE will be sent. Otherwise,
543
- # CHANNEL_SUCCESS, unless the callback raised ChannelRequestFailed. The
544
- # callback should accept the channel as the first argument, and the
545
- # request-specific data as the second.
546
- def do_request(request, want_reply, data) #:nodoc:
547
- result = true
548
-
549
- begin
550
- callback = @on_request[request] or raise ChannelRequestFailed
551
- callback.call(self, data)
552
- rescue ChannelRequestFailed
553
- result = false
554
- end
555
-
556
- if want_reply
557
- msg = Buffer.from(:byte, result ? CHANNEL_SUCCESS : CHANNEL_FAILURE, :long, remote_id)
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
464
+ end
465
+
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)
558
495
  connection.send_message(msg)
496
+ pending_requests << callback if callback
559
497
  end
560
- end
561
498
 
562
- # Invokes the #on_data callback when the server sends data to the
563
- # channel. This will reduce the available window size on the local end,
564
- # but does not actually throttle requests that come in illegally when
565
- # the window size is too small. The callback is invoked with the channel
566
- # as the first argument, and the data as the second.
567
- def do_data(data) #:nodoc:
568
- update_local_window_size(data.length)
569
- @on_data.call(self, data) if @on_data
570
- end
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
522
+ end
571
523
 
572
- # Invokes the #on_extended_data callback when the server sends
573
- # extended data to the channel. This will reduce the available window
574
- # size on the local end. The callback is invoked with the channel,
575
- # type, and data.
576
- def do_extended_data(type, data)
577
- update_local_window_size(data.length)
578
- @on_extended_data.call(self, type, data) if @on_extended_data
579
- 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
580
539
 
581
- # Invokes the #on_eof callback when the server indicates that no
582
- # further data is forthcoming. The callback is invoked with the channel
583
- # as the argument.
584
- def do_eof
585
- @on_eof.call(self) if @on_eof
586
- end
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
550
+ end
587
551
 
588
- # Invokes the #on_close callback when the server closes a channel.
589
- # The channel is the only argument.
590
- def do_close
591
- @on_close.call(self) if @on_close
592
- 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
560
+
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
593
578
 
594
- # Invokes the next pending request callback with +false+ as the second
595
- # argument.
596
- def do_failure
597
- if callback = pending_requests.shift
598
- callback.call(self, false)
599
- else
600
- error { "channel failure recieved with no pending request to handle it (bug?)" }
579
+ if want_reply
580
+ msg = Buffer.from(:byte, result ? CHANNEL_SUCCESS : CHANNEL_FAILURE, :long, remote_id)
581
+ connection.send_message(msg)
582
+ end
601
583
  end
602
- end
603
584
 
604
- # Invokes the next pending request callback with +true+ as the second
605
- # argument.
606
- def do_success
607
- if callback = pending_requests.shift
608
- callback.call(self, true)
609
- else
610
- error { "channel success recieved with no pending request to handle it (bug?)" }
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
611
593
  end
612
- end
613
594
 
614
- private
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
602
+ end
615
603
 
616
- # Updates the local window size by the given amount. If the window
617
- # size drops to less than half of the local maximum (an arbitrary
618
- # threshold), a CHANNEL_WINDOW_ADJUST message will be sent to the
619
- # server telling it that the window size has grown.
620
- def update_local_window_size(size)
621
- @local_window_size -= size
622
- if local_window_size < local_maximum_window_size/2
623
- connection.send_message(Buffer.from(:byte, CHANNEL_WINDOW_ADJUST,
624
- :long, remote_id, :long, 0x20000))
625
- @local_window_size += 0x20000
626
- @local_maximum_window_size += 0x20000
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
- # Gets an +Array+ of local environment variables in the remote process'
631
- # environment.
632
- # A variable name can either be described by a +Regexp+ or +String+.
633
- #
634
- # channel.forward_local_env [/^GIT_.*$/, "LANG"]
635
- def forward_local_env(env_variable_patterns)
636
- Array(env_variable_patterns).each do |env_variable_pattern|
637
- matched_variables = ENV.find_all do |env_name, _|
638
- case env_variable_pattern
639
- when Regexp then env_name =~ env_variable_pattern
640
- when String then env_name == env_variable_pattern
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
616
+
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
626
+
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
635
+ end
636
+
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
641
662
  end
642
663
  end
643
- matched_variables.each do |env_name, env_value|
644
- 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
645
682
  end
646
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
647
691
  end
692
+ end
648
693
  end
649
-
650
- end; end; end
694
+ end