net-ssh 2.7.0 → 7.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (199) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +0 -0
  3. data/.dockerignore +6 -0
  4. data/.github/FUNDING.yml +1 -0
  5. data/.github/config/rubocop_linter_action.yml +4 -0
  6. data/.github/workflows/ci-with-docker.yml +44 -0
  7. data/.github/workflows/ci.yml +94 -0
  8. data/.github/workflows/rubocop.yml +16 -0
  9. data/.gitignore +15 -0
  10. data/.rubocop.yml +22 -0
  11. data/.rubocop_todo.yml +1081 -0
  12. data/CHANGES.txt +387 -0
  13. data/DEVELOPMENT.md +23 -0
  14. data/Dockerfile +29 -0
  15. data/Dockerfile.openssl3 +17 -0
  16. data/Gemfile +13 -0
  17. data/Gemfile.noed25519 +12 -0
  18. data/Gemfile.norbnacl +12 -0
  19. data/ISSUE_TEMPLATE.md +30 -0
  20. data/Manifest +4 -5
  21. data/README.md +303 -0
  22. data/Rakefile +174 -40
  23. data/SECURITY.md +4 -0
  24. data/THANKS.txt +25 -0
  25. data/appveyor.yml +58 -0
  26. data/docker-compose.yml +25 -0
  27. data/lib/net/ssh/authentication/agent.rb +279 -18
  28. data/lib/net/ssh/authentication/certificate.rb +183 -0
  29. data/lib/net/ssh/authentication/constants.rb +17 -15
  30. data/lib/net/ssh/authentication/ed25519.rb +184 -0
  31. data/lib/net/ssh/authentication/ed25519_loader.rb +31 -0
  32. data/lib/net/ssh/authentication/key_manager.rb +125 -54
  33. data/lib/net/ssh/authentication/methods/abstract.rb +67 -48
  34. data/lib/net/ssh/authentication/methods/hostbased.rb +34 -37
  35. data/lib/net/ssh/authentication/methods/keyboard_interactive.rb +19 -12
  36. data/lib/net/ssh/authentication/methods/none.rb +16 -19
  37. data/lib/net/ssh/authentication/methods/password.rb +56 -19
  38. data/lib/net/ssh/authentication/methods/publickey.rb +96 -55
  39. data/lib/net/ssh/authentication/pageant.rb +483 -246
  40. data/lib/net/ssh/authentication/pub_key_fingerprint.rb +43 -0
  41. data/lib/net/ssh/authentication/session.rb +138 -120
  42. data/lib/net/ssh/buffer.rb +399 -300
  43. data/lib/net/ssh/buffered_io.rb +154 -150
  44. data/lib/net/ssh/config.rb +361 -166
  45. data/lib/net/ssh/connection/channel.rb +640 -596
  46. data/lib/net/ssh/connection/constants.rb +29 -29
  47. data/lib/net/ssh/connection/event_loop.rb +123 -0
  48. data/lib/net/ssh/connection/keepalive.rb +59 -0
  49. data/lib/net/ssh/connection/session.rb +628 -548
  50. data/lib/net/ssh/connection/term.rb +125 -123
  51. data/lib/net/ssh/errors.rb +101 -95
  52. data/lib/net/ssh/key_factory.rb +198 -100
  53. data/lib/net/ssh/known_hosts.rb +221 -98
  54. data/lib/net/ssh/loggable.rb +50 -49
  55. data/lib/net/ssh/packet.rb +83 -79
  56. data/lib/net/ssh/prompt.rb +50 -81
  57. data/lib/net/ssh/proxy/command.rb +108 -60
  58. data/lib/net/ssh/proxy/errors.rb +12 -10
  59. data/lib/net/ssh/proxy/http.rb +82 -78
  60. data/lib/net/ssh/proxy/https.rb +50 -0
  61. data/lib/net/ssh/proxy/jump.rb +54 -0
  62. data/lib/net/ssh/proxy/socks4.rb +5 -8
  63. data/lib/net/ssh/proxy/socks5.rb +18 -20
  64. data/lib/net/ssh/service/forward.rb +383 -255
  65. data/lib/net/ssh/test/channel.rb +145 -136
  66. data/lib/net/ssh/test/extensions.rb +131 -110
  67. data/lib/net/ssh/test/kex.rb +34 -32
  68. data/lib/net/ssh/test/local_packet.rb +46 -44
  69. data/lib/net/ssh/test/packet.rb +89 -70
  70. data/lib/net/ssh/test/remote_packet.rb +32 -30
  71. data/lib/net/ssh/test/script.rb +156 -142
  72. data/lib/net/ssh/test/socket.rb +49 -48
  73. data/lib/net/ssh/test.rb +82 -77
  74. data/lib/net/ssh/transport/aes128_gcm.rb +40 -0
  75. data/lib/net/ssh/transport/aes256_gcm.rb +40 -0
  76. data/lib/net/ssh/transport/algorithms.rb +472 -348
  77. data/lib/net/ssh/transport/chacha20_poly1305_cipher.rb +117 -0
  78. data/lib/net/ssh/transport/chacha20_poly1305_cipher_loader.rb +17 -0
  79. data/lib/net/ssh/transport/cipher_factory.rb +124 -100
  80. data/lib/net/ssh/transport/constants.rb +32 -24
  81. data/lib/net/ssh/transport/ctr.rb +42 -22
  82. data/lib/net/ssh/transport/gcm_cipher.rb +207 -0
  83. data/lib/net/ssh/transport/hmac/abstract.rb +97 -63
  84. data/lib/net/ssh/transport/hmac/md5.rb +0 -2
  85. data/lib/net/ssh/transport/hmac/md5_96.rb +0 -2
  86. data/lib/net/ssh/transport/hmac/none.rb +0 -2
  87. data/lib/net/ssh/transport/hmac/ripemd160.rb +0 -2
  88. data/lib/net/ssh/transport/hmac/sha1.rb +0 -2
  89. data/lib/net/ssh/transport/hmac/sha1_96.rb +0 -2
  90. data/lib/net/ssh/transport/hmac/sha2_256.rb +7 -11
  91. data/lib/net/ssh/transport/hmac/sha2_256_96.rb +4 -8
  92. data/lib/net/ssh/transport/hmac/sha2_256_etm.rb +12 -0
  93. data/lib/net/ssh/transport/hmac/sha2_512.rb +6 -9
  94. data/lib/net/ssh/transport/hmac/sha2_512_96.rb +4 -8
  95. data/lib/net/ssh/transport/hmac/sha2_512_etm.rb +12 -0
  96. data/lib/net/ssh/transport/hmac.rb +14 -12
  97. data/lib/net/ssh/transport/identity_cipher.rb +54 -44
  98. data/lib/net/ssh/transport/kex/abstract.rb +130 -0
  99. data/lib/net/ssh/transport/kex/abstract5656.rb +72 -0
  100. data/lib/net/ssh/transport/kex/curve25519_sha256.rb +39 -0
  101. data/lib/net/ssh/transport/kex/curve25519_sha256_loader.rb +30 -0
  102. data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha1.rb +33 -40
  103. data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha256.rb +11 -0
  104. data/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb +119 -213
  105. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb +53 -61
  106. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha256.rb +5 -9
  107. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp256.rb +36 -90
  108. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp384.rb +18 -10
  109. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp521.rb +18 -10
  110. data/lib/net/ssh/transport/kex.rb +15 -12
  111. data/lib/net/ssh/transport/key_expander.rb +24 -20
  112. data/lib/net/ssh/transport/openssl.rb +161 -124
  113. data/lib/net/ssh/transport/openssl_cipher_extensions.rb +8 -0
  114. data/lib/net/ssh/transport/packet_stream.rb +246 -183
  115. data/lib/net/ssh/transport/server_version.rb +57 -51
  116. data/lib/net/ssh/transport/session.rb +307 -235
  117. data/lib/net/ssh/transport/state.rb +178 -176
  118. data/lib/net/ssh/verifiers/accept_new.rb +33 -0
  119. data/lib/net/ssh/verifiers/accept_new_or_local_tunnel.rb +33 -0
  120. data/lib/net/ssh/verifiers/always.rb +58 -0
  121. data/lib/net/ssh/verifiers/never.rb +19 -0
  122. data/lib/net/ssh/version.rb +57 -51
  123. data/lib/net/ssh.rb +140 -40
  124. data/net-ssh-public_cert.pem +21 -0
  125. data/net-ssh.gemspec +39 -184
  126. data/support/ssh_tunnel_bug.rb +5 -5
  127. data.tar.gz.sig +0 -0
  128. metadata +205 -99
  129. metadata.gz.sig +0 -0
  130. data/README.rdoc +0 -219
  131. data/Rudyfile +0 -96
  132. data/gem-public_cert.pem +0 -20
  133. data/lib/net/ssh/authentication/agent/java_pageant.rb +0 -85
  134. data/lib/net/ssh/authentication/agent/socket.rb +0 -170
  135. data/lib/net/ssh/ruby_compat.rb +0 -51
  136. data/lib/net/ssh/verifiers/lenient.rb +0 -30
  137. data/lib/net/ssh/verifiers/null.rb +0 -12
  138. data/lib/net/ssh/verifiers/secure.rb +0 -54
  139. data/lib/net/ssh/verifiers/strict.rb +0 -24
  140. data/setup.rb +0 -1585
  141. data/support/arcfour_check.rb +0 -20
  142. data/test/README.txt +0 -47
  143. data/test/authentication/methods/common.rb +0 -28
  144. data/test/authentication/methods/test_abstract.rb +0 -51
  145. data/test/authentication/methods/test_hostbased.rb +0 -114
  146. data/test/authentication/methods/test_keyboard_interactive.rb +0 -100
  147. data/test/authentication/methods/test_none.rb +0 -41
  148. data/test/authentication/methods/test_password.rb +0 -52
  149. data/test/authentication/methods/test_publickey.rb +0 -148
  150. data/test/authentication/test_agent.rb +0 -205
  151. data/test/authentication/test_key_manager.rb +0 -218
  152. data/test/authentication/test_session.rb +0 -108
  153. data/test/common.rb +0 -108
  154. data/test/configs/eqsign +0 -3
  155. data/test/configs/exact_match +0 -8
  156. data/test/configs/host_plus +0 -10
  157. data/test/configs/multihost +0 -4
  158. data/test/configs/nohost +0 -19
  159. data/test/configs/numeric_host +0 -4
  160. data/test/configs/send_env +0 -2
  161. data/test/configs/substitutes +0 -8
  162. data/test/configs/wild_cards +0 -14
  163. data/test/connection/test_channel.rb +0 -467
  164. data/test/connection/test_session.rb +0 -526
  165. data/test/known_hosts/github +0 -1
  166. data/test/manual/test_forward.rb +0 -223
  167. data/test/start/test_options.rb +0 -36
  168. data/test/start/test_transport.rb +0 -28
  169. data/test/test_all.rb +0 -11
  170. data/test/test_buffer.rb +0 -433
  171. data/test/test_buffered_io.rb +0 -63
  172. data/test/test_config.rb +0 -151
  173. data/test/test_key_factory.rb +0 -173
  174. data/test/test_known_hosts.rb +0 -13
  175. data/test/transport/hmac/test_md5.rb +0 -41
  176. data/test/transport/hmac/test_md5_96.rb +0 -27
  177. data/test/transport/hmac/test_none.rb +0 -34
  178. data/test/transport/hmac/test_ripemd160.rb +0 -36
  179. data/test/transport/hmac/test_sha1.rb +0 -36
  180. data/test/transport/hmac/test_sha1_96.rb +0 -27
  181. data/test/transport/hmac/test_sha2_256.rb +0 -37
  182. data/test/transport/hmac/test_sha2_256_96.rb +0 -27
  183. data/test/transport/hmac/test_sha2_512.rb +0 -37
  184. data/test/transport/hmac/test_sha2_512_96.rb +0 -27
  185. data/test/transport/kex/test_diffie_hellman_group14_sha1.rb +0 -13
  186. data/test/transport/kex/test_diffie_hellman_group1_sha1.rb +0 -146
  187. data/test/transport/kex/test_diffie_hellman_group_exchange_sha1.rb +0 -92
  188. data/test/transport/kex/test_diffie_hellman_group_exchange_sha256.rb +0 -34
  189. data/test/transport/kex/test_ecdh_sha2_nistp256.rb +0 -161
  190. data/test/transport/kex/test_ecdh_sha2_nistp384.rb +0 -38
  191. data/test/transport/kex/test_ecdh_sha2_nistp521.rb +0 -38
  192. data/test/transport/test_algorithms.rb +0 -330
  193. data/test/transport/test_cipher_factory.rb +0 -443
  194. data/test/transport/test_hmac.rb +0 -34
  195. data/test/transport/test_identity_cipher.rb +0 -40
  196. data/test/transport/test_packet_stream.rb +0 -1755
  197. data/test/transport/test_server_version.rb +0 -78
  198. data/test/transport/test_session.rb +0 -319
  199. data/test/transport/test_state.rb +0 -181
@@ -1,93 +1,62 @@
1
- module Net; module SSH
2
-
3
- # A basic prompt module that can be mixed into other objects. If HighLine is
4
- # installed, it will be used to display prompts and read input from the
5
- # user. Otherwise, the termios library will be used. If neither HighLine
6
- # nor termios is installed, a simple prompt that echos text in the clear
7
- # will be used.
8
-
9
- module PromptMethods
10
-
11
- # Defines the prompt method to use if the Highline library is installed.
12
- module Highline
13
- # Uses Highline#ask to present a prompt and accept input. If +echo+ is
14
- # +false+, the characters entered by the user will not be echoed to the
15
- # screen.
16
- def prompt(prompt, echo=true)
17
- @highline ||= ::HighLine.new
18
- @highline.ask(prompt + " ") { |q| q.echo = echo }
19
- end
20
- end
21
-
22
- # Defines the prompt method to use if the Termios library is installed.
23
- module Termios
24
- # Displays the prompt to $stdout. If +echo+ is false, the Termios
25
- # library will be used to disable keystroke echoing for the duration of
26
- # this method.
27
- def prompt(prompt, echo=true)
28
- $stdout.print(prompt)
29
- $stdout.flush
30
-
31
- set_echo(false) unless echo
32
- $stdin.gets.chomp
33
- ensure
34
- if !echo
35
- set_echo(true)
36
- $stdout.puts
37
- end
1
+ require 'io/console'
2
+
3
+ module Net
4
+ module SSH
5
+ # Default prompt implementation, called for asking password from user.
6
+ # It will never be instantiated directly, but will instead be created for
7
+ # you automatically.
8
+ #
9
+ # A custom prompt objects can implement caching, or different UI. The prompt
10
+ # object should implemnted a start method, which should return something implementing
11
+ # ask and success. Net::SSH uses it like:
12
+ #
13
+ # prompter = options[:password_prompt].start({type:'password'})
14
+ # while !ok && max_retries < 3
15
+ # user = prompter.ask("user: ", true)
16
+ # password = prompter.ask("password: ", false)
17
+ # ok = send(user, password)
18
+ # prompter.sucess if ok
19
+ # end
20
+ #
21
+ class Prompt
22
+ # factory
23
+ def self.default(options = {})
24
+ @default ||= new(options)
38
25
  end
39
26
 
40
- private
27
+ def initialize(options = {}); end
41
28
 
42
- # Enables or disables keystroke echoing using the Termios library.
43
- def set_echo(enable)
44
- term = ::Termios.getattr($stdin)
45
-
46
- if enable
47
- term.c_lflag |= (::Termios::ECHO | ::Termios::ICANON)
48
- else
49
- term.c_lflag &= ~::Termios::ECHO
29
+ # default prompt object implementation. More sophisticated implemenetations
30
+ # might implement caching.
31
+ class Prompter
32
+ def initialize(info)
33
+ if info[:type] == 'keyboard-interactive'
34
+ $stdout.puts(info[:name]) unless info[:name].empty?
35
+ $stdout.puts(info[:instruction]) unless info[:instruction].empty?
50
36
  end
51
-
52
- ::Termios.setattr($stdin, ::Termios::TCSANOW, term)
53
37
  end
54
- end
55
38
 
56
- # Defines the prompt method to use when neither Highline nor Termios are
57
- # installed.
58
- module Clear
59
- # Displays the prompt to $stdout and pulls the response from $stdin.
60
- # Text is always echoed in the clear, regardless of the +echo+ setting.
61
- # The first time a prompt is given and +echo+ is false, a warning will
62
- # be written to $stderr recommending that either Highline or Termios
63
- # be installed.
64
- def prompt(prompt, echo=true)
65
- @seen_warning ||= false
66
- if !echo && !@seen_warning
67
- $stderr.puts "Text will be echoed in the clear. Please install the HighLine or Termios libraries to suppress echoed text."
68
- @seen_warning = true
39
+ # ask input from user, a prompter might ask for multiple inputs
40
+ # (like user and password) in a single session.
41
+ def ask(prompt, echo = true)
42
+ $stdout.print(prompt)
43
+ $stdout.flush
44
+ ret = $stdin.noecho(&:gets).chomp
45
+ $stdout.print("\n")
46
+ ret
69
47
  end
70
48
 
71
- $stdout.print(prompt)
72
- $stdout.flush
73
- $stdin.gets.chomp
49
+ # success method will be called when the password was accepted
50
+ # It's a good time to save password asked to a cache.
51
+ def success; end
74
52
  end
75
- end
76
- end
77
53
 
78
- # Try to load Highline and Termios in turn, selecting the corresponding
79
- # PromptMethods module to use. If neither are available, choose PromptMethods::Clear.
80
- Prompt = begin
81
- require 'highline'
82
- HighLine.track_eof = false
83
- PromptMethods::Highline
84
- rescue LoadError
85
- begin
86
- require 'termios'
87
- PromptMethods::Termios
88
- rescue LoadError
89
- PromptMethods::Clear
54
+ # start password session. Multiple questions might be asked multiple times
55
+ # on the returned object. Info hash tries to uniquely identify the password
56
+ # session, so caching implementations can save passwords properly.
57
+ def start(info)
58
+ Prompter.new(info)
90
59
  end
91
60
  end
92
-
93
- end; end
61
+ end
62
+ end
@@ -1,75 +1,123 @@
1
1
  require 'socket'
2
+ require 'rubygems'
2
3
  require 'net/ssh/proxy/errors'
3
- require 'net/ssh/ruby_compat'
4
4
 
5
- module Net; module SSH; module Proxy
5
+ module Net
6
+ module SSH
7
+ module Proxy
8
+ # An implementation of a command proxy. To use it, instantiate it,
9
+ # then pass the instantiated object via the :proxy key to
10
+ # Net::SSH.start:
11
+ #
12
+ # require 'net/ssh/proxy/command'
13
+ #
14
+ # proxy = Net::SSH::Proxy::Command.new('ssh relay nc %h %p')
15
+ # Net::SSH.start('host', 'user', :proxy => proxy) do |ssh|
16
+ # ...
17
+ # end
18
+ class Command
19
+ # The command line template
20
+ attr_reader :command_line_template
6
21
 
7
- # An implementation of a command proxy. To use it, instantiate it,
8
- # then pass the instantiated object via the :proxy key to
9
- # Net::SSH.start:
10
- #
11
- # require 'net/ssh/proxy/command'
12
- #
13
- # proxy = Net::SSH::Proxy::Command.new('ssh relay nc %h %p')
14
- # Net::SSH.start('host', 'user', :proxy => proxy) do |ssh|
15
- # ...
16
- # end
17
- class Command
22
+ # The command line for the session
23
+ attr_reader :command_line
18
24
 
19
- # The command line template
20
- attr_reader :command_line_template
25
+ # Timeout in seconds in open, defaults to 60
26
+ attr_accessor :timeout
21
27
 
22
- # The command line for the session
23
- attr_reader :command_line
28
+ # Create a new socket factory that tunnels via a command executed
29
+ # with the user's shell, which is composed from the given command
30
+ # template. In the command template, `%h' will be substituted by
31
+ # the host name to connect and `%p' by the port.
32
+ def initialize(command_line_template)
33
+ @command_line_template = command_line_template
34
+ @command_line = nil
35
+ @timeout = 60
36
+ end
24
37
 
25
- # Create a new socket factory that tunnels via a command executed
26
- # with the user's shell, which is composed from the given command
27
- # template. In the command template, `%h' will be substituted by
28
- # the host name to connect and `%p' by the port.
29
- def initialize(command_line_template)
30
- @command_line_template = command_line_template
31
- @command_line = nil
32
- end
38
+ # Return a new socket connected to the given host and port via the
39
+ # proxy that was requested when the socket factory was instantiated.
40
+ def open(host, port, connection_options = nil)
41
+ command_line = @command_line_template.gsub(/%(.)/) {
42
+ case $1
43
+ when 'h'
44
+ host
45
+ when 'p'
46
+ port.to_s
47
+ when 'r'
48
+ remote_user = connection_options && connection_options[:remote_user]
49
+ if remote_user
50
+ remote_user
51
+ else
52
+ raise ArgumentError, "remote user name not available"
53
+ end
54
+ when '%'
55
+ '%'
56
+ else
57
+ raise ArgumentError, "unknown key: #{$1}"
58
+ end
59
+ }
60
+ begin
61
+ io = IO.popen(command_line, "r+")
62
+ begin
63
+ if result = IO.select([io], nil, [io], @timeout)
64
+ if result.last.any? || io.eof?
65
+ raise "command failed"
66
+ end
67
+ else
68
+ raise "command timed out"
69
+ end
70
+ rescue StandardError
71
+ close_on_error(io)
72
+ raise
73
+ end
74
+ rescue StandardError => e
75
+ raise ConnectError, "#{e}: #{command_line}"
76
+ end
77
+ @command_line = command_line
78
+ if Gem.win_platform?
79
+ # read_nonblock and write_nonblock are not available on Windows
80
+ # pipe. Use sysread and syswrite as a replacement works.
81
+ def io.send(data, flag)
82
+ syswrite(data)
83
+ end
33
84
 
34
- # Return a new socket connected to the given host and port via the
35
- # proxy that was requested when the socket factory was instantiated.
36
- def open(host, port)
37
- command_line = @command_line_template.gsub(/%(.)/) {
38
- case $1
39
- when 'h'
40
- host
41
- when 'p'
42
- port.to_s
43
- when '%'
44
- '%'
45
- else
46
- raise ArgumentError, "unknown key: #{$1}"
47
- end
48
- }
49
- begin
50
- io = IO.popen(command_line, "r+")
51
- if result = Net::SSH::Compat.io_select([io], nil, [io], 60)
52
- if result.last.any?
53
- raise "command failed"
85
+ def io.recv(size)
86
+ sysread(size)
87
+ end
88
+ else
89
+ def io.send(data, flag)
90
+ begin
91
+ result = write_nonblock(data)
92
+ rescue IO::WaitWritable, Errno::EINTR
93
+ IO.select(nil, [self])
94
+ retry
95
+ end
96
+ result
97
+ end
98
+
99
+ def io.recv(size)
100
+ begin
101
+ result = read_nonblock(size)
102
+ rescue IO::WaitReadable, Errno::EINTR
103
+ timeout_in_seconds = 20
104
+ if IO.select([self], nil, [self], timeout_in_seconds) == nil
105
+ raise "Unexpected spurious read wakeup"
106
+ end
107
+
108
+ retry
109
+ end
110
+ result
111
+ end
54
112
  end
55
- else
56
- raise "command timed out"
57
- end
58
- rescue => e
59
- raise ConnectError, "#{e}: #{command_line}"
60
- end
61
- @command_line = command_line
62
- class << io
63
- def send(data, flag)
64
- write_nonblock(data)
113
+ io
65
114
  end
66
115
 
67
- def recv(size)
68
- read_nonblock(size)
116
+ def close_on_error(io)
117
+ Process.kill('TERM', io.pid)
118
+ Thread.new { io.close }
69
119
  end
70
120
  end
71
- io
72
121
  end
73
122
  end
74
-
75
- end; end; end
123
+ end
@@ -1,14 +1,16 @@
1
1
  require 'net/ssh/errors'
2
2
 
3
- module Net; module SSH; module Proxy
3
+ module Net
4
+ module SSH
5
+ module Proxy
6
+ # A general exception class for all Proxy errors.
7
+ class Error < Net::SSH::Exception; end
4
8
 
5
- # A general exception class for all Proxy errors.
6
- class Error < Net::SSH::Exception; end
9
+ # Used for reporting proxy connection errors.
10
+ class ConnectError < Error; end
7
11
 
8
- # Used for reporting proxy connection errors.
9
- class ConnectError < Error; end
10
-
11
- # Used when the server doesn't recognize the user's credentials.
12
- class UnauthorizedError < Error; end
13
-
14
- end; end; end
12
+ # Used when the server doesn't recognize the user's credentials.
13
+ class UnauthorizedError < Error; end
14
+ end
15
+ end
16
+ end
@@ -1,94 +1,98 @@
1
1
  require 'socket'
2
2
  require 'net/ssh/proxy/errors'
3
3
 
4
- module Net; module SSH; module Proxy
5
-
6
- # An implementation of an HTTP proxy. To use it, instantiate it, then
7
- # pass the instantiated object via the :proxy key to Net::SSH.start:
8
- #
9
- # require 'net/ssh/proxy/http'
10
- #
11
- # proxy = Net::SSH::Proxy::HTTP.new('proxy.host', proxy_port)
12
- # Net::SSH.start('host', 'user', :proxy => proxy) do |ssh|
13
- # ...
14
- # end
15
- #
16
- # If the proxy requires authentication, you can pass :user and :password
17
- # to the proxy's constructor:
18
- #
19
- # proxy = Net::SSH::Proxy::HTTP.new('proxy.host', proxy_port,
20
- # :user => "user", :password => "password")
21
- #
22
- # Note that HTTP digest authentication is not supported; Basic only at
23
- # this point.
24
- class HTTP
25
-
26
- # The hostname or IP address of the HTTP proxy.
27
- attr_reader :proxy_host
28
-
29
- # The port number of the proxy.
30
- attr_reader :proxy_port
31
-
32
- # The map of additional options that were given to the object at
33
- # initialization.
34
- attr_reader :options
35
-
36
- # Create a new socket factory that tunnels via the given host and
37
- # port. The +options+ parameter is a hash of additional settings that
38
- # can be used to tweak this proxy connection. Specifically, the following
39
- # options are supported:
40
- #
41
- # * :user => the user name to use when authenticating to the proxy
42
- # * :password => the password to use when authenticating
43
- def initialize(proxy_host, proxy_port=80, options={})
44
- @proxy_host = proxy_host
45
- @proxy_port = proxy_port
46
- @options = options
47
- end
4
+ module Net
5
+ module SSH
6
+ module Proxy
7
+ # An implementation of an HTTP proxy. To use it, instantiate it, then
8
+ # pass the instantiated object via the :proxy key to Net::SSH.start:
9
+ #
10
+ # require 'net/ssh/proxy/http'
11
+ #
12
+ # proxy = Net::SSH::Proxy::HTTP.new('proxy_host', proxy_port)
13
+ # Net::SSH.start('host', 'user', :proxy => proxy) do |ssh|
14
+ # ...
15
+ # end
16
+ #
17
+ # If the proxy requires authentication, you can pass :user and :password
18
+ # to the proxy's constructor:
19
+ #
20
+ # proxy = Net::SSH::Proxy::HTTP.new('proxy_host', proxy_port,
21
+ # :user => "user", :password => "password")
22
+ #
23
+ # Note that HTTP digest authentication is not supported; Basic only at
24
+ # this point.
25
+ class HTTP
26
+ # The hostname or IP address of the HTTP proxy.
27
+ attr_reader :proxy_host
28
+
29
+ # The port number of the proxy.
30
+ attr_reader :proxy_port
31
+
32
+ # The map of additional options that were given to the object at
33
+ # initialization.
34
+ attr_reader :options
35
+
36
+ # Create a new socket factory that tunnels via the given host and
37
+ # port. The +options+ parameter is a hash of additional settings that
38
+ # can be used to tweak this proxy connection. Specifically, the following
39
+ # options are supported:
40
+ #
41
+ # * :user => the user name to use when authenticating to the proxy
42
+ # * :password => the password to use when authenticating
43
+ def initialize(proxy_host, proxy_port = 80, options = {})
44
+ @proxy_host = proxy_host
45
+ @proxy_port = proxy_port
46
+ @options = options
47
+ end
48
48
 
49
- # Return a new socket connected to the given host and port via the
50
- # proxy that was requested when the socket factory was instantiated.
51
- def open(host, port)
52
- socket = TCPSocket.new(proxy_host, proxy_port)
53
- socket.write "CONNECT #{host}:#{port} HTTP/1.0\r\n"
49
+ # Return a new socket connected to the given host and port via the
50
+ # proxy that was requested when the socket factory was instantiated.
51
+ def open(host, port, connection_options)
52
+ socket = establish_connection(connection_options[:timeout])
53
+ socket.write "CONNECT #{host}:#{port} HTTP/1.1\r\n"
54
+ socket.write "Host: #{host}:#{port}\r\n"
54
55
 
55
- if options[:user]
56
- credentials = ["#{options[:user]}:#{options[:password]}"].pack("m*").gsub(/\s/, "")
57
- socket.write "Proxy-Authorization: Basic #{credentials}\r\n"
58
- end
56
+ if options[:user]
57
+ credentials = ["#{options[:user]}:#{options[:password]}"].pack("m*").gsub(/\s/, "")
58
+ socket.write "Proxy-Authorization: Basic #{credentials}\r\n"
59
+ end
59
60
 
60
- socket.write "\r\n"
61
+ socket.write "\r\n"
61
62
 
62
- resp = parse_response(socket)
63
+ resp = parse_response(socket)
63
64
 
64
- return socket if resp[:code] == 200
65
+ return socket if resp[:code] == 200
65
66
 
66
- socket.close
67
- raise ConnectError, resp.inspect
68
- end
69
-
70
- private
67
+ socket.close
68
+ raise ConnectError, resp.inspect
69
+ end
71
70
 
72
- def parse_response(socket)
73
- version, code, reason = socket.gets.chomp.split(/ /, 3)
74
- headers = {}
71
+ protected
75
72
 
76
- while (line = socket.gets) && (line.chomp! != "")
77
- name, value = line.split(/:/, 2)
78
- headers[name.strip] = value.strip
73
+ def establish_connection(connect_timeout)
74
+ Socket.tcp(proxy_host, proxy_port, nil, nil,
75
+ connect_timeout: connect_timeout)
79
76
  end
80
77
 
81
- if headers["Content-Length"]
82
- body = socket.read(headers["Content-Length"].to_i)
83
- end
78
+ def parse_response(socket)
79
+ version, code, reason = socket.gets.chomp.split(/ /, 3)
80
+ headers = {}
84
81
 
85
- return { :version => version,
86
- :code => code.to_i,
87
- :reason => reason,
88
- :headers => headers,
89
- :body => body }
90
- end
82
+ while (line = socket.gets) && (line.chomp! != "")
83
+ name, value = line.split(/:/, 2)
84
+ headers[name.strip] = value.strip
85
+ end
91
86
 
92
- end
87
+ body = socket.read(headers["Content-Length"].to_i) if headers["Content-Length"]
93
88
 
94
- end; end; end
89
+ return { version: version,
90
+ code: code.to_i,
91
+ reason: reason,
92
+ headers: headers,
93
+ body: body }
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,50 @@
1
+ require 'socket'
2
+ require 'openssl'
3
+ require 'net/ssh/proxy/errors'
4
+ require 'net/ssh/proxy/http'
5
+
6
+ module Net
7
+ module SSH
8
+ module Proxy
9
+ # A specialization of the HTTP proxy which encrypts the whole connection
10
+ # using OpenSSL. This has the advantage that proxy authentication
11
+ # information is not sent in plaintext.
12
+ class HTTPS < HTTP
13
+ # Create a new socket factory that tunnels via the given host and
14
+ # port. The +options+ parameter is a hash of additional settings that
15
+ # can be used to tweak this proxy connection. In addition to the options
16
+ # taken by Net::SSH::Proxy::HTTP it supports:
17
+ #
18
+ # * :ssl_context => the SSL configuration to use for the connection
19
+ def initialize(proxy_host, proxy_port = 80, options = {})
20
+ @ssl_context = options.delete(:ssl_context) ||
21
+ OpenSSL::SSL::SSLContext.new
22
+ super(proxy_host, proxy_port, options)
23
+ end
24
+
25
+ protected
26
+
27
+ # Shim to make OpenSSL::SSL::SSLSocket behave like a regular TCPSocket
28
+ # for all intents and purposes of Net::SSH::BufferedIo
29
+ module SSLSocketCompatibility
30
+ def self.extended(object) # :nodoc:
31
+ object.define_singleton_method(:recv, object.method(:sysread))
32
+ object.sync_close = true
33
+ end
34
+
35
+ def send(data, _opts)
36
+ syswrite(data)
37
+ end
38
+ end
39
+
40
+ def establish_connection(connect_timeout)
41
+ plain_socket = super(connect_timeout)
42
+ OpenSSL::SSL::SSLSocket.new(plain_socket, @ssl_context).tap do |socket|
43
+ socket.extend(SSLSocketCompatibility)
44
+ socket.connect
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,54 @@
1
+ require 'uri'
2
+ require 'net/ssh/proxy/command'
3
+
4
+ module Net
5
+ module SSH
6
+ module Proxy
7
+ # An implementation of a jump proxy. To use it, instantiate it,
8
+ # then pass the instantiated object via the :proxy key to
9
+ # Net::SSH.start:
10
+ #
11
+ # require 'net/ssh/proxy/jump'
12
+ #
13
+ # proxy = Net::SSH::Proxy::Jump.new('user@proxy')
14
+ # Net::SSH.start('host', 'user', :proxy => proxy) do |ssh|
15
+ # ...
16
+ # end
17
+ class Jump < Command
18
+ # The jump proxies
19
+ attr_reader :jump_proxies
20
+
21
+ # Create a new socket factory that tunnels via multiple jump proxes as
22
+ # [user@]host[:port].
23
+ def initialize(jump_proxies)
24
+ @jump_proxies = jump_proxies
25
+ end
26
+
27
+ # Return a new socket connected to the given host and port via the jump
28
+ # proxy that was requested when the socket factory was instantiated.
29
+ def open(host, port, connection_options = nil)
30
+ build_proxy_command_equivalent(connection_options)
31
+ super
32
+ end
33
+
34
+ # We cannot build the ProxyCommand template until we know if the :config
35
+ # option was specified during `Net::SSH.start`.
36
+ def build_proxy_command_equivalent(connection_options = nil)
37
+ first_jump, extra_jumps = jump_proxies.split(",", 2)
38
+ config = connection_options && connection_options[:config]
39
+ uri = URI.parse("ssh://#{first_jump}")
40
+
41
+ template = "ssh".dup
42
+ template << " -l #{uri.user}" if uri.user
43
+ template << " -p #{uri.port}" if uri.port
44
+ template << " -J #{extra_jumps}" if extra_jumps
45
+ template << " -F #{config}" if config != true && config
46
+ template << " -W %h:%p "
47
+ template << uri.host
48
+
49
+ @command_line_template = template
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end