net-ssh 1.1.4 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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