harbr 0.2.10 → 2.8.1

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 (179) hide show
  1. checksums.yaml +4 -4
  2. data/.DS_Store +0 -0
  3. data/exe/harbr +225 -150
  4. data/lib/examples/container.toml +13 -0
  5. data/lib/harbr/container.rb +14 -10
  6. data/lib/harbr/host.rb +21 -0
  7. data/lib/harbr/version.rb +1 -1
  8. data/lib/harbr.rb +21 -6
  9. data/vendor/bundle/ruby/3.2.0/cache/dddr-1.0.8.gem +0 -0
  10. data/vendor/bundle/ruby/3.2.0/cache/dddr-1.1.0.gem +0 -0
  11. data/vendor/bundle/ruby/3.2.0/cache/dddr-1.1.1.gem +0 -0
  12. data/vendor/bundle/ruby/3.2.0/cache/net-ssh-7.2.1.gem +0 -0
  13. data/vendor/bundle/ruby/3.2.0/gems/dddr-1.0.8/.DS_Store +0 -0
  14. data/vendor/bundle/ruby/3.2.0/gems/dddr-1.0.8/.rspec +3 -0
  15. data/vendor/bundle/ruby/3.2.0/gems/dddr-1.0.8/.standard.yml +3 -0
  16. data/vendor/bundle/ruby/3.2.0/gems/dddr-1.0.8/CHANGELOG.md +5 -0
  17. data/vendor/bundle/ruby/3.2.0/gems/dddr-1.0.8/CODE_OF_CONDUCT.md +84 -0
  18. data/vendor/bundle/ruby/3.2.0/gems/dddr-1.0.8/LICENSE.txt +21 -0
  19. data/vendor/bundle/ruby/3.2.0/gems/dddr-1.0.8/README.md +96 -0
  20. data/vendor/bundle/ruby/3.2.0/gems/dddr-1.0.8/Rakefile +10 -0
  21. data/vendor/bundle/ruby/3.2.0/gems/dddr-1.0.8/hero.png +0 -0
  22. data/vendor/bundle/ruby/3.2.0/gems/dddr-1.0.8/lib/dddr/version.rb +5 -0
  23. data/vendor/bundle/ruby/3.2.0/gems/dddr-1.0.8/lib/dddr.rb +205 -0
  24. data/vendor/bundle/ruby/3.2.0/gems/dddr-1.0.8/sig/dddr.rbs +4 -0
  25. data/vendor/bundle/ruby/3.2.0/gems/dddr-1.1.0/.DS_Store +0 -0
  26. data/vendor/bundle/ruby/3.2.0/gems/dddr-1.1.0/.rspec +3 -0
  27. data/vendor/bundle/ruby/3.2.0/gems/dddr-1.1.0/.standard.yml +3 -0
  28. data/vendor/bundle/ruby/3.2.0/gems/dddr-1.1.0/CHANGELOG.md +5 -0
  29. data/vendor/bundle/ruby/3.2.0/gems/dddr-1.1.0/CODE_OF_CONDUCT.md +84 -0
  30. data/vendor/bundle/ruby/3.2.0/gems/dddr-1.1.0/LICENSE.txt +21 -0
  31. data/vendor/bundle/ruby/3.2.0/gems/dddr-1.1.0/README.md +96 -0
  32. data/vendor/bundle/ruby/3.2.0/gems/dddr-1.1.0/Rakefile +10 -0
  33. data/vendor/bundle/ruby/3.2.0/gems/dddr-1.1.0/hero.png +0 -0
  34. data/vendor/bundle/ruby/3.2.0/gems/dddr-1.1.0/lib/dddr/version.rb +5 -0
  35. data/vendor/bundle/ruby/3.2.0/gems/dddr-1.1.0/lib/dddr.rb +182 -0
  36. data/vendor/bundle/ruby/3.2.0/gems/dddr-1.1.0/sig/dddr.rbs +4 -0
  37. data/vendor/bundle/ruby/3.2.0/gems/dddr-1.1.1/.DS_Store +0 -0
  38. data/vendor/bundle/ruby/3.2.0/gems/dddr-1.1.1/.rspec +3 -0
  39. data/vendor/bundle/ruby/3.2.0/gems/dddr-1.1.1/.standard.yml +3 -0
  40. data/vendor/bundle/ruby/3.2.0/gems/dddr-1.1.1/CHANGELOG.md +5 -0
  41. data/vendor/bundle/ruby/3.2.0/gems/dddr-1.1.1/CODE_OF_CONDUCT.md +84 -0
  42. data/vendor/bundle/ruby/3.2.0/gems/dddr-1.1.1/LICENSE.txt +21 -0
  43. data/vendor/bundle/ruby/3.2.0/gems/dddr-1.1.1/README.md +96 -0
  44. data/vendor/bundle/ruby/3.2.0/gems/dddr-1.1.1/Rakefile +10 -0
  45. data/vendor/bundle/ruby/3.2.0/gems/dddr-1.1.1/hero.png +0 -0
  46. data/vendor/bundle/ruby/3.2.0/gems/dddr-1.1.1/lib/dddr/version.rb +5 -0
  47. data/vendor/bundle/ruby/3.2.0/gems/dddr-1.1.1/lib/dddr.rb +184 -0
  48. data/vendor/bundle/ruby/3.2.0/gems/dddr-1.1.1/sig/dddr.rbs +4 -0
  49. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/.dockerignore +6 -0
  50. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/.github/FUNDING.yml +1 -0
  51. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/.github/config/rubocop_linter_action.yml +4 -0
  52. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/.github/workflows/ci-with-docker.yml +44 -0
  53. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/.github/workflows/ci.yml +94 -0
  54. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/.github/workflows/rubocop.yml +16 -0
  55. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/.gitignore +15 -0
  56. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/.rubocop.yml +22 -0
  57. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/.rubocop_todo.yml +1081 -0
  58. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/CHANGES.txt +738 -0
  59. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/DEVELOPMENT.md +23 -0
  60. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/Dockerfile +29 -0
  61. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/Dockerfile.openssl3 +17 -0
  62. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/Gemfile +13 -0
  63. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/Gemfile.noed25519 +12 -0
  64. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/Gemfile.norbnacl +12 -0
  65. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/ISSUE_TEMPLATE.md +30 -0
  66. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/LICENSE.txt +19 -0
  67. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/Manifest +132 -0
  68. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/README.md +298 -0
  69. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/Rakefile +192 -0
  70. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/SECURITY.md +4 -0
  71. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/THANKS.txt +110 -0
  72. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/appveyor.yml +58 -0
  73. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/docker-compose.yml +25 -0
  74. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/authentication/agent.rb +284 -0
  75. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/authentication/certificate.rb +183 -0
  76. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/authentication/constants.rb +20 -0
  77. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/authentication/ed25519.rb +186 -0
  78. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/authentication/ed25519_loader.rb +31 -0
  79. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/authentication/key_manager.rb +327 -0
  80. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/authentication/methods/abstract.rb +79 -0
  81. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/authentication/methods/hostbased.rb +72 -0
  82. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/authentication/methods/keyboard_interactive.rb +77 -0
  83. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/authentication/methods/none.rb +34 -0
  84. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/authentication/methods/password.rb +80 -0
  85. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/authentication/methods/publickey.rb +137 -0
  86. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/authentication/pageant.rb +497 -0
  87. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/authentication/pub_key_fingerprint.rb +43 -0
  88. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/authentication/session.rb +172 -0
  89. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/buffer.rb +449 -0
  90. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/buffered_io.rb +202 -0
  91. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/config.rb +406 -0
  92. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/connection/channel.rb +694 -0
  93. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/connection/constants.rb +33 -0
  94. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/connection/event_loop.rb +123 -0
  95. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/connection/keepalive.rb +59 -0
  96. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/connection/session.rb +712 -0
  97. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/connection/term.rb +180 -0
  98. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/errors.rb +106 -0
  99. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/key_factory.rb +218 -0
  100. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/known_hosts.rb +265 -0
  101. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/loggable.rb +62 -0
  102. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/packet.rb +106 -0
  103. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/prompt.rb +62 -0
  104. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/proxy/command.rb +123 -0
  105. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/proxy/errors.rb +16 -0
  106. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/proxy/http.rb +98 -0
  107. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/proxy/https.rb +50 -0
  108. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/proxy/jump.rb +54 -0
  109. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/proxy/socks4.rb +67 -0
  110. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/proxy/socks5.rb +140 -0
  111. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/service/forward.rb +426 -0
  112. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/test/channel.rb +147 -0
  113. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/test/extensions.rb +173 -0
  114. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/test/kex.rb +46 -0
  115. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/test/local_packet.rb +53 -0
  116. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/test/packet.rb +101 -0
  117. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/test/remote_packet.rb +40 -0
  118. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/test/script.rb +180 -0
  119. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/test/socket.rb +65 -0
  120. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/test.rb +94 -0
  121. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/transport/algorithms.rb +524 -0
  122. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/transport/chacha20_poly1305_cipher.rb +117 -0
  123. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/transport/chacha20_poly1305_cipher_loader.rb +17 -0
  124. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/transport/cipher_factory.rb +128 -0
  125. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/transport/constants.rb +40 -0
  126. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/transport/ctr.rb +115 -0
  127. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/transport/hmac/abstract.rb +97 -0
  128. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/transport/hmac/md5.rb +10 -0
  129. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/transport/hmac/md5_96.rb +9 -0
  130. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/transport/hmac/none.rb +13 -0
  131. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/transport/hmac/ripemd160.rb +11 -0
  132. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/transport/hmac/sha1.rb +11 -0
  133. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/transport/hmac/sha1_96.rb +9 -0
  134. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/transport/hmac/sha2_256.rb +11 -0
  135. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/transport/hmac/sha2_256_96.rb +9 -0
  136. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/transport/hmac/sha2_256_etm.rb +12 -0
  137. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/transport/hmac/sha2_512.rb +11 -0
  138. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/transport/hmac/sha2_512_96.rb +9 -0
  139. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/transport/hmac/sha2_512_etm.rb +12 -0
  140. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/transport/hmac.rb +47 -0
  141. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/transport/identity_cipher.rb +65 -0
  142. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/transport/kex/abstract.rb +130 -0
  143. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/transport/kex/abstract5656.rb +72 -0
  144. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/transport/kex/curve25519_sha256.rb +39 -0
  145. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/transport/kex/curve25519_sha256_loader.rb +30 -0
  146. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/transport/kex/diffie_hellman_group14_sha1.rb +37 -0
  147. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/transport/kex/diffie_hellman_group14_sha256.rb +11 -0
  148. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb +122 -0
  149. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb +72 -0
  150. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha256.rb +11 -0
  151. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/transport/kex/ecdh_sha2_nistp256.rb +39 -0
  152. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/transport/kex/ecdh_sha2_nistp384.rb +21 -0
  153. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/transport/kex/ecdh_sha2_nistp521.rb +21 -0
  154. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/transport/kex.rb +31 -0
  155. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/transport/key_expander.rb +30 -0
  156. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/transport/openssl.rb +274 -0
  157. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/transport/openssl_cipher_extensions.rb +8 -0
  158. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/transport/packet_stream.rb +301 -0
  159. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/transport/server_version.rb +77 -0
  160. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/transport/session.rb +354 -0
  161. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/transport/state.rb +208 -0
  162. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/verifiers/accept_new.rb +33 -0
  163. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/verifiers/accept_new_or_local_tunnel.rb +33 -0
  164. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/verifiers/always.rb +58 -0
  165. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/verifiers/never.rb +19 -0
  166. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh/version.rb +68 -0
  167. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/lib/net/ssh.rb +338 -0
  168. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/net-ssh-public_cert.pem +20 -0
  169. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/net-ssh.gemspec +46 -0
  170. data/vendor/bundle/ruby/3.2.0/gems/net-ssh-7.2.1/support/ssh_tunnel_bug.rb +65 -0
  171. data/vendor/bundle/ruby/3.2.0/specifications/dddr-1.0.8.gemspec +27 -0
  172. data/vendor/bundle/ruby/3.2.0/specifications/dddr-1.1.0.gemspec +27 -0
  173. data/vendor/bundle/ruby/3.2.0/specifications/dddr-1.1.1.gemspec +27 -0
  174. data/vendor/bundle/ruby/3.2.0/specifications/net-ssh-7.2.1.gemspec +38 -0
  175. metadata +174 -9
  176. data/config/manifest.yml +0 -5
  177. data/lib/harbr/job.rb +0 -252
  178. data/lib/harbr/lxd/job.rb +0 -119
  179. data/lib/harbr/lxd/setup.rb +0 -45
@@ -0,0 +1,94 @@
1
+ require 'net/ssh/transport/session'
2
+ require 'net/ssh/connection/session'
3
+ require 'net/ssh/test/kex'
4
+ require 'net/ssh/test/socket'
5
+
6
+ module Net
7
+ module SSH
8
+ # This module may be used in unit tests, for when you want to test that your
9
+ # SSH state machines are really doing what you expect they are doing. You will
10
+ # typically include this module in your unit test class, and then build a
11
+ # "story" of expected sends and receives:
12
+ #
13
+ # require 'minitest/autorun'
14
+ # require 'net/ssh/test'
15
+ #
16
+ # class MyTest < Minitest::Test
17
+ # include Net::SSH::Test
18
+ #
19
+ # def test_exec_via_channel_works
20
+ # story do |session|
21
+ # channel = session.opens_channel
22
+ # channel.sends_exec "ls"
23
+ # channel.gets_data "result of ls"
24
+ # channel.gets_close
25
+ # channel.sends_close
26
+ # end
27
+ #
28
+ # assert_scripted do
29
+ # result = nil
30
+ #
31
+ # connection.open_channel do |ch|
32
+ # ch.exec("ls") do |success|
33
+ # ch.on_data { |c, data| result = data }
34
+ # ch.on_close { |c| c.close }
35
+ # end
36
+ # end
37
+ #
38
+ # connection.loop
39
+ # assert_equal "result of ls", result
40
+ # end
41
+ # end
42
+ # end
43
+ #
44
+ # See Net::SSH::Test::Channel and Net::SSH::Test::Script for more options.
45
+ #
46
+ # Note that the Net::SSH::Test system is rather finicky yet, and can be kind
47
+ # of frustrating to get working. Any suggestions for improvement will be
48
+ # welcome!
49
+ module Test
50
+ # If a block is given, yields the script for the test socket (#socket).
51
+ # Otherwise, simply returns the socket's script. See Net::SSH::Test::Script.
52
+ def story
53
+ Net::SSH::Test::Extensions::IO.with_test_extension { yield socket.script if block_given? }
54
+ return socket.script
55
+ end
56
+
57
+ # Returns the test socket instance to use for these tests (see
58
+ # Net::SSH::Test::Socket).
59
+ def socket(options = {})
60
+ @socket ||= Net::SSH::Test::Socket.new
61
+ end
62
+
63
+ # Returns the connection session (Net::SSH::Connection::Session) for use
64
+ # in these tests. It is a fully functional SSH session, operating over
65
+ # a mock socket (#socket).
66
+ def connection(options = {})
67
+ @connection ||= Net::SSH::Connection::Session.new(transport(options), options)
68
+ end
69
+
70
+ # Returns the transport session (Net::SSH::Transport::Session) for use
71
+ # in these tests. It is a fully functional SSH transport session, operating
72
+ # over a mock socket (#socket).
73
+ def transport(options = {})
74
+ @transport ||= Net::SSH::Transport::Session.new(
75
+ options[:host] || "localhost",
76
+ options.merge(kex: "test", host_key: "ssh-rsa", append_all_supported_algorithms: true, verify_host_key: :never, proxy: socket(options))
77
+ )
78
+ end
79
+
80
+ # First asserts that a story has been described (see #story). Then yields,
81
+ # and then asserts that all items described in the script have been
82
+ # processed. Typically, this is called immediately after a story has
83
+ # been built, and the SSH commands being tested are then executed within
84
+ # the block passed to this assertion.
85
+ def assert_scripted
86
+ raise "there is no script to be processed" if socket.script.events.empty?
87
+
88
+ Net::SSH::Test::Extensions::IO.with_test_extension { yield }
89
+ assert socket.script.events.empty?, "there should not be any remaining scripted events, but there are still" \
90
+ "#{socket.script.events.length} pending"
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,524 @@
1
+ require 'net/ssh/buffer'
2
+ require 'net/ssh/known_hosts'
3
+ require 'net/ssh/loggable'
4
+ require 'net/ssh/transport/cipher_factory'
5
+ require 'net/ssh/transport/constants'
6
+ require 'net/ssh/transport/hmac'
7
+ require 'net/ssh/transport/kex'
8
+ require 'net/ssh/transport/kex/curve25519_sha256_loader'
9
+ require 'net/ssh/transport/server_version'
10
+ require 'net/ssh/authentication/ed25519_loader'
11
+
12
+ module Net
13
+ module SSH
14
+ module Transport
15
+ # Implements the higher-level logic behind an SSH key-exchange. It handles
16
+ # both the initial exchange, as well as subsequent re-exchanges (as needed).
17
+ # It also encapsulates the negotiation of the algorithms, and provides a
18
+ # single point of access to the negotiated algorithms.
19
+ #
20
+ # You will never instantiate or reference this directly. It is used
21
+ # internally by the transport layer.
22
+ class Algorithms
23
+ include Loggable
24
+ include Constants
25
+
26
+ # Define the default algorithms, in order of preference, supported by Net::SSH.
27
+ DEFAULT_ALGORITHMS = {
28
+ host_key: %w[ecdsa-sha2-nistp521-cert-v01@openssh.com
29
+ ecdsa-sha2-nistp384-cert-v01@openssh.com
30
+ ecdsa-sha2-nistp256-cert-v01@openssh.com
31
+ ecdsa-sha2-nistp521
32
+ ecdsa-sha2-nistp384
33
+ ecdsa-sha2-nistp256
34
+ ssh-rsa-cert-v01@openssh.com
35
+ ssh-rsa-cert-v00@openssh.com
36
+ ssh-rsa
37
+ rsa-sha2-256
38
+ rsa-sha2-512],
39
+
40
+ kex: %w[ecdh-sha2-nistp521
41
+ ecdh-sha2-nistp384
42
+ ecdh-sha2-nistp256
43
+ diffie-hellman-group-exchange-sha256
44
+ diffie-hellman-group14-sha256
45
+ diffie-hellman-group14-sha1],
46
+
47
+ encryption: %w[aes256-ctr aes192-ctr aes128-ctr],
48
+
49
+ hmac: %w[hmac-sha2-512-etm@openssh.com hmac-sha2-256-etm@openssh.com
50
+ hmac-sha2-512 hmac-sha2-256
51
+ hmac-sha1]
52
+ }.freeze
53
+
54
+ if Net::SSH::Transport::ChaCha20Poly1305CipherLoader::LOADED
55
+ DEFAULT_ALGORITHMS[:encryption].unshift(
56
+ 'chacha20-poly1305@openssh.com'
57
+ )
58
+ end
59
+ if Net::SSH::Authentication::ED25519Loader::LOADED
60
+ DEFAULT_ALGORITHMS[:host_key].unshift(
61
+ 'ssh-ed25519-cert-v01@openssh.com',
62
+ 'ssh-ed25519'
63
+ )
64
+ end
65
+
66
+ if Net::SSH::Transport::Kex::Curve25519Sha256Loader::LOADED
67
+ DEFAULT_ALGORITHMS[:kex].unshift(
68
+ 'curve25519-sha256',
69
+ 'curve25519-sha256@libssh.org'
70
+ )
71
+ end
72
+
73
+ # Define all algorithms, with the deprecated, supported by Net::SSH.
74
+ ALGORITHMS = {
75
+ host_key: DEFAULT_ALGORITHMS[:host_key] + %w[ssh-dss],
76
+
77
+ kex: DEFAULT_ALGORITHMS[:kex] +
78
+ %w[diffie-hellman-group-exchange-sha1
79
+ diffie-hellman-group1-sha1],
80
+
81
+ encryption: DEFAULT_ALGORITHMS[:encryption] +
82
+ %w[aes256-cbc aes192-cbc aes128-cbc
83
+ rijndael-cbc@lysator.liu.se
84
+ blowfish-ctr blowfish-cbc
85
+ cast128-ctr cast128-cbc
86
+ 3des-ctr 3des-cbc
87
+ idea-cbc
88
+ none],
89
+
90
+ hmac: DEFAULT_ALGORITHMS[:hmac] +
91
+ %w[hmac-sha2-512-96 hmac-sha2-256-96
92
+ hmac-sha1-96
93
+ hmac-ripemd160 hmac-ripemd160@openssh.com
94
+ hmac-md5 hmac-md5-96
95
+ none],
96
+
97
+ compression: %w[none zlib@openssh.com zlib],
98
+ language: %w[]
99
+ }.freeze
100
+
101
+ # The underlying transport layer session that supports this object
102
+ attr_reader :session
103
+
104
+ # The hash of options used to initialize this object
105
+ attr_reader :options
106
+
107
+ # The kex algorithm to use settled on between the client and server.
108
+ attr_reader :kex
109
+
110
+ # The type of host key that will be used for this session.
111
+ attr_reader :host_key
112
+
113
+ # The type of the cipher to use to encrypt packets sent from the client to
114
+ # the server.
115
+ attr_reader :encryption_client
116
+
117
+ # The type of the cipher to use to decrypt packets arriving from the server.
118
+ attr_reader :encryption_server
119
+
120
+ # The type of HMAC to use to sign packets sent by the client.
121
+ attr_reader :hmac_client
122
+
123
+ # The type of HMAC to use to validate packets arriving from the server.
124
+ attr_reader :hmac_server
125
+
126
+ # The type of compression to use to compress packets being sent by the client.
127
+ attr_reader :compression_client
128
+
129
+ # The type of compression to use to decompress packets arriving from the server.
130
+ attr_reader :compression_server
131
+
132
+ # The language that will be used in messages sent by the client.
133
+ attr_reader :language_client
134
+
135
+ # The language that will be used in messages sent from the server.
136
+ attr_reader :language_server
137
+
138
+ # The hash of algorithms preferred by the client, which will be told to
139
+ # the server during algorithm negotiation.
140
+ attr_reader :algorithms
141
+
142
+ # The session-id for this session, as decided during the initial key exchange.
143
+ attr_reader :session_id
144
+
145
+ # Returns true if the given packet can be processed during a key-exchange.
146
+ def self.allowed_packet?(packet)
147
+ (1..4).include?(packet.type) ||
148
+ (6..19).include?(packet.type) ||
149
+ (21..49).include?(packet.type)
150
+ end
151
+
152
+ # Instantiates a new Algorithms object, and prepares the hash of preferred
153
+ # algorithms based on the options parameter and the ALGORITHMS constant.
154
+ def initialize(session, options = {})
155
+ @session = session
156
+ @logger = session.logger
157
+ @options = options
158
+ @algorithms = {}
159
+ @pending = @initialized = false
160
+ @client_packet = @server_packet = nil
161
+ prepare_preferred_algorithms!
162
+ end
163
+
164
+ # Start the algorithm negotation
165
+ def start
166
+ raise ArgumentError, "Cannot call start if it's negotiation started or done" if @pending || @initialized
167
+
168
+ send_kexinit
169
+ end
170
+
171
+ # Request a rekey operation. This will return immediately, and does not
172
+ # actually perform the rekey operation. It does cause the session to change
173
+ # state, however--until the key exchange finishes, no new packets will be
174
+ # processed.
175
+ def rekey!
176
+ @client_packet = @server_packet = nil
177
+ @initialized = false
178
+ send_kexinit
179
+ end
180
+
181
+ # Called by the transport layer when a KEXINIT packet is received, indicating
182
+ # that the server wants to exchange keys. This can be spontaneous, or it
183
+ # can be in response to a client-initiated rekey request (see #rekey!). Either
184
+ # way, this will block until the key exchange completes.
185
+ def accept_kexinit(packet)
186
+ info { "got KEXINIT from server" }
187
+ @server_data = parse_server_algorithm_packet(packet)
188
+ @server_packet = @server_data[:raw]
189
+ if !pending?
190
+ send_kexinit
191
+ else
192
+ proceed!
193
+ end
194
+ end
195
+
196
+ # A convenience method for accessing the list of preferred types for a
197
+ # specific algorithm (see #algorithms).
198
+ def [](key)
199
+ algorithms[key]
200
+ end
201
+
202
+ # Returns +true+ if a key-exchange is pending. This will be true from the
203
+ # moment either the client or server requests the key exchange, until the
204
+ # exchange completes. While an exchange is pending, only a limited number
205
+ # of packets are allowed, so event processing essentially stops during this
206
+ # period.
207
+ def pending?
208
+ @pending
209
+ end
210
+
211
+ # Returns true if no exchange is pending, and otherwise returns true or
212
+ # false depending on whether the given packet is of a type that is allowed
213
+ # during a key exchange.
214
+ def allow?(packet)
215
+ !pending? || Algorithms.allowed_packet?(packet)
216
+ end
217
+
218
+ # Returns true if the algorithms have been negotiated at all.
219
+ def initialized?
220
+ @initialized
221
+ end
222
+
223
+ def host_key_format
224
+ case host_key
225
+ when /^([a-z0-9-]+)-cert-v\d{2}@openssh.com$/
226
+ Regexp.last_match[1]
227
+ else
228
+ host_key
229
+ end
230
+ end
231
+
232
+ private
233
+
234
+ # Sends a KEXINIT packet to the server. If a server KEXINIT has already
235
+ # been received, this will then invoke #proceed! to proceed with the key
236
+ # exchange, otherwise it returns immediately (but sets the object to the
237
+ # pending state).
238
+ def send_kexinit
239
+ info { "sending KEXINIT" }
240
+ @pending = true
241
+ packet = build_client_algorithm_packet
242
+ @client_packet = packet.to_s
243
+ session.send_message(packet)
244
+ proceed! if @server_packet
245
+ end
246
+
247
+ # After both client and server have sent their KEXINIT packets, this
248
+ # will do the algorithm negotiation and key exchange. Once both finish,
249
+ # the object leaves the pending state and the method returns.
250
+ def proceed!
251
+ info { "negotiating algorithms" }
252
+ negotiate_algorithms
253
+ exchange_keys
254
+ @pending = false
255
+ end
256
+
257
+ # Prepares the list of preferred algorithms, based on the options hash
258
+ # that was given when the object was constructed, and the ALGORITHMS
259
+ # constant. Also, when determining the host_key type to use, the known
260
+ # hosts files are examined to see if the host has ever sent a host_key
261
+ # before, and if so, that key type is used as the preferred type for
262
+ # communicating with this server.
263
+ def prepare_preferred_algorithms!
264
+ options[:compression] = %w[zlib@openssh.com zlib] if options[:compression] == true
265
+
266
+ ALGORITHMS.each do |algorithm, supported|
267
+ algorithms[algorithm] = compose_algorithm_list(
268
+ supported, options[algorithm] || DEFAULT_ALGORITHMS[algorithm],
269
+ options[:append_all_supported_algorithms]
270
+ )
271
+ end
272
+
273
+ # for convention, make sure our list has the same keys as the server
274
+ # list
275
+
276
+ algorithms[:encryption_client ] = algorithms[:encryption_server ] = algorithms[:encryption]
277
+ algorithms[:hmac_client ] = algorithms[:hmac_server ] = algorithms[:hmac]
278
+ algorithms[:compression_client] = algorithms[:compression_server] = algorithms[:compression]
279
+ algorithms[:language_client ] = algorithms[:language_server ] = algorithms[:language]
280
+
281
+ if !options.key?(:host_key)
282
+ # make sure the host keys are specified in preference order, where any
283
+ # existing known key for the host has preference.
284
+
285
+ existing_keys = session.host_keys
286
+ host_keys = existing_keys.flat_map { |key| key.respond_to?(:ssh_types) ? key.ssh_types : [key.ssh_type] }.uniq
287
+ algorithms[:host_key].each do |name|
288
+ host_keys << name unless host_keys.include?(name)
289
+ end
290
+ algorithms[:host_key] = host_keys
291
+ end
292
+ end
293
+
294
+ # Composes the list of algorithms by taking supported algorithms and matching with supplied options.
295
+ def compose_algorithm_list(supported, option, append_all_supported_algorithms = false)
296
+ return supported.dup unless option
297
+
298
+ list = []
299
+ option = Array(option).compact.uniq
300
+
301
+ if option.first && option.first.start_with?('+', '-')
302
+ list = supported.dup
303
+
304
+ appends = option.select { |opt| opt.start_with?('+') }.map { |opt| opt[1..-1] }
305
+ deletions = option.select { |opt| opt.start_with?('-') }.map { |opt| opt[1..-1] }
306
+
307
+ list.concat(appends)
308
+
309
+ deletions.each do |opt|
310
+ if opt.include?('*')
311
+ opt_escaped = Regexp.escape(opt)
312
+ algo_re = /\A#{opt_escaped.gsub('\*', '[A-Za-z\d\-@\.]*')}\z/
313
+ list.delete_if { |existing_opt| algo_re.match(existing_opt) }
314
+ else
315
+ list.delete(opt)
316
+ end
317
+ end
318
+
319
+ list.uniq!
320
+ else
321
+ list = option
322
+
323
+ if append_all_supported_algorithms
324
+ supported.each { |name| list << name unless list.include?(name) }
325
+ end
326
+ end
327
+
328
+ unsupported = []
329
+ list.select! do |name|
330
+ is_supported = supported.include?(name)
331
+ unsupported << name unless is_supported
332
+ is_supported
333
+ end
334
+
335
+ lwarn { %(unsupported algorithm: `#{unsupported}') } unless unsupported.empty?
336
+
337
+ list
338
+ end
339
+
340
+ # Parses a KEXINIT packet from the server.
341
+ def parse_server_algorithm_packet(packet)
342
+ data = { raw: packet.content }
343
+
344
+ packet.read(16) # skip the cookie value
345
+
346
+ data[:kex] = packet.read_string.split(/,/)
347
+ data[:host_key] = packet.read_string.split(/,/)
348
+ data[:encryption_client] = packet.read_string.split(/,/)
349
+ data[:encryption_server] = packet.read_string.split(/,/)
350
+ data[:hmac_client] = packet.read_string.split(/,/)
351
+ data[:hmac_server] = packet.read_string.split(/,/)
352
+ data[:compression_client] = packet.read_string.split(/,/)
353
+ data[:compression_server] = packet.read_string.split(/,/)
354
+ data[:language_client] = packet.read_string.split(/,/)
355
+ data[:language_server] = packet.read_string.split(/,/)
356
+
357
+ # TODO: if first_kex_packet_follows, we need to try to skip the
358
+ # actual kexinit stuff and try to guess what the server is doing...
359
+ # need to read more about this scenario.
360
+ # first_kex_packet_follows = packet.read_bool
361
+
362
+ return data
363
+ end
364
+
365
+ # Given the #algorithms map of preferred algorithm types, this constructs
366
+ # a KEXINIT packet to send to the server. It does not actually send it,
367
+ # it simply builds the packet and returns it.
368
+ def build_client_algorithm_packet
369
+ kex = algorithms[:kex].join(",")
370
+ host_key = algorithms[:host_key].join(",")
371
+ encryption = algorithms[:encryption].join(",")
372
+ hmac = algorithms[:hmac].join(",")
373
+ compression = algorithms[:compression].join(",")
374
+ language = algorithms[:language].join(",")
375
+
376
+ Net::SSH::Buffer.from(:byte, KEXINIT,
377
+ :long, [rand(0xFFFFFFFF), rand(0xFFFFFFFF), rand(0xFFFFFFFF), rand(0xFFFFFFFF)],
378
+ :mstring, [kex, host_key, encryption, encryption, hmac, hmac],
379
+ :mstring, [compression, compression, language, language],
380
+ :bool, false, :long, 0)
381
+ end
382
+
383
+ # Given the parsed server KEX packet, and the client's preferred algorithm
384
+ # lists in #algorithms, determine which preferred algorithms each has
385
+ # in common and set those as the selected algorithms. If, for any algorithm,
386
+ # no type can be settled on, an exception is raised.
387
+ def negotiate_algorithms
388
+ @kex = negotiate(:kex)
389
+ @host_key = negotiate(:host_key)
390
+ @encryption_client = negotiate(:encryption_client)
391
+ @encryption_server = negotiate(:encryption_server)
392
+ @hmac_client = negotiate(:hmac_client)
393
+ @hmac_server = negotiate(:hmac_server)
394
+ @compression_client = negotiate(:compression_client)
395
+ @compression_server = negotiate(:compression_server)
396
+ @language_client = negotiate(:language_client) rescue ""
397
+ @language_server = negotiate(:language_server) rescue ""
398
+
399
+ debug do
400
+ "negotiated:\n" +
401
+ %i[kex host_key encryption_server encryption_client hmac_client hmac_server
402
+ compression_client compression_server language_client language_server].map do |key|
403
+ "* #{key}: #{instance_variable_get("@#{key}")}"
404
+ end.join("\n")
405
+ end
406
+ end
407
+
408
+ # Negotiates a single algorithm based on the preferences reported by the
409
+ # server and those set by the client. This is called by
410
+ # #negotiate_algorithms.
411
+ def negotiate(algorithm)
412
+ match = self[algorithm].find { |item| @server_data[algorithm].include?(item) }
413
+
414
+ if match.nil?
415
+ raise Net::SSH::Exception, "could not settle on #{algorithm} algorithm\n"\
416
+ "Server #{algorithm} preferences: #{@server_data[algorithm].join(',')}\n"\
417
+ "Client #{algorithm} preferences: #{self[algorithm].join(',')}"
418
+ end
419
+
420
+ return match
421
+ end
422
+
423
+ # Considers the sizes of the keys and block-sizes for the selected ciphers,
424
+ # and the lengths of the hmacs, and returns the largest as the byte requirement
425
+ # for the key-exchange algorithm.
426
+ def kex_byte_requirement
427
+ sizes = [8] # require at least 8 bytes
428
+
429
+ sizes.concat(CipherFactory.get_lengths(encryption_client))
430
+ sizes.concat(CipherFactory.get_lengths(encryption_server))
431
+
432
+ sizes << HMAC.key_length(hmac_client)
433
+ sizes << HMAC.key_length(hmac_server)
434
+
435
+ sizes.max
436
+ end
437
+
438
+ # Instantiates one of the Transport::Kex classes (based on the negotiated
439
+ # kex algorithm), and uses it to exchange keys. Then, the ciphers and
440
+ # HMACs are initialized and fed to the transport layer, to be used in
441
+ # further communication with the server.
442
+ def exchange_keys
443
+ debug { "exchanging keys" }
444
+
445
+ need_bytes = kex_byte_requirement
446
+ algorithm = Kex::MAP[kex].new(self, session,
447
+ client_version_string: Net::SSH::Transport::ServerVersion::PROTO_VERSION,
448
+ server_version_string: session.server_version.version,
449
+ server_algorithm_packet: @server_packet,
450
+ client_algorithm_packet: @client_packet,
451
+ need_bytes: need_bytes,
452
+ minimum_dh_bits: options[:minimum_dh_bits],
453
+ logger: logger)
454
+ result = algorithm.exchange_keys
455
+
456
+ secret = result[:shared_secret].to_ssh
457
+ hash = result[:session_id]
458
+ digester = result[:hashing_algorithm]
459
+
460
+ @session_id ||= hash
461
+
462
+ key = Proc.new { |salt| digester.digest(secret + hash + salt + @session_id) }
463
+
464
+ iv_client = key["A"]
465
+ iv_server = key["B"]
466
+ key_client = key["C"]
467
+ key_server = key["D"]
468
+ mac_key_client = key["E"]
469
+ mac_key_server = key["F"]
470
+
471
+ parameters = { shared: secret, hash: hash, digester: digester }
472
+
473
+ cipher_client = CipherFactory.get(
474
+ encryption_client,
475
+ parameters.merge(iv: iv_client, key: key_client, encrypt: true)
476
+ )
477
+ cipher_server = CipherFactory.get(
478
+ encryption_server,
479
+ parameters.merge(iv: iv_server, key: key_server, decrypt: true)
480
+ )
481
+
482
+ mac_client =
483
+ if cipher_client.implicit_mac?
484
+ cipher_client.implicit_mac
485
+ else
486
+ HMAC.get(hmac_client, mac_key_client, parameters)
487
+ end
488
+ mac_server =
489
+ if cipher_server.implicit_mac?
490
+ cipher_server.implicit_mac
491
+ else
492
+ HMAC.get(hmac_server, mac_key_server, parameters)
493
+ end
494
+
495
+ session.configure_client cipher: cipher_client, hmac: mac_client,
496
+ compression: normalize_compression_name(compression_client),
497
+ compression_level: options[:compression_level],
498
+ rekey_limit: options[:rekey_limit],
499
+ max_packets: options[:rekey_packet_limit],
500
+ max_blocks: options[:rekey_blocks_limit]
501
+
502
+ session.configure_server cipher: cipher_server, hmac: mac_server,
503
+ compression: normalize_compression_name(compression_server),
504
+ rekey_limit: options[:rekey_limit],
505
+ max_packets: options[:rekey_packet_limit],
506
+ max_blocks: options[:rekey_blocks_limit]
507
+
508
+ @initialized = true
509
+ end
510
+
511
+ # Given the SSH name for some compression algorithm, return a normalized
512
+ # name as a symbol.
513
+ def normalize_compression_name(name)
514
+ case name
515
+ when "none" then false
516
+ when "zlib" then :standard
517
+ when "zlib@openssh.com" then :delayed
518
+ else raise ArgumentError, "unknown compression type `#{name}'"
519
+ end
520
+ end
521
+ end
522
+ end
523
+ end
524
+ end