net-ssh 1.1.4 → 2.0.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 (297) hide show
  1. data/CHANGELOG.rdoc +37 -0
  2. data/Manifest +101 -0
  3. data/README.rdoc +110 -0
  4. data/Rakefile +26 -0
  5. data/{THANKS → THANKS.rdoc} +2 -5
  6. data/lib/net/ssh.rb +189 -57
  7. data/lib/net/ssh/authentication/agent.rb +175 -0
  8. data/lib/net/ssh/authentication/constants.rb +18 -0
  9. data/lib/net/ssh/authentication/key_manager.rb +166 -0
  10. data/lib/net/ssh/authentication/methods/abstract.rb +60 -0
  11. data/lib/net/ssh/authentication/methods/hostbased.rb +71 -0
  12. data/lib/net/ssh/authentication/methods/keyboard_interactive.rb +66 -0
  13. data/lib/net/ssh/authentication/methods/password.rb +39 -0
  14. data/lib/net/ssh/authentication/methods/publickey.rb +92 -0
  15. data/lib/net/ssh/authentication/pageant.rb +176 -0
  16. data/lib/net/ssh/authentication/session.rb +116 -0
  17. data/lib/net/ssh/buffer.rb +339 -0
  18. data/lib/net/ssh/buffered_io.rb +149 -0
  19. data/lib/net/ssh/config.rb +173 -0
  20. data/lib/net/ssh/connection/channel.rb +575 -454
  21. data/lib/net/ssh/connection/constants.rb +31 -45
  22. data/lib/net/ssh/connection/session.rb +569 -0
  23. data/lib/net/ssh/connection/term.rb +176 -88
  24. data/lib/net/ssh/errors.rb +83 -61
  25. data/lib/net/ssh/key_factory.rb +85 -0
  26. data/lib/net/ssh/known_hosts.rb +129 -0
  27. data/lib/net/ssh/loggable.rb +61 -0
  28. data/lib/net/ssh/packet.rb +102 -0
  29. data/lib/net/ssh/prompt.rb +93 -0
  30. data/lib/net/ssh/proxy/errors.rb +8 -28
  31. data/lib/net/ssh/proxy/http.rb +75 -107
  32. data/lib/net/ssh/proxy/socks4.rb +35 -48
  33. data/lib/net/ssh/proxy/socks5.rb +76 -108
  34. data/lib/net/ssh/service/forward.rb +267 -0
  35. data/lib/net/ssh/test.rb +89 -0
  36. data/lib/net/ssh/test/channel.rb +129 -0
  37. data/lib/net/ssh/test/extensions.rb +152 -0
  38. data/lib/net/ssh/test/kex.rb +44 -0
  39. data/lib/net/ssh/test/local_packet.rb +51 -0
  40. data/lib/net/ssh/test/packet.rb +81 -0
  41. data/lib/net/ssh/test/remote_packet.rb +38 -0
  42. data/lib/net/ssh/test/script.rb +157 -0
  43. data/lib/net/ssh/test/socket.rb +59 -0
  44. data/lib/net/ssh/transport/algorithms.rb +384 -0
  45. data/lib/net/ssh/transport/cipher_factory.rb +72 -0
  46. data/lib/net/ssh/transport/constants.rb +22 -58
  47. data/lib/net/ssh/transport/hmac.rb +31 -0
  48. data/lib/net/ssh/transport/hmac/abstract.rb +48 -0
  49. data/lib/net/ssh/transport/hmac/md5.rb +12 -0
  50. data/lib/net/ssh/transport/hmac/md5_96.rb +11 -0
  51. data/lib/net/ssh/transport/hmac/none.rb +15 -0
  52. data/lib/net/ssh/transport/hmac/sha1.rb +13 -0
  53. data/lib/net/ssh/transport/hmac/sha1_96.rb +11 -0
  54. data/lib/net/ssh/transport/identity_cipher.rb +40 -0
  55. data/lib/net/ssh/transport/kex.rb +13 -0
  56. data/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb +208 -0
  57. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb +77 -0
  58. data/lib/net/ssh/{util → transport}/openssl.rb +22 -40
  59. data/lib/net/ssh/transport/packet_stream.rb +230 -0
  60. data/lib/net/ssh/transport/server_version.rb +61 -0
  61. data/lib/net/ssh/transport/session.rb +225 -303
  62. data/lib/net/ssh/transport/state.rb +170 -0
  63. data/lib/net/ssh/verifiers/lenient.rb +30 -0
  64. data/lib/net/ssh/verifiers/null.rb +12 -0
  65. data/lib/net/ssh/verifiers/strict.rb +53 -0
  66. data/lib/net/ssh/version.rb +57 -26
  67. data/net-ssh.gemspec +54 -0
  68. data/setup.rb +1585 -0
  69. data/test/authentication/methods/common.rb +28 -0
  70. data/test/authentication/methods/test_abstract.rb +51 -0
  71. data/test/authentication/methods/test_hostbased.rb +108 -0
  72. data/test/authentication/methods/test_keyboard_interactive.rb +98 -0
  73. data/test/authentication/methods/test_password.rb +50 -0
  74. data/test/authentication/methods/test_publickey.rb +123 -0
  75. data/test/authentication/test_agent.rb +205 -0
  76. data/test/authentication/test_key_manager.rb +100 -0
  77. data/test/authentication/test_session.rb +93 -0
  78. data/test/common.rb +106 -0
  79. data/test/configs/exact_match +8 -0
  80. data/test/configs/wild_cards +14 -0
  81. data/test/connection/test_channel.rb +452 -0
  82. data/test/connection/test_session.rb +483 -0
  83. data/test/test_all.rb +6 -0
  84. data/test/test_buffer.rb +336 -0
  85. data/test/test_buffered_io.rb +63 -0
  86. data/test/test_config.rb +78 -0
  87. data/test/test_key_factory.rb +67 -0
  88. data/test/transport/hmac/test_md5.rb +34 -0
  89. data/test/transport/hmac/test_md5_96.rb +25 -0
  90. data/test/transport/hmac/test_none.rb +34 -0
  91. data/test/transport/hmac/test_sha1.rb +34 -0
  92. data/test/transport/hmac/test_sha1_96.rb +25 -0
  93. data/test/transport/kex/test_diffie_hellman_group1_sha1.rb +146 -0
  94. data/test/transport/kex/test_diffie_hellman_group_exchange_sha1.rb +92 -0
  95. data/test/transport/test_algorithms.rb +302 -0
  96. data/test/transport/test_cipher_factory.rb +163 -0
  97. data/test/transport/test_hmac.rb +34 -0
  98. data/test/transport/test_identity_cipher.rb +40 -0
  99. data/test/transport/test_packet_stream.rb +433 -0
  100. data/test/transport/test_server_version.rb +55 -0
  101. data/test/transport/test_session.rb +312 -0
  102. data/test/transport/test_state.rb +173 -0
  103. metadata +102 -253
  104. data/ChangeLog +0 -560
  105. data/LICENSE +0 -7
  106. data/NEWS +0 -152
  107. data/README +0 -14
  108. data/bin/rb-keygen +0 -210
  109. data/doc/LICENSE-BSD +0 -27
  110. data/doc/LICENSE-GPL +0 -280
  111. data/doc/LICENSE-RUBY +0 -56
  112. data/doc/manual-html/chapter-1.html +0 -388
  113. data/doc/manual-html/chapter-2.html +0 -552
  114. data/doc/manual-html/chapter-3.html +0 -470
  115. data/doc/manual-html/chapter-4.html +0 -413
  116. data/doc/manual-html/chapter-5.html +0 -525
  117. data/doc/manual-html/chapter-6.html +0 -456
  118. data/doc/manual-html/chapter-7.html +0 -343
  119. data/doc/manual-html/index.html +0 -235
  120. data/doc/manual-html/stylesheets/manual.css +0 -270
  121. data/doc/manual-html/stylesheets/ruby.css +0 -17
  122. data/doc/manual/chapter.erb +0 -38
  123. data/doc/manual/example.erb +0 -18
  124. data/doc/manual/index.erb +0 -29
  125. data/doc/manual/manual.rb +0 -311
  126. data/doc/manual/manual.yml +0 -73
  127. data/doc/manual/page.erb +0 -87
  128. data/doc/manual/parts/0000.txt +0 -5
  129. data/doc/manual/parts/0001.txt +0 -3
  130. data/doc/manual/parts/0002.txt +0 -40
  131. data/doc/manual/parts/0003.txt +0 -6
  132. data/doc/manual/parts/0004.txt +0 -7
  133. data/doc/manual/parts/0005.txt +0 -1
  134. data/doc/manual/parts/0006.txt +0 -49
  135. data/doc/manual/parts/0007.txt +0 -67
  136. data/doc/manual/parts/0008.txt +0 -43
  137. data/doc/manual/parts/0009.txt +0 -14
  138. data/doc/manual/parts/0010.txt +0 -7
  139. data/doc/manual/parts/0011.txt +0 -14
  140. data/doc/manual/parts/0012.txt +0 -3
  141. data/doc/manual/parts/0013.txt +0 -20
  142. data/doc/manual/parts/0014.txt +0 -32
  143. data/doc/manual/parts/0015.txt +0 -14
  144. data/doc/manual/parts/0016.txt +0 -28
  145. data/doc/manual/parts/0017.txt +0 -50
  146. data/doc/manual/parts/0018.txt +0 -35
  147. data/doc/manual/parts/0019.txt +0 -7
  148. data/doc/manual/parts/0020.txt +0 -72
  149. data/doc/manual/parts/0021.txt +0 -50
  150. data/doc/manual/parts/0022.txt +0 -42
  151. data/doc/manual/parts/0023.txt +0 -51
  152. data/doc/manual/parts/0024.txt +0 -18
  153. data/doc/manual/parts/0025.txt +0 -18
  154. data/doc/manual/parts/0026.txt +0 -15
  155. data/doc/manual/parts/0027.txt +0 -37
  156. data/doc/manual/parts/0028.txt +0 -16
  157. data/doc/manual/parts/0029.txt +0 -1
  158. data/doc/manual/parts/0030.txt +0 -52
  159. data/doc/manual/parts/0031.txt +0 -25
  160. data/doc/manual/stylesheets/manual.css +0 -270
  161. data/doc/manual/stylesheets/ruby.css +0 -17
  162. data/doc/manual/tutorial.erb +0 -30
  163. data/examples/auth-forward.rb +0 -41
  164. data/examples/channel-demo.rb +0 -81
  165. data/examples/port-forward.rb +0 -51
  166. data/examples/process-demo.rb +0 -91
  167. data/examples/remote-net-port-forward.rb +0 -45
  168. data/examples/remote-port-forward.rb +0 -80
  169. data/examples/shell-demo.rb +0 -46
  170. data/examples/ssh-client.rb +0 -67
  171. data/examples/sync-shell-demo.rb +0 -69
  172. data/examples/tail-demo.rb +0 -49
  173. data/lib/net/ssh/connection/driver.rb +0 -446
  174. data/lib/net/ssh/connection/services.rb +0 -72
  175. data/lib/net/ssh/host-key-verifier.rb +0 -52
  176. data/lib/net/ssh/known-hosts.rb +0 -96
  177. data/lib/net/ssh/lenient-host-key-verifier.rb +0 -25
  178. data/lib/net/ssh/null-host-key-verifier.rb +0 -14
  179. data/lib/net/ssh/service/agentforward/driver.rb +0 -78
  180. data/lib/net/ssh/service/agentforward/services.rb +0 -41
  181. data/lib/net/ssh/service/forward/driver.rb +0 -319
  182. data/lib/net/ssh/service/forward/local-network-handler.rb +0 -71
  183. data/lib/net/ssh/service/forward/remote-network-handler.rb +0 -83
  184. data/lib/net/ssh/service/forward/services.rb +0 -76
  185. data/lib/net/ssh/service/process/driver.rb +0 -153
  186. data/lib/net/ssh/service/process/open.rb +0 -193
  187. data/lib/net/ssh/service/process/popen3.rb +0 -178
  188. data/lib/net/ssh/service/process/services.rb +0 -66
  189. data/lib/net/ssh/service/services.rb +0 -60
  190. data/lib/net/ssh/service/shell/driver.rb +0 -86
  191. data/lib/net/ssh/service/shell/services.rb +0 -54
  192. data/lib/net/ssh/service/shell/shell.rb +0 -222
  193. data/lib/net/ssh/service/shell/sync.rb +0 -114
  194. data/lib/net/ssh/session.rb +0 -305
  195. data/lib/net/ssh/transport/algorithm-negotiator.rb +0 -275
  196. data/lib/net/ssh/transport/compress/compressor.rb +0 -53
  197. data/lib/net/ssh/transport/compress/decompressor.rb +0 -53
  198. data/lib/net/ssh/transport/compress/none-compressor.rb +0 -39
  199. data/lib/net/ssh/transport/compress/none-decompressor.rb +0 -39
  200. data/lib/net/ssh/transport/compress/services.rb +0 -68
  201. data/lib/net/ssh/transport/compress/zlib-compressor.rb +0 -60
  202. data/lib/net/ssh/transport/compress/zlib-decompressor.rb +0 -52
  203. data/lib/net/ssh/transport/errors.rb +0 -47
  204. data/lib/net/ssh/transport/identity-cipher.rb +0 -61
  205. data/lib/net/ssh/transport/kex/dh-gex.rb +0 -106
  206. data/lib/net/ssh/transport/kex/dh.rb +0 -249
  207. data/lib/net/ssh/transport/kex/services.rb +0 -62
  208. data/lib/net/ssh/transport/ossl/buffer-factory.rb +0 -52
  209. data/lib/net/ssh/transport/ossl/buffer.rb +0 -87
  210. data/lib/net/ssh/transport/ossl/cipher-factory.rb +0 -98
  211. data/lib/net/ssh/transport/ossl/digest-factory.rb +0 -51
  212. data/lib/net/ssh/transport/ossl/hmac-factory.rb +0 -71
  213. data/lib/net/ssh/transport/ossl/hmac/hmac.rb +0 -62
  214. data/lib/net/ssh/transport/ossl/hmac/md5-96.rb +0 -44
  215. data/lib/net/ssh/transport/ossl/hmac/md5.rb +0 -46
  216. data/lib/net/ssh/transport/ossl/hmac/none.rb +0 -46
  217. data/lib/net/ssh/transport/ossl/hmac/services.rb +0 -68
  218. data/lib/net/ssh/transport/ossl/hmac/sha1-96.rb +0 -44
  219. data/lib/net/ssh/transport/ossl/hmac/sha1.rb +0 -45
  220. data/lib/net/ssh/transport/ossl/key-factory.rb +0 -116
  221. data/lib/net/ssh/transport/ossl/services.rb +0 -149
  222. data/lib/net/ssh/transport/packet-stream.rb +0 -236
  223. data/lib/net/ssh/transport/services.rb +0 -146
  224. data/lib/net/ssh/transport/version-negotiator.rb +0 -73
  225. data/lib/net/ssh/userauth/agent.rb +0 -222
  226. data/lib/net/ssh/userauth/constants.rb +0 -35
  227. data/lib/net/ssh/userauth/driver.rb +0 -183
  228. data/lib/net/ssh/userauth/methods/hostbased.rb +0 -119
  229. data/lib/net/ssh/userauth/methods/keyboard-interactive.rb +0 -104
  230. data/lib/net/ssh/userauth/methods/password.rb +0 -70
  231. data/lib/net/ssh/userauth/methods/publickey.rb +0 -137
  232. data/lib/net/ssh/userauth/methods/services.rb +0 -90
  233. data/lib/net/ssh/userauth/pageant.rb +0 -197
  234. data/lib/net/ssh/userauth/services.rb +0 -141
  235. data/lib/net/ssh/userauth/userkeys.rb +0 -258
  236. data/lib/net/ssh/util/buffer.rb +0 -274
  237. data/lib/net/ssh/util/prompter.rb +0 -73
  238. data/test/ALL-TESTS.rb +0 -18
  239. data/test/connection/tc_channel.rb +0 -136
  240. data/test/connection/tc_driver.rb +0 -287
  241. data/test/connection/tc_integration.rb +0 -87
  242. data/test/proxy/tc_http.rb +0 -209
  243. data/test/proxy/tc_socks4.rb +0 -148
  244. data/test/proxy/tc_socks5.rb +0 -214
  245. data/test/service/agentforward/tc_driver.rb +0 -138
  246. data/test/service/forward/tc_driver.rb +0 -289
  247. data/test/service/forward/tc_local_network_handler.rb +0 -123
  248. data/test/service/forward/tc_remote_network_handler.rb +0 -111
  249. data/test/service/process/tc_driver.rb +0 -79
  250. data/test/service/process/tc_integration.rb +0 -119
  251. data/test/service/process/tc_open.rb +0 -179
  252. data/test/service/process/tc_popen3.rb +0 -164
  253. data/test/tc_integration.rb +0 -80
  254. data/test/transport/compress/tc_none_compress.rb +0 -41
  255. data/test/transport/compress/tc_none_decompress.rb +0 -45
  256. data/test/transport/compress/tc_zlib_compress.rb +0 -61
  257. data/test/transport/compress/tc_zlib_decompress.rb +0 -48
  258. data/test/transport/kex/tc_dh.rb +0 -312
  259. data/test/transport/kex/tc_dh_gex.rb +0 -71
  260. data/test/transport/ossl/fixtures/dsa-encrypted +0 -15
  261. data/test/transport/ossl/fixtures/dsa-encrypted-bad +0 -15
  262. data/test/transport/ossl/fixtures/dsa-unencrypted +0 -12
  263. data/test/transport/ossl/fixtures/dsa-unencrypted-bad +0 -12
  264. data/test/transport/ossl/fixtures/dsa-unencrypted.pub +0 -1
  265. data/test/transport/ossl/fixtures/not-a-private-key +0 -4
  266. data/test/transport/ossl/fixtures/not-supported +0 -2
  267. data/test/transport/ossl/fixtures/rsa-encrypted +0 -18
  268. data/test/transport/ossl/fixtures/rsa-encrypted-bad +0 -18
  269. data/test/transport/ossl/fixtures/rsa-unencrypted +0 -15
  270. data/test/transport/ossl/fixtures/rsa-unencrypted-bad +0 -15
  271. data/test/transport/ossl/fixtures/rsa-unencrypted.pub +0 -1
  272. data/test/transport/ossl/hmac/tc_hmac.rb +0 -58
  273. data/test/transport/ossl/hmac/tc_md5.rb +0 -50
  274. data/test/transport/ossl/hmac/tc_md5_96.rb +0 -50
  275. data/test/transport/ossl/hmac/tc_none.rb +0 -50
  276. data/test/transport/ossl/hmac/tc_sha1.rb +0 -50
  277. data/test/transport/ossl/hmac/tc_sha1_96.rb +0 -50
  278. data/test/transport/ossl/tc_buffer.rb +0 -97
  279. data/test/transport/ossl/tc_buffer_factory.rb +0 -67
  280. data/test/transport/ossl/tc_cipher_factory.rb +0 -84
  281. data/test/transport/ossl/tc_digest_factory.rb +0 -39
  282. data/test/transport/ossl/tc_hmac_factory.rb +0 -72
  283. data/test/transport/ossl/tc_key_factory.rb +0 -199
  284. data/test/transport/tc_algorithm_negotiator.rb +0 -170
  285. data/test/transport/tc_identity_cipher.rb +0 -52
  286. data/test/transport/tc_integration.rb +0 -115
  287. data/test/transport/tc_packet_stream.rb +0 -184
  288. data/test/transport/tc_session.rb +0 -296
  289. data/test/transport/tc_version_negotiator.rb +0 -86
  290. data/test/userauth/methods/tc_hostbased.rb +0 -136
  291. data/test/userauth/methods/tc_password.rb +0 -89
  292. data/test/userauth/methods/tc_publickey.rb +0 -167
  293. data/test/userauth/tc_agent.rb +0 -223
  294. data/test/userauth/tc_driver.rb +0 -190
  295. data/test/userauth/tc_integration.rb +0 -97
  296. data/test/userauth/tc_userkeys.rb +0 -265
  297. data/test/util/tc_buffer.rb +0 -217
@@ -0,0 +1,173 @@
1
+ module Net; module SSH
2
+
3
+ # The Net::SSH::Config class is used to parse OpenSSH configuration files,
4
+ # and translates that syntax into the configuration syntax that Net::SSH
5
+ # understands. This lets Net::SSH scripts read their configuration (to
6
+ # some extent) from OpenSSH configuration files (~/.ssh/config, /etc/ssh_config,
7
+ # and so forth).
8
+ #
9
+ # Only a subset of OpenSSH configuration options are understood:
10
+ #
11
+ # * Ciphers => maps to the :encryption option
12
+ # * Compression => :compression
13
+ # * CompressionLevel => :compression_level
14
+ # * ConnectTimeout => maps to the :timeout option
15
+ # * ForwardAgent => :forward_agent
16
+ # * GlobalKnownHostsFile => :global_known_hosts_file
17
+ # * HostBasedAuthentication => maps to the :auth_methods option
18
+ # * HostKeyAlgorithms => maps to :host_key option
19
+ # * HostKeyAlias => :host_key_alias
20
+ # * HostName => :host_name
21
+ # * IdentityFile => maps to the :keys option
22
+ # * Macs => maps to the :hmac option
23
+ # * PasswordAuthentication => maps to the :auth_methods option
24
+ # * Port => :port
25
+ # * PreferredAuthentications => maps to the :auth_methods option
26
+ # * RekeyLimit => :rekey_limit
27
+ # * User => :user
28
+ # * UserKnownHostsFile => :user_known_hosts_file
29
+ #
30
+ # Note that you will never need to use this class directly--you can control
31
+ # whether the OpenSSH configuration files are read by passing the :config
32
+ # option to Net::SSH.start. (They are, by default.)
33
+ class Config
34
+ class <<self
35
+ @@default_files = %w(~/.ssh/config /etc/ssh_config /etc/ssh/ssh_config)
36
+
37
+ # Returns an array of locations of OpenSSH configuration files
38
+ # to parse by default.
39
+ def default_files
40
+ @@default_files
41
+ end
42
+
43
+ # Loads the configuration data for the given +host+ from all of the
44
+ # given +files+ (defaulting to the list of files returned by
45
+ # #default_files), translates the resulting hash into the options
46
+ # recognized by Net::SSH, and returns them.
47
+ def for(host, files=default_files)
48
+ translate(files.inject({}) { |settings, file| load(file, host, settings) })
49
+ end
50
+
51
+ # Load the OpenSSH configuration settings in the given +file+ for the
52
+ # given +host+. If +settings+ is given, the options are merged into
53
+ # that hash, with existing values taking precedence over newly parsed
54
+ # ones. Returns a hash containing the OpenSSH options. (See
55
+ # #translate for how to convert the OpenSSH options into Net::SSH
56
+ # options.)
57
+ def load(file, host, settings={})
58
+ file = File.expand_path(file)
59
+ return settings unless File.readable?(file)
60
+
61
+ in_match = false
62
+ IO.foreach(file) do |line|
63
+ next if line =~ /^\s*(?:#.*)?$/
64
+
65
+ key, value = line.strip.split(/\s+/, 2)
66
+ key.downcase!
67
+
68
+ value = $1 if value =~ /^"(.*)"$/
69
+ value = case value.strip
70
+ when /^\d+$/ then value.to_i
71
+ when /^no$/i then false
72
+ when /^yes$/i then true
73
+ else value
74
+ end
75
+
76
+ if key == 'host'
77
+ in_match = (host =~ pattern2regex(value))
78
+ elsif in_match
79
+ if key == 'identityfile'
80
+ settings[key] ||= []
81
+ settings[key] << value
82
+ else
83
+ settings[key] = value unless settings.key?(key)
84
+ end
85
+ end
86
+ end
87
+
88
+ return settings
89
+ end
90
+
91
+ # Given a hash of OpenSSH configuration options, converts them into
92
+ # a hash of Net::SSH options. Unrecognized options are ignored. The
93
+ # +settings+ hash must have Strings for keys, all downcased, and
94
+ # the returned hash will have Symbols for keys.
95
+ def translate(settings)
96
+ settings.inject({}) do |hash, (key, value)|
97
+ case key
98
+ when 'ciphers' then
99
+ hash[:encryption] = value.split(/,/)
100
+ when 'compression' then
101
+ hash[:compression] = value
102
+ when 'compressionlevel' then
103
+ hash[:compression_level] = value
104
+ when 'connecttimeout' then
105
+ hash[:timeout] = value
106
+ when 'forwardagent' then
107
+ hash[:forward_agent] = value
108
+ when 'globalknownhostsfile'
109
+ hash[:global_known_hosts_file] = value
110
+ when 'hostbasedauthentication' then
111
+ if value
112
+ hash[:auth_methods] ||= []
113
+ hash[:auth_methods] << "hostbased"
114
+ end
115
+ when 'hostkeyalgorithms' then
116
+ hash[:host_key] = value.split(/,/)
117
+ when 'hostkeyalias' then
118
+ hash[:host_key_alias] = value
119
+ when 'hostname' then
120
+ hash[:host_name] = value
121
+ when 'identityfile' then
122
+ hash[:keys] = value
123
+ when 'macs' then
124
+ hash[:hmac] = value.split(/,/)
125
+ when 'passwordauthentication'
126
+ if value
127
+ hash[:auth_methods] ||= []
128
+ hash[:auth_methods] << "password"
129
+ end
130
+ when 'port'
131
+ hash[:port] = value
132
+ when 'preferredauthentications'
133
+ hash[:auth_methods] = value.split(/,/)
134
+ when 'pubkeyauthentication'
135
+ if value
136
+ hash[:auth_methods] ||= []
137
+ hash[:auth_methods] << "publickey"
138
+ end
139
+ when 'rekeylimit'
140
+ hash[:rekey_limit] = interpret_size(value)
141
+ when 'user'
142
+ hash[:user] = value
143
+ when 'userknownhostsfile'
144
+ hash[:user_known_hosts_file] = value
145
+ end
146
+ hash
147
+ end
148
+ end
149
+
150
+ private
151
+
152
+ # Converts an ssh_config pattern into a regex for matching against
153
+ # host names.
154
+ def pattern2regex(pattern)
155
+ pattern = "^" + pattern.gsub(/\./, "\\.").
156
+ gsub(/\?/, '.').
157
+ gsub(/\*/, '.*') + "$"
158
+ Regexp.new(pattern, true)
159
+ end
160
+
161
+ # Converts the given size into an integer number of bytes.
162
+ def interpret_size(size)
163
+ case size
164
+ when /k$/i then size.to_i * 1024
165
+ when /m$/i then size.to_i * 1024 * 1024
166
+ when /g$/i then size.to_i * 1024 * 1024 * 1024
167
+ else size.to_i
168
+ end
169
+ end
170
+ end
171
+ end
172
+
173
+ end; end
@@ -1,504 +1,625 @@
1
- #--
2
- # =============================================================================
3
- # Copyright (c) 2004,2005 Jamis Buck (jamis@37signals.com)
4
- # All rights reserved.
5
- #
6
- # This source file is distributed as part of the Net::SSH Secure Shell Client
7
- # library for Ruby. This file (and the library as a whole) may be used only as
8
- # allowed by either the BSD license, or the Ruby license (or, by association
9
- # with the Ruby license, the GPL). See the "doc" subdirectory of the Net::SSH
10
- # distribution for the texts of these licenses.
11
- # -----------------------------------------------------------------------------
12
- # net-ssh website : http://net-ssh.rubyforge.org
13
- # project website: http://rubyforge.org/projects/net-ssh
14
- # =============================================================================
15
- #++
16
-
1
+ require 'net/ssh/loggable'
17
2
  require 'net/ssh/connection/constants'
18
3
  require 'net/ssh/connection/term'
19
4
 
20
- module Net
21
- module SSH
22
- module Connection
23
-
24
- class Channel
25
- include Constants
26
-
27
- #--
28
- # ====================================================================
29
- # ATTRIBUTES
30
- # ====================================================================
31
- #++
32
-
33
- # The channel's local id (assigned by the connection)
34
- attr_reader :local_id
35
-
36
- # The channel's remote id (assigned by the remote server)
37
- attr_reader :remote_id
38
-
39
- # The connection driver instance that owns this channel
40
- attr_reader :connection
41
-
42
- # The type of this channel
43
- attr_reader :type
44
-
45
- # The maximum packet size that may be sent over this channel
46
- attr_reader :maximum_packet_size
47
-
48
- # The maximum data window size for this channel
49
- attr_reader :window_size
50
-
51
- # The maximum packet size that may be sent over this channel
52
- attr_reader :local_maximum_packet_size
53
-
54
- # The maximum data window size for this channel
55
- attr_reader :local_window_size
56
-
57
- #--
58
- # ====================================================================
59
- # FACTORY METHODS
60
- # ====================================================================
61
- #++
62
-
63
- # Requests that a new channel be opened on the remote host.
64
- # This will return immediately, but the +on_confirm_open+ callback
65
- # will be invoked when the remote host confirms that the channel has
66
- # been successfully opened.
67
- def self.open( connection, log, buffers, type, data=nil )
68
- channel = new( connection, log, buffers, type )
69
-
70
- msg = buffers.writer
71
-
72
- msg.write_byte CHANNEL_OPEN
73
- msg.write_string type
74
- msg.write_long channel.local_id
75
- msg.write_long channel.local_window_size
76
- msg.write_long channel.local_maximum_packet_size
77
- msg.write data.to_s if data
78
-
79
- connection.send_message msg
80
-
81
- channel
82
- end
83
-
84
- # Creates a new channel object with the given internal
85
- # information. The channel is assumed to already be
86
- # connected to a remote host.
87
- def self.create( connection, log, buffers, type, remote_id,
88
- window_size, packet_size )
89
- # begin
90
- channel = new( connection, log, buffers, type )
91
- channel.do_confirm_open remote_id, window_size, packet_size
92
- channel
93
- end
94
-
95
- private_class_method :new
96
-
97
- #--
98
- # ====================================================================
99
- # CONSTRUCTOR
100
- # ====================================================================
101
- #++
102
-
103
- # Create a new channel object on the given connection, and of the given
104
- # type.
105
- def initialize( connection, log, buffers, type )
106
- @connection = connection
107
- @log = log
108
- @buffers = buffers
109
- @type = type
110
- @local_id = @connection.allocate_channel_id
111
- @local_window_size = 0x20000
112
- @local_maximum_packet_size = 0x10000
113
- end
114
-
115
- #--
116
- # ====================================================================
117
- # CALLBACK HOOKS
118
- # ====================================================================
119
- #++
120
-
121
- # Set the callback to use when the channel has been confirmed
122
- # to be open.
123
- def on_confirm_open( &block )
124
- @on_confirm_open = block
125
- end
126
-
127
- # Set the callback to use when the channel could not be opened
128
- # for some reason.
129
- def on_confirm_failed( &block )
130
- @on_confirm_failed = block
131
- end
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, &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 = 0x10000
118
+ @local_window_size = @local_maximum_window_size = 0x20000
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 = false
130
+ end
132
131
 
133
- # Set the callback to be invoked when the server requests
134
- # that the window size be adjusted.
135
- def on_window_adjust( &block )
136
- @on_window_adjust = block
137
- end
132
+ # A shortcut for accessing properties of the channel (see #properties).
133
+ def [](name)
134
+ @properties[name]
135
+ end
138
136
 
139
- # Set the callback to be invoked when the server sends a
140
- # data packet over the channel.
141
- def on_data( &block )
142
- @on_data = block
143
- end
137
+ # A shortcut for setting properties of the channel (see #properties).
138
+ def []=(name, value)
139
+ @properties[name] = value
140
+ end
144
141
 
145
- # Set the callback to be invoked when the server sends an
146
- # extended data packet over the channel.
147
- def on_extended_data( &block )
148
- @on_extended_data = block
149
- end
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
150
161
 
151
- # Set the callback to be invoked when the server sends an EOF
152
- # packet.
153
- def on_eof( &block )
154
- @on_eof = block
155
- end
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
156
178
 
157
- # Set the callback to be invoked when the server sends a
158
- # request packet.
159
- def on_request( &block )
160
- @on_request = block
161
- end
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
162
190
 
163
- # Set the callback to invoked when the server sends
164
- # confirmation of a successful operation.
165
- def on_success( &block )
166
- @on_success = block
167
- end
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)
168
229
 
169
- # Set the callback to invoked when the server sends
170
- # notification of a failed operation.
171
- def on_failure( &block )
172
- @on_failure = block
173
- end
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
174
235
 
175
- # Set the callback to be invoked when the channel is closed.
176
- def on_close( &block )
177
- @on_close = block
178
- end
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
179
253
 
180
- #--
181
- # ====================================================================
182
- # CHANNEL STATE ACCESSORS
183
- # ====================================================================
184
- #++
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
185
262
 
186
- def valid?
187
- not @local_id.nil?
188
- end
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
189
271
 
190
- # Retrieved a named property of the channel.
191
- def property( name )
192
- ( @properties ||= Hash.new )[ name ]
193
- end
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
194
279
 
195
- # Set a named property on the channel.
196
- def set_property( name, value )
197
- ( @properties ||= Hash.new )[ name ] = value
198
- end
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
199
291
 
200
- alias :[] :property
201
- alias :[]= :set_property
202
-
203
- #--
204
- # ====================================================================
205
- # CHANNEL AFFECTORS
206
- # ====================================================================
207
- #++
208
-
209
- # Closes the channel.
210
- def close( client_initiated=true )
211
- unless defined?(@already_closed) && @already_closed
212
- msg = @buffers.writer
213
- msg.write_byte CHANNEL_CLOSE
214
- msg.write_long @remote_id
215
- @connection.send_message msg
216
- @already_closed = true
217
- end
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
218
298
 
219
- unless client_initiated
220
- @connection.remove_channel( self )
221
- callback :close, self
222
- end
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
+ def eof!
302
+ return if eof?
303
+ @eof = true
304
+ connection.send_message(Buffer.from(:byte, CHANNEL_EOF, :long, remote_id))
305
+ end
223
306
 
224
- self
225
- end
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
313
+ end
226
314
 
227
- # Send an EOF across the channel. No data should be sent from the client
228
- # to the server over this channel after this, although packets may still
229
- # be received from the server.
230
- def send_eof
231
- msg = @buffers.writer
232
- msg.write_byte CHANNEL_EOF
233
- msg.write_long @remote_id
234
- @connection.send_message msg
235
- self
236
- end
315
+ # Registers a callback to be invoked when data packets are received by the
316
+ # channel. The callback is called with the channel as the first argument,
317
+ # and the data as the second.
318
+ #
319
+ # channel.on_data do |ch, data|
320
+ # puts "got data: #{data.inspect}"
321
+ # end
322
+ #
323
+ # Data received this way is typically the data written by the remote
324
+ # process to its +stdout+ stream.
325
+ def on_data(&block)
326
+ old, @on_data = @on_data, block
327
+ old
328
+ end
237
329
 
238
- # Send the given signal to process on the other side of the channel. The
239
- # parameter should be one of the Channel::SIGxxx constants.
240
- def send_signal( sig, want_reply=false )
241
- send_request_string "signal", sig, want_reply
242
- self
243
- end
330
+ # Registers a callback to be invoked when extended data packets are received
331
+ # by the channel. The callback is called with the channel as the first
332
+ # argument, the data type (as an integer) as the second, and the data as
333
+ # the third. Extended data is almost exclusively used to send +stderr+ data
334
+ # (+type+ == 1). Other extended data types are not defined by the SSH
335
+ # protocol.
336
+ #
337
+ # channel.on_extended_data do |ch, type, data|
338
+ # puts "got stderr: #{data.inspect}"
339
+ # end
340
+ def on_extended_data(&block)
341
+ old, @on_extended_data = @on_extended_data, block
342
+ old
343
+ end
244
344
 
245
- # Send a channel request with the given name. It will have one data
246
- # item, which will be interpreted as a string.
247
- def send_request_string( request_name, data, want_reply=false )
248
- msg = @buffers.writer
249
- msg.write_string data.to_s
250
- send_request request_name, msg, want_reply
251
- end
345
+ # Registers a callback to be invoked for each pass of the event loop for
346
+ # this channel. There are no guarantees on timeliness in the event loop,
347
+ # but it will be called roughly once for each packet received by the
348
+ # connection (not the channel). This callback is invoked with the channel
349
+ # as the sole argument.
350
+ #
351
+ # Here's an example that accumulates the channel data into a variable on
352
+ # the channel itself, and displays individual lines in the input one
353
+ # at a time when the channel is processed:
354
+ #
355
+ # channel[:data] = ""
356
+ #
357
+ # channel.on_data do |ch, data|
358
+ # channel[:data] << data
359
+ # end
360
+ #
361
+ # channel.on_process do |ch|
362
+ # if channel[:data] =~ /^.*?\n/
363
+ # puts $&
364
+ # channel[:data] = $'
365
+ # end
366
+ # end
367
+ def on_process(&block)
368
+ old, @on_process = @on_process, block
369
+ old
370
+ end
252
371
 
253
- # Send a generic channel request with the given name. The data item will
254
- # be written directly into the request (after converting it to a string,
255
- # as necessary).
256
- def send_request( request_name, data, want_reply=false )
257
- msg = @buffers.writer
258
- msg.write_byte CHANNEL_REQUEST
259
- msg.write_long @remote_id
260
- msg.write_string request_name
261
- msg.write_bool want_reply
262
- msg.write data.to_s
263
- @connection.send_message msg
264
- self
265
- end
372
+ # Registers a callback to be invoked when the server acknowledges that a
373
+ # channel is closed. This is invoked with the channel as the sole argument.
374
+ #
375
+ # channel.on_close do |ch|
376
+ # puts "remote end is closing!"
377
+ # end
378
+ def on_close(&block)
379
+ old, @on_close = @on_close, block
380
+ old
381
+ end
266
382
 
267
- # Send a "window adjust" message to the server for this channel,
268
- # informing it that it may send this many more bytes over the
269
- # channel.
270
- def send_window_adjust( size )
271
- msg = @buffers.writer
272
- msg.write_byte CHANNEL_WINDOW_ADJUST
273
- msg.write_long @remote_id
274
- msg.write_long size
275
- @connection.send_message msg
276
- end
383
+ # Registers a callback to be invoked when the server indicates that no more
384
+ # data will be sent to the channel (although the channel can still send
385
+ # data to the server). The channel is the sole argument to the callback.
386
+ #
387
+ # channel.on_eof do |ch|
388
+ # puts "remote end is done sending data"
389
+ # end
390
+ def on_eof(&block)
391
+ old, @on_eof = @on_eof, block
392
+ old
393
+ end
277
394
 
278
- # Send a data packet to the server, over the channel.
279
- def send_data( data )
280
- @connection.register_data_request( self, data )
281
- end
395
+ # Registers a callback to be invoked when the server was unable to open
396
+ # the requested channel. The channel itself will be passed to the block,
397
+ # along with the integer "reason code" for the failure, and a textual
398
+ # description of the failure from the server.
399
+ #
400
+ # channel = session.open_channel do |ch|
401
+ # # ..
402
+ # end
403
+ #
404
+ # channel.on_open_failed { |ch, code, desc| ... }
405
+ def on_open_failed(&block)
406
+ old, @on_open_failed = @on_open_failed, block
407
+ old
408
+ end
282
409
 
283
- # Send an extended data packet to the server, over the channel.
284
- # Extended data always has a numeric type associated with it. The
285
- # only predefined type is 1, whic corresponds to +stderr+ data.
286
- def send_extended_data( type, data )
287
- @connection.register_data_request( self, data, type )
288
- end
410
+ # Registers a callback to be invoked when a channel request of the given
411
+ # type is received. The callback will receive the channel as the first
412
+ # argument, and the associated (unparsed) data as the second. The data
413
+ # will be a Net::SSH::Buffer that you will need to parse, yourself,
414
+ # according to the kind of request you are watching.
415
+ #
416
+ # By default, if the request wants a reply, Net::SSH will send a
417
+ # CHANNEL_SUCCESS response for any request that was handled by a registered
418
+ # callback, and CHANNEL_FAILURE for any that wasn't, but if you want your
419
+ # registered callback to result in a CHANNEL_FAILURE response, just raise
420
+ # Net::SSH::ChannelRequestFailed.
421
+ #
422
+ # Some common channel requests that your programs might want to listen
423
+ # for are:
424
+ #
425
+ # * "exit-status" : the exit status of the remote process will be reported
426
+ # as a long integer in the data buffer, which you can grab via
427
+ # data.read_long.
428
+ # * "exit-signal" : if the remote process died as a result of a signal
429
+ # being sent to it, the signal will be reported as a string in the
430
+ # data, via data.read_string. (Not all SSH servers support this channel
431
+ # request type.)
432
+ #
433
+ # channel.on_request "exit-status" do |ch, data|
434
+ # puts "process terminated with exit status: #{data.read_long}"
435
+ # end
436
+ def on_request(type, &block)
437
+ old, @on_request[type] = @on_request[type], block
438
+ old
439
+ end
289
440
 
290
- # Splits the given data so that it will fit in a data packet, taking
291
- # into consideration the current window size and maximum packet size.
292
- # The +overhead+ parameter is the number of additional bytes added by
293
- # the packet itself.
294
- #
295
- # This returns a tuple, <tt>[data,data_to_return]</tt>, where the first
296
- # element is the data to include in the packet, and the second element
297
- # is the data remaining that would not fit in the packet.
298
- def split_data_for_packet( data, overhead )
299
- data_to_return = nil
300
-
301
- if data.length > window_size
302
- data_to_return = data[window_size..-1]
303
- data = data[0,window_size]
304
- end
441
+ # Sends a new channel request with the given name. The extra +data+
442
+ # parameter must either be empty, or consist of an even number of
443
+ # arguments. See Net::SSH::Buffer.from for a description of their format.
444
+ # If a block is given, it is registered as a callback for a pending
445
+ # request, and the packet will be flagged so that the server knows a
446
+ # reply is required. If no block is given, the server will send no
447
+ # response to this request. Responses, where required, will cause the
448
+ # callback to be invoked with the channel as the first argument, and
449
+ # either true or false as the second, depending on whether the request
450
+ # succeeded or not. The meaning of "success" and "failure" in this context
451
+ # is dependent on the specific request that was sent.
452
+ #
453
+ # channel.send_channel_request "shell" do |ch, success|
454
+ # if success
455
+ # puts "user shell started successfully"
456
+ # else
457
+ # puts "could not start user shell"
458
+ # end
459
+ # end
460
+ #
461
+ # Most channel requests you'll want to send are already wrapped in more
462
+ # convenient helper methods (see #exec and #subsystem).
463
+ def send_channel_request(request_name, *data, &callback)
464
+ info { "sending channel request #{request_name.inspect}" }
465
+ msg = Buffer.from(:byte, CHANNEL_REQUEST,
466
+ :long, remote_id, :string, request_name,
467
+ :bool, !callback.nil?, *data)
468
+ connection.send_message(msg)
469
+ pending_requests << callback if callback
470
+ end
305
471
 
306
- max_size_less_overhead = maximum_packet_size - overhead
307
- if data.length > max_size_less_overhead
308
- data_to_return = data[max_size_less_overhead..-1] + ( data_to_return || "" )
309
- data = data[0,max_size_less_overhead]
472
+ public # these methods are public, but for Net::SSH internal use only
473
+
474
+ # Enqueues pending output at the connection as CHANNEL_DATA packets. This
475
+ # does nothing if the channel has not yet been confirmed open (see
476
+ # #do_open_confirmation). This is called automatically by #process, which
477
+ # is called from the event loop (Connection::Session#process). You will
478
+ # generally not need to invoke it directly.
479
+ def enqueue_pending_output #:nodoc:
480
+ return unless remote_id
481
+
482
+ while output.length > 0
483
+ length = output.length
484
+ length = remote_window_size if length > remote_window_size
485
+ length = remote_maximum_packet_size if length > remote_maximum_packet_size
486
+
487
+ if length > 0
488
+ connection.send_message(Buffer.from(:byte, CHANNEL_DATA, :long, remote_id, :string, output.read(length)))
489
+ output.consume!
490
+ @remote_window_size -= length
491
+ else
492
+ break
310
493
  end
311
-
312
- [ data, data_to_return ]
313
- end
314
- private :split_data_for_packet
315
-
316
- # Send a data packet to the server, over the channel. Only sends as
317
- # much of that data as the channel is currently capable of sending
318
- # (based on window size and maximum packet size), and returns any
319
- # data that could not be sent. Returns +nil+ if all the data that
320
- # was requested to be sent, was sent.
321
- def send_data_packet( data )
322
- # overhead is ( byte.length + id.length + strlen.length ) = 9
323
- data, data_to_return = split_data_for_packet( data.to_s, 9 )
324
- @window_size -= data.length
325
-
326
- msg = @buffers.writer
327
- msg.write_byte CHANNEL_DATA
328
- msg.write_long @remote_id
329
- msg.write_string data
330
- @connection.send_message msg
331
-
332
- data_to_return
333
494
  end
495
+ end
334
496
 
335
- # Send an extended data packet to the server, over the channel.
336
- # Extended data always has a numeric type associated with it. The
337
- # only predefined type is 1, whic corresponds to +stderr+ data.
338
- def send_extended_data_packet( type, data )
339
- # overhead is
340
- # ( byte.length + id.length + type.length + strlen.length ) = 13
341
- data, data_to_return = split_data_for_packet( data.to_s, 13 )
342
- @window_size -= data.length
343
-
344
- msg = @buffers.writer
345
- msg.write_byte CHANNEL_EXTENDED_DATA
346
- msg.write_long @remote_id
347
- msg.write_long type
348
- msg.write_string data
349
- @connection.send_message msg
350
-
351
- data_to_return
352
- end
353
-
354
- VALID_PTY_OPTIONS = { :term=>"xterm",
355
- :chars_wide=>80,
356
- :chars_high=>24,
357
- :pixels_wide=>640,
358
- :pixels_high=>480,
359
- :modes=>{},
360
- :want_reply=>false }
361
-
362
- # Request that a pty be opened for this channel. Valid options are
363
- # :term, :chars_wide, :chars_high, :pixels_wide, :pixels_high, :modes,
364
- # and :want_reply. :modes is a Hash, where the keys are constants from
365
- # Net::SSH::Service::Term, and values are integers describing the
366
- # corresponding key.
367
- def request_pty( opts = {} )
368
- invalid_opts = opts.keys - VALID_PTY_OPTIONS.keys
369
- unless invalid_opts.empty?
370
- raise ArgumentError,
371
- "invalid option(s) to request_pty: #{invalid_opts.inspect}"
372
- end
373
-
374
- opts = VALID_PTY_OPTIONS.merge( opts )
497
+ # Invoked when the server confirms that a channel has been opened.
498
+ # The remote_id is the id of the channel as assigned by the remote host,
499
+ # and max_window and max_packet are the maximum window and maximum
500
+ # packet sizes, respectively. If an open-confirmation callback was
501
+ # given when the channel was created, it is invoked at this time with
502
+ # the channel itself as the sole argument.
503
+ def do_open_confirmation(remote_id, max_window, max_packet) #:nodoc:
504
+ @remote_id = remote_id
505
+ @remote_window_size = @remote_maximum_window_size = max_window
506
+ @remote_maximum_packet_size = max_packet
507
+ connection.forward.agent(self) if connection.options[:forward_agent] && type == "session"
508
+ @on_confirm_open.call(self) if @on_confirm_open
509
+ end
375
510
 
376
- msg = @buffers.writer
377
- msg.write_string opts[ :term ]
378
- msg.write_long opts[ :chars_wide ]
379
- msg.write_long opts[ :chars_high ]
380
- msg.write_long opts[ :pixels_wide ]
381
- msg.write_long opts[ :pixels_high ]
511
+ # Invoked when the server failed to open the channel. If an #on_open_failed
512
+ # callback was specified, it will be invoked with the channel, reason code,
513
+ # and description as arguments. Otherwise, a ChannelOpenFailed exception
514
+ # will be raised.
515
+ def do_open_failed(reason_code, description)
516
+ if @on_open_failed
517
+ @on_open_failed.call(self, reason_code, description)
518
+ else
519
+ raise ChannelOpenFailed.new(reason_code, description)
520
+ end
521
+ end
382
522
 
383
- modes = @buffers.writer
384
- opts[ :modes ].each do |mode, data|
385
- modes.write_byte mode
386
- modes.write_long data
387
- end
388
- modes.write_byte Term::TTY_OP_END
523
+ # Invoked when the server sends a CHANNEL_WINDOW_ADJUST packet, and
524
+ # causes the remote window size to be adjusted upwards by the given
525
+ # number of bytes. This has the effect of allowing more data to be sent
526
+ # from the local end to the remote end of the channel.
527
+ def do_window_adjust(bytes) #:nodoc:
528
+ @remote_maximum_window_size += bytes
529
+ @remote_window_size += bytes
530
+ end
389
531
 
390
- msg.write_string modes.to_s
532
+ # Invoked when the server sends a channel request. If any #on_request
533
+ # callback has been registered for the specific type of this request,
534
+ # it is invoked. If +want_reply+ is true, a packet will be sent of
535
+ # either CHANNEL_SUCCESS or CHANNEL_FAILURE type. If there was no callback
536
+ # to handle the request, CHANNEL_FAILURE will be sent. Otherwise,
537
+ # CHANNEL_SUCCESS, unless the callback raised ChannelRequestFailed. The
538
+ # callback should accept the channel as the first argument, and the
539
+ # request-specific data as the second.
540
+ def do_request(request, want_reply, data) #:nodoc:
541
+ result = true
391
542
 
392
- send_request "pty-req", msg, opts[:want_reply]
543
+ begin
544
+ callback = @on_request[request] or raise ChannelRequestFailed
545
+ callback.call(self, data)
546
+ rescue ChannelRequestFailed
547
+ result = false
393
548
  end
394
549
 
395
- # Execute the given remote command over the channel. This should be
396
- # invoked in the "on_confirm" callback of a channel. This method will
397
- # return immediately.
398
- def exec( command, want_reply=false )
399
- send_request_string "exec", command, want_reply
400
- end
401
-
402
- # Request the given subsystem. This method will return immediately.
403
- def subsystem( subsystem, want_reply=true )
404
- send_request_string "subsystem", subsystem, want_reply
550
+ if want_reply
551
+ msg = Buffer.from(:byte, result ? CHANNEL_SUCCESS : CHANNEL_FAILURE, :long, remote_id)
552
+ connection.send_message(msg)
405
553
  end
554
+ end
406
555
 
407
- #--
408
- # ====================================================================
409
- # CHANNEL EVENTS
410
- # ====================================================================
411
- #++
412
-
413
- # A convenience method for defining new event callbacks.
414
- def self.event( event, *parameters )
415
- define_method "do_#{event}" do |*args|
416
- callback event, self, *args
417
- self
418
- end
419
- end
556
+ # Invokes the #on_data callback when the server sends data to the
557
+ # channel. This will reduce the available window size on the local end,
558
+ # but does not actually throttle requests that come in illegally when
559
+ # the window size is too small. The callback is invoked with the channel
560
+ # as the first argument, and the data as the second.
561
+ def do_data(data) #:nodoc:
562
+ update_local_window_size(data.length)
563
+ @on_data.call(self, data) if @on_data
564
+ end
420
565
 
421
- # Invoked when the server confirms the opening of a channel.
422
- def do_confirm_open( remote_id, window_size, packet_size )
423
- @remote_id = remote_id
424
- @window_size = window_size
425
- @maximum_packet_size = packet_size
426
- callback :confirm_open, self
427
- end
566
+ # Invokes the #on_extended_data callback when the server sends
567
+ # extended data to the channel. This will reduce the available window
568
+ # size on the local end. The callback is invoked with the channel,
569
+ # type, and data.
570
+ def do_extended_data(type, data)
571
+ update_local_window_size(data.length)
572
+ @on_extended_data.call(self, type, data) if @on_extended_data
573
+ end
428
574
 
429
- # Invoked when the server failed to confirm the opening of a channel.
430
- def do_confirm_failed( reason_code, description, language )
431
- @local_id = nil
432
- @connection = nil
433
- callback :confirm_failed, self, reason_code, description, language
434
- end
575
+ # Invokes the #on_eof callback when the server indicates that no
576
+ # further data is forthcoming. The callback is invoked with the channel
577
+ # as the argument.
578
+ def do_eof
579
+ @on_eof.call(self) if @on_eof
580
+ end
435
581
 
436
- # Invoked when the server asks to adjust the window size. This in turn
437
- # calls the "on_window_adjust" callback.
438
- def do_window_adjust( bytes_to_add )
439
- @window_size += bytes_to_add
440
- callback :window_adjust, self, bytes_to_add
441
- end
582
+ # Invokes the #on_close callback when the server closes a channel.
583
+ # The channel is the only argument.
584
+ def do_close
585
+ @on_close.call(self) if @on_close
586
+ end
442
587
 
443
- # Invoked when the server sends a data packet. This in turn calls the
444
- # "on_data" callback.
445
- def do_data( data )
446
- update_local_window_size data
447
- callback :data, self, data
588
+ # Invokes the next pending request callback with +false+ as the second
589
+ # argument.
590
+ def do_failure
591
+ if callback = pending_requests.shift
592
+ callback.call(self, false)
593
+ else
594
+ error { "channel failure recieved with no pending request to handle it (bug?)" }
448
595
  end
596
+ end
449
597
 
450
- # Invoked when the server sends an extended data packet. This in turn
451
- # calls the "on_extended_data" callback.
452
- def do_extended_data( type, data )
453
- update_local_window_size data
454
- callback :extended_data, self, type, data
598
+ # Invokes the next pending request callback with +true+ as the second
599
+ # argument.
600
+ def do_success
601
+ if callback = pending_requests.shift
602
+ callback.call(self, true)
603
+ else
604
+ error { "channel success recieved with no pending request to handle it (bug?)" }
455
605
  end
606
+ end
456
607
 
457
- # Invoked when the server sends an EOF packet. This in turn calls the
458
- # "on_eof" callback.
459
- event :eof
460
-
461
- # Invoked when the server sends a request packet. This in turn calls
462
- # the "on_request" callback.
463
- event :request, :type, :want_reply, :data
464
-
465
- # Invoked when the server sends confirmation of a successful operation.
466
- # This in turn invokes the "on_success" callback, if set.
467
- event :success
468
-
469
- # Invoked when the server sends notification of a failed operation.
470
- # This in turn invokes the "on_failure" callback, if set.
471
- event :failure
472
-
473
- #--
474
- # ====================================================================
475
- # PRIVATE UTILITIES
476
- # ====================================================================
477
- #++
478
-
479
- # Updates the window size for this channel based on the size of the
480
- # data that was receieved. If no more space in the window is left,
481
- # a message is sent to the server indicating that the window size
482
- # is increased.
483
- def update_local_window_size( data )
484
- @local_window_size -= data.length
485
- if @local_window_size < 4096
486
- @local_window_size += 0x20000
487
- send_window_adjust 0x20000
488
- end
489
- end
490
- private :update_local_window_size
608
+ private
491
609
 
492
- # A convenience utility method for invoking a named callback with a
493
- # set of arguments.
494
- def callback( which, *args )
495
- block = instance_variable_get( "@on_#{which.to_s}" )
496
- block.call( *args ) if block
610
+ # Updates the local window size by the given amount. If the window
611
+ # size drops to less than half of the local maximum (an arbitrary
612
+ # threshold), a CHANNEL_WINDOW_ADJUST message will be sent to the
613
+ # server telling it that the window size has grown.
614
+ def update_local_window_size(size)
615
+ @local_window_size -= size
616
+ if local_window_size < local_maximum_window_size/2
617
+ connection.send_message(Buffer.from(:byte, CHANNEL_WINDOW_ADJUST,
618
+ :long, remote_id, :long, 0x20000))
619
+ @local_window_size += 0x20000
620
+ @local_maximum_window_size += 0x20000
497
621
  end
498
- private :callback
499
-
500
622
  end
501
-
502
- end
503
623
  end
504
- end
624
+
625
+ end; end; end