net-ssh 3.2.0 → 7.2.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (210) hide show
  1. checksums.yaml +5 -5
  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 +93 -0
  8. data/.github/workflows/rubocop.yml +16 -0
  9. data/.gitignore +13 -0
  10. data/.rubocop.yml +22 -0
  11. data/.rubocop_todo.yml +1081 -0
  12. data/CHANGES.txt +237 -7
  13. data/DEVELOPMENT.md +23 -0
  14. data/Dockerfile +27 -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 +298 -0
  22. data/Rakefile +125 -74
  23. data/SECURITY.md +4 -0
  24. data/appveyor.yml +58 -0
  25. data/docker-compose.yml +23 -0
  26. data/lib/net/ssh/authentication/agent.rb +279 -18
  27. data/lib/net/ssh/authentication/certificate.rb +183 -0
  28. data/lib/net/ssh/authentication/constants.rb +17 -15
  29. data/lib/net/ssh/authentication/ed25519.rb +186 -0
  30. data/lib/net/ssh/authentication/ed25519_loader.rb +31 -0
  31. data/lib/net/ssh/authentication/key_manager.rb +86 -39
  32. data/lib/net/ssh/authentication/methods/abstract.rb +67 -48
  33. data/lib/net/ssh/authentication/methods/hostbased.rb +34 -37
  34. data/lib/net/ssh/authentication/methods/keyboard_interactive.rb +13 -13
  35. data/lib/net/ssh/authentication/methods/none.rb +16 -19
  36. data/lib/net/ssh/authentication/methods/password.rb +27 -17
  37. data/lib/net/ssh/authentication/methods/publickey.rb +96 -55
  38. data/lib/net/ssh/authentication/pageant.rb +471 -367
  39. data/lib/net/ssh/authentication/pub_key_fingerprint.rb +43 -0
  40. data/lib/net/ssh/authentication/session.rb +131 -121
  41. data/lib/net/ssh/buffer.rb +399 -300
  42. data/lib/net/ssh/buffered_io.rb +154 -150
  43. data/lib/net/ssh/config.rb +308 -185
  44. data/lib/net/ssh/connection/channel.rb +635 -613
  45. data/lib/net/ssh/connection/constants.rb +29 -29
  46. data/lib/net/ssh/connection/event_loop.rb +123 -0
  47. data/lib/net/ssh/connection/keepalive.rb +55 -51
  48. data/lib/net/ssh/connection/session.rb +620 -551
  49. data/lib/net/ssh/connection/term.rb +125 -123
  50. data/lib/net/ssh/errors.rb +101 -99
  51. data/lib/net/ssh/key_factory.rb +197 -105
  52. data/lib/net/ssh/known_hosts.rb +214 -127
  53. data/lib/net/ssh/loggable.rb +50 -49
  54. data/lib/net/ssh/packet.rb +83 -79
  55. data/lib/net/ssh/prompt.rb +50 -81
  56. data/lib/net/ssh/proxy/command.rb +105 -90
  57. data/lib/net/ssh/proxy/errors.rb +12 -10
  58. data/lib/net/ssh/proxy/http.rb +82 -79
  59. data/lib/net/ssh/proxy/https.rb +50 -0
  60. data/lib/net/ssh/proxy/jump.rb +54 -0
  61. data/lib/net/ssh/proxy/socks4.rb +2 -6
  62. data/lib/net/ssh/proxy/socks5.rb +14 -17
  63. data/lib/net/ssh/service/forward.rb +370 -317
  64. data/lib/net/ssh/test/channel.rb +145 -136
  65. data/lib/net/ssh/test/extensions.rb +131 -110
  66. data/lib/net/ssh/test/kex.rb +34 -32
  67. data/lib/net/ssh/test/local_packet.rb +46 -44
  68. data/lib/net/ssh/test/packet.rb +89 -70
  69. data/lib/net/ssh/test/remote_packet.rb +32 -30
  70. data/lib/net/ssh/test/script.rb +156 -142
  71. data/lib/net/ssh/test/socket.rb +49 -48
  72. data/lib/net/ssh/test.rb +82 -77
  73. data/lib/net/ssh/transport/algorithms.rb +462 -359
  74. data/lib/net/ssh/transport/chacha20_poly1305_cipher.rb +117 -0
  75. data/lib/net/ssh/transport/chacha20_poly1305_cipher_loader.rb +17 -0
  76. data/lib/net/ssh/transport/cipher_factory.rb +122 -99
  77. data/lib/net/ssh/transport/constants.rb +32 -24
  78. data/lib/net/ssh/transport/ctr.rb +42 -22
  79. data/lib/net/ssh/transport/hmac/abstract.rb +81 -63
  80. data/lib/net/ssh/transport/hmac/md5.rb +0 -2
  81. data/lib/net/ssh/transport/hmac/md5_96.rb +0 -2
  82. data/lib/net/ssh/transport/hmac/none.rb +0 -2
  83. data/lib/net/ssh/transport/hmac/ripemd160.rb +0 -2
  84. data/lib/net/ssh/transport/hmac/sha1.rb +0 -2
  85. data/lib/net/ssh/transport/hmac/sha1_96.rb +0 -2
  86. data/lib/net/ssh/transport/hmac/sha2_256.rb +7 -11
  87. data/lib/net/ssh/transport/hmac/sha2_256_96.rb +4 -8
  88. data/lib/net/ssh/transport/hmac/sha2_256_etm.rb +12 -0
  89. data/lib/net/ssh/transport/hmac/sha2_512.rb +6 -9
  90. data/lib/net/ssh/transport/hmac/sha2_512_96.rb +4 -8
  91. data/lib/net/ssh/transport/hmac/sha2_512_etm.rb +12 -0
  92. data/lib/net/ssh/transport/hmac.rb +14 -12
  93. data/lib/net/ssh/transport/identity_cipher.rb +54 -44
  94. data/lib/net/ssh/transport/kex/abstract.rb +130 -0
  95. data/lib/net/ssh/transport/kex/abstract5656.rb +72 -0
  96. data/lib/net/ssh/transport/kex/curve25519_sha256.rb +39 -0
  97. data/lib/net/ssh/transport/kex/curve25519_sha256_loader.rb +30 -0
  98. data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha1.rb +33 -40
  99. data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha256.rb +11 -0
  100. data/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb +119 -213
  101. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb +53 -61
  102. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha256.rb +5 -9
  103. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp256.rb +36 -90
  104. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp384.rb +18 -10
  105. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp521.rb +18 -10
  106. data/lib/net/ssh/transport/kex.rb +15 -12
  107. data/lib/net/ssh/transport/key_expander.rb +24 -20
  108. data/lib/net/ssh/transport/openssl.rb +161 -124
  109. data/lib/net/ssh/transport/openssl_cipher_extensions.rb +8 -0
  110. data/lib/net/ssh/transport/packet_stream.rb +246 -185
  111. data/lib/net/ssh/transport/server_version.rb +55 -56
  112. data/lib/net/ssh/transport/session.rb +306 -255
  113. data/lib/net/ssh/transport/state.rb +178 -176
  114. data/lib/net/ssh/verifiers/accept_new.rb +33 -0
  115. data/lib/net/ssh/verifiers/accept_new_or_local_tunnel.rb +33 -0
  116. data/lib/net/ssh/verifiers/always.rb +58 -0
  117. data/lib/net/ssh/verifiers/never.rb +19 -0
  118. data/lib/net/ssh/version.rb +55 -53
  119. data/lib/net/ssh.rb +111 -47
  120. data/net-ssh-public_cert.pem +18 -18
  121. data/net-ssh.gemspec +38 -205
  122. data/support/ssh_tunnel_bug.rb +5 -5
  123. data.tar.gz.sig +0 -0
  124. metadata +173 -118
  125. metadata.gz.sig +0 -0
  126. data/.travis.yml +0 -18
  127. data/README.rdoc +0 -182
  128. data/lib/net/ssh/authentication/agent/java_pageant.rb +0 -85
  129. data/lib/net/ssh/authentication/agent/socket.rb +0 -178
  130. data/lib/net/ssh/ruby_compat.rb +0 -46
  131. data/lib/net/ssh/verifiers/lenient.rb +0 -30
  132. data/lib/net/ssh/verifiers/null.rb +0 -12
  133. data/lib/net/ssh/verifiers/secure.rb +0 -52
  134. data/lib/net/ssh/verifiers/strict.rb +0 -24
  135. data/setup.rb +0 -1585
  136. data/support/arcfour_check.rb +0 -20
  137. data/test/README.txt +0 -18
  138. data/test/authentication/methods/common.rb +0 -28
  139. data/test/authentication/methods/test_abstract.rb +0 -51
  140. data/test/authentication/methods/test_hostbased.rb +0 -114
  141. data/test/authentication/methods/test_keyboard_interactive.rb +0 -121
  142. data/test/authentication/methods/test_none.rb +0 -41
  143. data/test/authentication/methods/test_password.rb +0 -95
  144. data/test/authentication/methods/test_publickey.rb +0 -148
  145. data/test/authentication/test_agent.rb +0 -232
  146. data/test/authentication/test_key_manager.rb +0 -240
  147. data/test/authentication/test_session.rb +0 -107
  148. data/test/common.rb +0 -125
  149. data/test/configs/auth_off +0 -5
  150. data/test/configs/auth_on +0 -4
  151. data/test/configs/empty +0 -0
  152. data/test/configs/eqsign +0 -3
  153. data/test/configs/exact_match +0 -8
  154. data/test/configs/host_plus +0 -10
  155. data/test/configs/multihost +0 -4
  156. data/test/configs/negative_match +0 -6
  157. data/test/configs/nohost +0 -19
  158. data/test/configs/numeric_host +0 -4
  159. data/test/configs/proxy_remote_user +0 -2
  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 -487
  164. data/test/connection/test_session.rb +0 -564
  165. data/test/integration/README.txt +0 -17
  166. data/test/integration/Vagrantfile +0 -12
  167. data/test/integration/common.rb +0 -63
  168. data/test/integration/playbook.yml +0 -56
  169. data/test/integration/test_forward.rb +0 -637
  170. data/test/integration/test_id_rsa_keys.rb +0 -96
  171. data/test/integration/test_proxy.rb +0 -93
  172. data/test/known_hosts/github +0 -1
  173. data/test/known_hosts/github_hash +0 -1
  174. data/test/manual/test_pageant.rb +0 -37
  175. data/test/start/test_connection.rb +0 -53
  176. data/test/start/test_options.rb +0 -57
  177. data/test/start/test_transport.rb +0 -28
  178. data/test/start/test_user_nil.rb +0 -27
  179. data/test/test_all.rb +0 -12
  180. data/test/test_buffer.rb +0 -433
  181. data/test/test_buffered_io.rb +0 -63
  182. data/test/test_config.rb +0 -268
  183. data/test/test_key_factory.rb +0 -191
  184. data/test/test_known_hosts.rb +0 -66
  185. data/test/transport/hmac/test_md5.rb +0 -41
  186. data/test/transport/hmac/test_md5_96.rb +0 -27
  187. data/test/transport/hmac/test_none.rb +0 -34
  188. data/test/transport/hmac/test_ripemd160.rb +0 -36
  189. data/test/transport/hmac/test_sha1.rb +0 -36
  190. data/test/transport/hmac/test_sha1_96.rb +0 -27
  191. data/test/transport/hmac/test_sha2_256.rb +0 -37
  192. data/test/transport/hmac/test_sha2_256_96.rb +0 -27
  193. data/test/transport/hmac/test_sha2_512.rb +0 -37
  194. data/test/transport/hmac/test_sha2_512_96.rb +0 -27
  195. data/test/transport/kex/test_diffie_hellman_group14_sha1.rb +0 -13
  196. data/test/transport/kex/test_diffie_hellman_group1_sha1.rb +0 -150
  197. data/test/transport/kex/test_diffie_hellman_group_exchange_sha1.rb +0 -96
  198. data/test/transport/kex/test_diffie_hellman_group_exchange_sha256.rb +0 -19
  199. data/test/transport/kex/test_ecdh_sha2_nistp256.rb +0 -161
  200. data/test/transport/kex/test_ecdh_sha2_nistp384.rb +0 -38
  201. data/test/transport/kex/test_ecdh_sha2_nistp521.rb +0 -38
  202. data/test/transport/test_algorithms.rb +0 -328
  203. data/test/transport/test_cipher_factory.rb +0 -443
  204. data/test/transport/test_hmac.rb +0 -34
  205. data/test/transport/test_identity_cipher.rb +0 -40
  206. data/test/transport/test_packet_stream.rb +0 -1762
  207. data/test/transport/test_server_version.rb +0 -74
  208. data/test/transport/test_session.rb +0 -331
  209. data/test/transport/test_state.rb +0 -181
  210. data/test/verifiers/test_secure.rb +0 -40
@@ -1,373 +1,426 @@
1
- # -*- coding: utf-8 -*-
2
1
  require 'net/ssh/loggable'
3
2
 
4
- module Net; module SSH; module Service
5
-
6
- # This class implements various port forwarding services for use by
7
- # Net::SSH clients. The Forward class should never need to be instantiated
8
- # directly; instead, it should be accessed via the singleton instance
9
- # returned by Connection::Session#forward:
10
- #
11
- # ssh.forward.local(1234, "www.capify.org", 80)
12
- class Forward
13
- include Loggable
14
-
15
- # The underlying connection service instance that the port-forwarding
16
- # services employ.
17
- attr_reader :session
18
-
19
- # A simple class for representing a requested remote forwarded port.
20
- Remote = Struct.new(:host, :port) #:nodoc:
21
-
22
- # Instantiates a new Forward service instance atop the given connection
23
- # service session. This will register new channel open handlers to handle
24
- # the specialized channels that the SSH port forwarding protocols employ.
25
- def initialize(session)
26
- @session = session
27
- self.logger = session.logger
28
- @remote_forwarded_ports = {}
29
- @local_forwarded_ports = {}
30
- @agent_forwarded = false
31
-
32
- session.on_open_channel('forwarded-tcpip', &method(:forwarded_tcpip))
33
- session.on_open_channel('auth-agent', &method(:auth_agent_channel))
34
- session.on_open_channel('auth-agent@openssh.com', &method(:auth_agent_channel))
35
- end
36
-
37
- # Starts listening for connections on the local host, and forwards them
38
- # to the specified remote host/port via the SSH connection. This method
39
- # accepts either three or four arguments. When four arguments are given,
40
- # they are:
41
- #
42
- # * the local address to bind to
43
- # * the local port to listen on
44
- # * the remote host to forward connections to
45
- # * the port on the remote host to connect to
46
- #
47
- # If three arguments are given, it is as if the local bind address is
48
- # "127.0.0.1", and the rest are applied as above.
49
- #
50
- # To request an ephemeral port on the remote server, provide 0 (zero) for
51
- # the port number. In all cases, this method will return the port that
52
- # has been assigned.
53
- #
54
- # ssh.forward.local(1234, "www.capify.org", 80)
55
- # assigned_port = ssh.forward.local("0.0.0.0", 0, "www.capify.org", 80)
56
- def local(*args)
57
- if args.length < 3 || args.length > 4
58
- raise ArgumentError, "expected 3 or 4 parameters, got #{args.length}"
59
- end
3
+ module Net
4
+ module SSH
5
+ module Service
6
+ # This class implements various port forwarding services for use by
7
+ # Net::SSH clients. The Forward class should never need to be instantiated
8
+ # directly; instead, it should be accessed via the singleton instance
9
+ # returned by Connection::Session#forward:
10
+ #
11
+ # ssh.forward.local(1234, "www.capify.org", 80)
12
+ class Forward
13
+ include Loggable
14
+
15
+ # The underlying connection service instance that the port-forwarding
16
+ # services employ.
17
+ attr_reader :session
18
+
19
+ # A simple class for representing a requested remote forwarded port.
20
+ Remote = Struct.new(:host, :port) # :nodoc:
21
+
22
+ # Instantiates a new Forward service instance atop the given connection
23
+ # service session. This will register new channel open handlers to handle
24
+ # the specialized channels that the SSH port forwarding protocols employ.
25
+ def initialize(session)
26
+ @session = session
27
+ self.logger = session.logger
28
+ @remote_forwarded_ports = {}
29
+ @local_forwarded_ports = {}
30
+ @agent_forwarded = false
31
+ @local_forwarded_sockets = {}
32
+
33
+ session.on_open_channel('forwarded-tcpip', &method(:forwarded_tcpip))
34
+ session.on_open_channel('auth-agent', &method(:auth_agent_channel))
35
+ session.on_open_channel('auth-agent@openssh.com', &method(:auth_agent_channel))
36
+ end
60
37
 
61
- local_port_type = :long
38
+ # Starts listening for connections on the local host, and forwards them
39
+ # to the specified remote host/port via the SSH connection. This method
40
+ # accepts either three or four arguments. When four arguments are given,
41
+ # they are:
42
+ #
43
+ # * the local address to bind to
44
+ # * the local port to listen on
45
+ # * the remote host to forward connections to
46
+ # * the port on the remote host to connect to
47
+ #
48
+ # If three arguments are given, it is as if the local bind address is
49
+ # "127.0.0.1", and the rest are applied as above.
50
+ #
51
+ # To request an ephemeral port on the remote server, provide 0 (zero) for
52
+ # the port number. In all cases, this method will return the port that
53
+ # has been assigned.
54
+ #
55
+ # ssh.forward.local(1234, "www.capify.org", 80)
56
+ # assigned_port = ssh.forward.local("0.0.0.0", 0, "www.capify.org", 80)
57
+ def local(*args)
58
+ if args.length < 3 || args.length > 4
59
+ raise ArgumentError, "expected 3 or 4 parameters, got #{args.length}"
60
+ end
62
61
 
63
- socket = begin
64
- if defined?(UNIXServer) and args.first.class == UNIXServer
65
- local_port_type = :string
66
- args.shift
67
- else
68
- bind_address = "127.0.0.1"
69
- bind_address = args.shift if args.first.is_a?(String) && args.first =~ /\D/
70
- local_port = args.shift.to_i
71
62
  local_port_type = :long
72
- TCPServer.new(bind_address, local_port)
73
- end
74
- end
75
63
 
76
- local_port = socket.addr[1] if local_port == 0 # ephemeral port was requested
77
- remote_host = args.shift
78
- remote_port = args.shift.to_i
64
+ socket = begin
65
+ if defined?(UNIXServer) and args.first.class == UNIXServer
66
+ local_port_type = :string
67
+ args.shift
68
+ else
69
+ bind_address = "127.0.0.1"
70
+ bind_address = args.shift if args.first.is_a?(String) && args.first =~ /\D/
71
+ local_port = args.shift.to_i
72
+ local_port_type = :long
73
+ TCPServer.new(bind_address, local_port)
74
+ end
75
+ end
76
+
77
+ local_port = socket.addr[1] if local_port == 0 # ephemeral port was requested
78
+ remote_host = args.shift
79
+ remote_port = args.shift.to_i
79
80
 
80
- @local_forwarded_ports[[local_port, bind_address]] = socket
81
+ @local_forwarded_ports[[local_port, bind_address]] = socket
81
82
 
82
- session.listen_to(socket) do |server|
83
- client = server.accept
84
- debug { "received connection on #{socket}" }
83
+ session.listen_to(socket) do |server|
84
+ client = server.accept
85
+ debug { "received connection on #{socket}" }
85
86
 
86
- channel = session.open_channel("direct-tcpip", :string, remote_host, :long, remote_port, :string, bind_address, local_port_type, local_port) do |achannel|
87
- achannel.info { "direct channel established" }
88
- end
87
+ channel = session.open_channel("direct-tcpip", :string, remote_host, :long,
88
+ remote_port, :string, bind_address, local_port_type, local_port) do |achannel|
89
+ achannel.info { "direct channel established" }
90
+ end
89
91
 
90
- prepare_client(client, channel, :local)
92
+ prepare_client(client, channel, :local)
91
93
 
92
- channel.on_open_failed do |ch, code, description|
93
- channel.error { "could not establish direct channel: #{description} (#{code})" }
94
- session.stop_listening_to(channel[:socket])
95
- channel[:socket].close
94
+ channel.on_open_failed do |ch, code, description|
95
+ channel.error { "could not establish direct channel: #{description} (#{code})" }
96
+ session.stop_listening_to(channel[:socket])
97
+ channel[:socket].close
98
+ end
99
+ end
100
+
101
+ local_port
96
102
  end
97
- end
98
103
 
99
- local_port
100
- end
104
+ # Terminates an active local forwarded port.
105
+ #
106
+ # ssh.forward.cancel_local(1234)
107
+ # ssh.forward.cancel_local(1234, "0.0.0.0")
108
+ def cancel_local(port, bind_address = "127.0.0.1")
109
+ socket = @local_forwarded_ports.delete([port, bind_address])
110
+ socket.shutdown rescue nil
111
+ socket.close rescue nil
112
+ session.stop_listening_to(socket)
113
+ end
101
114
 
102
- # Terminates an active local forwarded port. If no such forwarded port
103
- # exists, this will raise an exception. Otherwise, the forwarded connection
104
- # is terminated.
105
- #
106
- # ssh.forward.cancel_local(1234)
107
- # ssh.forward.cancel_local(1234, "0.0.0.0")
108
- def cancel_local(port, bind_address="127.0.0.1")
109
- socket = @local_forwarded_ports.delete([port, bind_address])
110
- socket.shutdown rescue nil
111
- socket.close rescue nil
112
- session.stop_listening_to(socket)
113
- end
115
+ # Returns a list of all active locally forwarded ports. The returned value
116
+ # is an array of arrays, where each element is a two-element tuple
117
+ # consisting of the local port and bind address corresponding to the
118
+ # forwarding port.
119
+ def active_locals
120
+ @local_forwarded_ports.keys
121
+ end
114
122
 
115
- # Returns a list of all active locally forwarded ports. The returned value
116
- # is an array of arrays, where each element is a two-element tuple
117
- # consisting of the local port and bind address corresponding to the
118
- # forwarding port.
119
- def active_locals
120
- @local_forwarded_ports.keys
121
- end
123
+ # Starts listening for connections on the local host, and forwards them
124
+ # to the specified remote socket via the SSH connection. This will
125
+ # (re)create the local socket file. The remote server needs to have the
126
+ # socket file already available.
127
+ #
128
+ # ssh.forward.local_socket('/tmp/local.sock', '/tmp/remote.sock')
129
+ def local_socket(local_socket_path, remote_socket_path)
130
+ File.delete(local_socket_path) if File.exist?(local_socket_path)
131
+ socket = Socket.unix_server_socket(local_socket_path)
132
+
133
+ @local_forwarded_sockets[local_socket_path] = socket
134
+
135
+ session.listen_to(socket) do |server|
136
+ client = server.accept[0]
137
+ debug { "received connection on #{socket}" }
138
+
139
+ channel = session.open_channel("direct-streamlocal@openssh.com",
140
+ :string, remote_socket_path,
141
+ :string, nil,
142
+ :long, 0) do |achannel|
143
+ achannel.info { "direct channel established" }
144
+ end
122
145
 
123
- # Requests that all connections on the given remote-port be forwarded via
124
- # the local host to the given port/host. The last argument describes the
125
- # bind address on the remote host, and defaults to 127.0.0.1.
126
- #
127
- # This method will return immediately, but the port will not actually be
128
- # forwarded immediately. If the remote server is not able to begin the
129
- # listener for this request, an exception will be raised asynchronously.
130
- #
131
- # To request an ephemeral port on the remote server, provide 0 (zero) for
132
- # the port number. The assigned port will show up in the # #active_remotes
133
- # list.
134
- #
135
- # remote_host is interpreted by the server per RFC 4254, which has these
136
- # special values:
137
- #
138
- # - "" means that connections are to be accepted on all protocol
139
- # families supported by the SSH implementation.
140
- # - "0.0.0.0" means to listen on all IPv4 addresses.
141
- # - "::" means to listen on all IPv6 addresses.
142
- # - "localhost" means to listen on all protocol families supported by
143
- # the SSH implementation on loopback addresses only ([RFC3330] and
144
- # [RFC3513]).
145
- # - "127.0.0.1" and "::1" indicate listening on the loopback
146
- # interfaces for IPv4 and IPv6, respectively.
147
- #
148
- # You may pass a block that will be called when the the port forward
149
- # request receives a response. This block will be passed the remote_port
150
- # that was actually bound to, or nil if the binding failed. If the block
151
- # returns :no_exception, the "failed binding" exception will not be thrown.
152
- #
153
- # If you want to block until the port is active, you could do something
154
- # like this:
155
- #
156
- # got_remote_port = nil
157
- # remote(port, host, remote_port, remote_host) do |actual_remote_port|
158
- # got_remote_port = actual_remote_port || :error
159
- # :no_exception # will yield the exception on my own thread
160
- # end
161
- # session.loop { !got_remote_port }
162
- # if got_remote_port == :error
163
- # raise Net::SSH::Exception, "remote forwarding request failed"
164
- # end
165
- #
166
- def remote(port, host, remote_port, remote_host="127.0.0.1")
167
- session.send_global_request("tcpip-forward", :string, remote_host, :long, remote_port) do |success, response|
168
- if success
169
- remote_port = response.read_long if remote_port == 0
170
- debug { "remote forward from remote #{remote_host}:#{remote_port} to #{host}:#{port} established" }
171
- @remote_forwarded_ports[[remote_port, remote_host]] = Remote.new(host, port)
172
- yield remote_port, remote_host if block_given?
173
- else
174
- instruction = if block_given?
175
- yield :error
146
+ prepare_client(client, channel, :local)
147
+
148
+ channel.on_open_failed do |ch, code, description|
149
+ channel.error { "could not establish direct channel: #{description} (#{code})" }
150
+ session.stop_listening_to(channel[:socket])
151
+ channel[:socket].close
152
+ end
176
153
  end
177
- unless instruction == :no_exception
178
- error { "remote forwarding request failed" }
179
- raise Net::SSH::Exception, "remote forwarding request failed"
154
+
155
+ local_socket_path
156
+ end
157
+
158
+ # Terminates an active local forwarded socket.
159
+ #
160
+ # ssh.forward.cancel_local_socket('/tmp/foo.sock')
161
+ def cancel_local_socket(local_socket_path)
162
+ socket = @local_forwarded_sockets.delete(local_socket_path)
163
+ socket.shutdown rescue nil
164
+ socket.close rescue nil
165
+ session.stop_listening_to(socket)
166
+ end
167
+
168
+ # Returns a list of all active locally forwarded sockets. The returned value
169
+ # is an array of Unix domain socket file paths.
170
+ def active_local_sockets
171
+ @local_forwarded_sockets.keys
172
+ end
173
+
174
+ # Requests that all connections on the given remote-port be forwarded via
175
+ # the local host to the given port/host. The last argument describes the
176
+ # bind address on the remote host, and defaults to 127.0.0.1.
177
+ #
178
+ # This method will return immediately, but the port will not actually be
179
+ # forwarded immediately. If the remote server is not able to begin the
180
+ # listener for this request, an exception will be raised asynchronously.
181
+ #
182
+ # To request an ephemeral port on the remote server, provide 0 (zero) for
183
+ # the port number. The assigned port will show up in the # #active_remotes
184
+ # list.
185
+ #
186
+ # remote_host is interpreted by the server per RFC 4254, which has these
187
+ # special values:
188
+ #
189
+ # - "" means that connections are to be accepted on all protocol
190
+ # families supported by the SSH implementation.
191
+ # - "0.0.0.0" means to listen on all IPv4 addresses.
192
+ # - "::" means to listen on all IPv6 addresses.
193
+ # - "localhost" means to listen on all protocol families supported by
194
+ # the SSH implementation on loopback addresses only ([RFC3330] and
195
+ # [RFC3513]).
196
+ # - "127.0.0.1" and "::1" indicate listening on the loopback
197
+ # interfaces for IPv4 and IPv6, respectively.
198
+ #
199
+ # You may pass a block that will be called when the the port forward
200
+ # request receives a response. This block will be passed the remote_port
201
+ # that was actually bound to, or nil if the binding failed. If the block
202
+ # returns :no_exception, the "failed binding" exception will not be thrown.
203
+ #
204
+ # If you want to block until the port is active, you could do something
205
+ # like this:
206
+ #
207
+ # got_remote_port = nil
208
+ # remote(port, host, remote_port, remote_host) do |actual_remote_port|
209
+ # got_remote_port = actual_remote_port || :error
210
+ # :no_exception # will yield the exception on my own thread
211
+ # end
212
+ # session.loop { !got_remote_port }
213
+ # if got_remote_port == :error
214
+ # raise Net::SSH::Exception, "remote forwarding request failed"
215
+ # end
216
+ #
217
+ def remote(port, host, remote_port, remote_host = "127.0.0.1")
218
+ session.send_global_request("tcpip-forward", :string, remote_host, :long, remote_port) do |success, response|
219
+ if success
220
+ remote_port = response.read_long if remote_port == 0
221
+ debug { "remote forward from remote #{remote_host}:#{remote_port} to #{host}:#{port} established" }
222
+ @remote_forwarded_ports[[remote_port, remote_host]] = Remote.new(host, port)
223
+ yield remote_port, remote_host if block_given?
224
+ else
225
+ instruction = if block_given?
226
+ yield :error
227
+ end
228
+ unless instruction == :no_exception
229
+ error { "remote forwarding request failed" }
230
+ raise Net::SSH::Exception, "remote forwarding request failed"
231
+ end
232
+ end
180
233
  end
181
234
  end
182
- end
183
- end
184
235
 
185
- # an alias, for token backwards compatibility with the 1.x API
186
- alias :remote_to :remote
187
-
188
- # Requests that a remote forwarded port be cancelled. The remote forwarded
189
- # port on the remote host, bound to the given address on the remote host,
190
- # will be terminated, but not immediately. This method returns immediately
191
- # after queueing the request to be sent to the server. If for some reason
192
- # the port cannot be cancelled, an exception will be raised (asynchronously).
193
- #
194
- # If you want to know when the connection has been cancelled, it will no
195
- # longer be present in the #active_remotes list. If you want to block until
196
- # the port is no longer active, you could do something like this:
197
- #
198
- # ssh.forward.cancel_remote(1234, "0.0.0.0")
199
- # ssh.loop { ssh.forward.active_remotes.include?([1234, "0.0.0.0"]) }
200
- def cancel_remote(port, host="127.0.0.1")
201
- session.send_global_request("cancel-tcpip-forward", :string, host, :long, port) do |success, response|
202
- if success
203
- @remote_forwarded_ports.delete([port, host])
204
- else
205
- raise Net::SSH::Exception, "could not cancel remote forward request on #{host}:#{port}"
236
+ # an alias, for token backwards compatibility with the 1.x API
237
+ alias :remote_to :remote
238
+
239
+ # Requests that a remote forwarded port be cancelled. The remote forwarded
240
+ # port on the remote host, bound to the given address on the remote host,
241
+ # will be terminated, but not immediately. This method returns immediately
242
+ # after queueing the request to be sent to the server. If for some reason
243
+ # the port cannot be cancelled, an exception will be raised (asynchronously).
244
+ #
245
+ # If you want to know when the connection has been cancelled, it will no
246
+ # longer be present in the #active_remotes list. If you want to block until
247
+ # the port is no longer active, you could do something like this:
248
+ #
249
+ # ssh.forward.cancel_remote(1234, "0.0.0.0")
250
+ # ssh.loop { ssh.forward.active_remotes.include?([1234, "0.0.0.0"]) }
251
+ def cancel_remote(port, host = "127.0.0.1")
252
+ session.send_global_request("cancel-tcpip-forward", :string, host, :long, port) do |success, response|
253
+ if success
254
+ @remote_forwarded_ports.delete([port, host])
255
+ else
256
+ raise Net::SSH::Exception, "could not cancel remote forward request on #{host}:#{port}"
257
+ end
258
+ end
206
259
  end
207
- end
208
- end
209
260
 
210
- # Returns all active forwarded remote ports. The returned value is an
211
- # array of two-element tuples, where the first element is the port on the
212
- # remote host and the second is the bind address.
213
- def active_remotes
214
- @remote_forwarded_ports.keys
215
- end
261
+ # Returns all active forwarded remote ports. The returned value is an
262
+ # array of two-element tuples, where the first element is the port on the
263
+ # remote host and the second is the bind address.
264
+ def active_remotes
265
+ @remote_forwarded_ports.keys
266
+ end
216
267
 
217
- # Returns all active remote forwarded ports and where they forward to. The
218
- # returned value is a hash from [<forwarding port on the local host>, <local forwarding address>]
219
- # to [<port on the remote host>, <remote bind address>].
220
- def active_remote_destinations
221
- @remote_forwarded_ports.inject({}) do |result, (remote, local)|
222
- result[[local.port, local.host]] = remote
223
- result
224
- end
225
- end
268
+ # Returns all active remote forwarded ports and where they forward to. The
269
+ # returned value is a hash from [<forwarding port on the local host>, <local forwarding address>]
270
+ # to [<port on the remote host>, <remote bind address>].
271
+ def active_remote_destinations
272
+ @remote_forwarded_ports.each_with_object({}) do |(remote, local), result|
273
+ result[[local.port, local.host]] = remote
274
+ end
275
+ end
226
276
 
227
- # Enables SSH agent forwarding on the given channel. The forwarded agent
228
- # will remain active even after the channel closes--the channel is only
229
- # used as the transport for enabling the forwarded connection. You should
230
- # never need to call this directly--it is called automatically the first
231
- # time a session channel is opened, when the connection was created with
232
- # :forward_agent set to true:
233
- #
234
- # Net::SSH.start("remote.host", "me", :forward_agent => true) do |ssh|
235
- # ssh.open_channel do |ch|
236
- # # agent will be automatically forwarded by this point
237
- # end
238
- # ssh.loop
239
- # end
240
- def agent(channel)
241
- return if @agent_forwarded
242
- @agent_forwarded = true
243
-
244
- channel.send_channel_request("auth-agent-req@openssh.com") do |achannel, success|
245
- if success
246
- debug { "authentication agent forwarding is active" }
247
- else
248
- achannel.send_channel_request("auth-agent-req") do |a2channel, success2|
249
- if success2
277
+ # Enables SSH agent forwarding on the given channel. The forwarded agent
278
+ # will remain active even after the channel closes--the channel is only
279
+ # used as the transport for enabling the forwarded connection. You should
280
+ # never need to call this directly--it is called automatically the first
281
+ # time a session channel is opened, when the connection was created with
282
+ # :forward_agent set to true:
283
+ #
284
+ # Net::SSH.start("remote.host", "me", :forward_agent => true) do |ssh|
285
+ # ssh.open_channel do |ch|
286
+ # # agent will be automatically forwarded by this point
287
+ # end
288
+ # ssh.loop
289
+ # end
290
+ def agent(channel)
291
+ return if @agent_forwarded
292
+
293
+ @agent_forwarded = true
294
+
295
+ channel.send_channel_request("auth-agent-req@openssh.com") do |achannel, success|
296
+ if success
250
297
  debug { "authentication agent forwarding is active" }
251
298
  else
252
- error { "could not establish forwarding of authentication agent" }
299
+ achannel.send_channel_request("auth-agent-req") do |a2channel, success2|
300
+ if success2
301
+ debug { "authentication agent forwarding is active" }
302
+ else
303
+ error { "could not establish forwarding of authentication agent" }
304
+ end
305
+ end
253
306
  end
254
307
  end
255
308
  end
256
- end
257
- end
258
309
 
259
- private
310
+ private
260
311
 
261
- # Perform setup operations that are common to all forwarded channels.
262
- # +client+ is a socket, +channel+ is the channel that was just created,
263
- # and +type+ is an arbitrary string describing the type of the channel.
264
- def prepare_client(client, channel, type)
265
- client.extend(Net::SSH::BufferedIo)
266
- client.extend(Net::SSH::ForwardedBufferedIo)
267
- client.logger = logger
312
+ # Perform setup operations that are common to all forwarded channels.
313
+ # +client+ is a socket, +channel+ is the channel that was just created,
314
+ # and +type+ is an arbitrary string describing the type of the channel.
315
+ def prepare_client(client, channel, type)
316
+ client.extend(Net::SSH::BufferedIo)
317
+ client.extend(Net::SSH::ForwardedBufferedIo)
318
+ client.logger = logger
268
319
 
269
- session.listen_to(client)
270
- channel[:socket] = client
320
+ session.listen_to(client)
321
+ channel[:socket] = client
271
322
 
272
- channel.on_data do |ch, data|
273
- debug { "data:#{data.length} on #{type} forwarded channel" }
274
- ch[:socket].enqueue(data)
275
- end
323
+ channel.on_data do |ch, data|
324
+ debug { "data:#{data.length} on #{type} forwarded channel" }
325
+ ch[:socket].enqueue(data)
326
+ end
276
327
 
277
- channel.on_eof do |ch|
278
- debug { "eof #{type} on #{type} forwarded channel" }
279
- begin
280
- ch[:socket].send_pending
281
- ch[:socket].shutdown Socket::SHUT_WR
282
- rescue IOError => e
283
- if e.message =~ /closed/ then
328
+ channel.on_eof do |ch|
329
+ debug { "eof #{type} on #{type} forwarded channel" }
330
+ begin
331
+ ch[:socket].send_pending
332
+ ch[:socket].shutdown Socket::SHUT_WR
333
+ rescue IOError => e
334
+ if e.message =~ /closed/ then
335
+ debug { "epipe in on_eof => shallowing exception:#{e}" }
336
+ else
337
+ raise
338
+ end
339
+ rescue Errno::EPIPE => e
284
340
  debug { "epipe in on_eof => shallowing exception:#{e}" }
285
- else
286
- raise
341
+ rescue Errno::ENOTCONN => e
342
+ debug { "enotconn in on_eof => shallowing exception:#{e}" }
287
343
  end
288
- rescue Errno::EPIPE => e
289
- debug { "epipe in on_eof => shallowing exception:#{e}" }
290
- rescue Errno::ENOTCONN => e
291
- debug { "enotconn in on_eof => shallowing exception:#{e}" }
292
344
  end
293
- end
294
345
 
295
- channel.on_close do |ch|
296
- debug { "closing #{type} forwarded channel" }
297
- ch[:socket].close if !client.closed?
298
- session.stop_listening_to(ch[:socket])
299
- end
346
+ channel.on_close do |ch|
347
+ debug { "closing #{type} forwarded channel" }
348
+ ch[:socket].close if !client.closed?
349
+ session.stop_listening_to(ch[:socket])
350
+ end
300
351
 
301
- channel.on_process do |ch|
302
- if ch[:socket].closed?
303
- ch.info { "#{type} forwarded connection closed" }
304
- ch.close
305
- elsif ch[:socket].available > 0
306
- data = ch[:socket].read_available(8192)
307
- ch.debug { "read #{data.length} bytes from client, sending over #{type} forwarded connection" }
308
- ch.send_data(data)
352
+ channel.on_process do |ch|
353
+ if ch[:socket].closed?
354
+ ch.info { "#{type} forwarded connection closed" }
355
+ ch.close
356
+ elsif ch[:socket].available > 0
357
+ data = ch[:socket].read_available(8192)
358
+ ch.debug { "read #{data.length} bytes from client, sending over #{type} forwarded connection" }
359
+ ch.send_data(data)
360
+ end
309
361
  end
310
362
  end
311
- end
312
363
 
313
- # not a real socket, so use a simpler behaviour
314
- def prepare_simple_client(client, channel, type)
315
- channel[:socket] = client
364
+ # not a real socket, so use a simpler behaviour
365
+ def prepare_simple_client(client, channel, type)
366
+ channel[:socket] = client
316
367
 
317
- channel.on_data do |ch, data|
318
- ch.debug { "data:#{data.length} on #{type} forwarded channel" }
319
- ch[:socket].send(data)
320
- end
368
+ channel.on_data do |ch, data|
369
+ ch.debug { "data:#{data.length} on #{type} forwarded channel" }
370
+ ch[:socket].send(data)
371
+ end
321
372
 
322
- channel.on_process do |ch|
323
- data = ch[:socket].read(8192)
324
- if data
325
- ch.debug { "read #{data.length} bytes from client, sending over #{type} forwarded connection" }
326
- ch.send_data(data)
373
+ channel.on_process do |ch|
374
+ data = ch[:socket].read(8192)
375
+ if data
376
+ ch.debug { "read #{data.length} bytes from client, sending over #{type} forwarded connection" }
377
+ ch.send_data(data)
378
+ end
327
379
  end
328
380
  end
329
- end
330
381
 
331
- # The callback used when a new "forwarded-tcpip" channel is requested
332
- # by the server. This will open a new socket to the host/port specified
333
- # when the forwarded connection was first requested.
334
- def forwarded_tcpip(session, channel, packet)
335
- connected_address = packet.read_string
336
- connected_port = packet.read_long
337
- originator_address = packet.read_string
338
- originator_port = packet.read_long
382
+ # The callback used when a new "forwarded-tcpip" channel is requested
383
+ # by the server. This will open a new socket to the host/port specified
384
+ # when the forwarded connection was first requested.
385
+ def forwarded_tcpip(session, channel, packet)
386
+ connected_address = packet.read_string
387
+ connected_port = packet.read_long
388
+ originator_address = packet.read_string
389
+ originator_port = packet.read_long
390
+
391
+ puts "REMOTE 0: #{connected_port} #{connected_address} #{originator_address} #{originator_port}"
392
+ remote = @remote_forwarded_ports[[connected_port, connected_address]]
393
+ if remote.nil?
394
+ raise Net::SSH::ChannelOpenFailed.new(1, "unknown request from remote forwarded connection on #{connected_address}:#{connected_port}")
395
+ end
339
396
 
340
- remote = @remote_forwarded_ports[[connected_port, connected_address]]
397
+ puts "REMOTE: #{remote.host} #{remote.port}"
398
+ client = TCPSocket.new(remote.host, remote.port)
399
+ info { "connected #{connected_address}:#{connected_port} originator #{originator_address}:#{originator_port}" }
341
400
 
342
- if remote.nil?
343
- raise Net::SSH::ChannelOpenFailed.new(1, "unknown request from remote forwarded connection on #{connected_address}:#{connected_port}")
401
+ prepare_client(client, channel, :remote)
402
+ rescue SocketError => err
403
+ raise Net::SSH::ChannelOpenFailed.new(2, "could not connect to remote host (#{remote.host}:#{remote.port}): #{err.message}")
344
404
  end
345
405
 
346
- client = TCPSocket.new(remote.host, remote.port)
347
- info { "connected #{connected_address}:#{connected_port} originator #{originator_address}:#{originator_port}" }
348
-
349
- prepare_client(client, channel, :remote)
350
- rescue SocketError => err
351
- raise Net::SSH::ChannelOpenFailed.new(2, "could not connect to remote host (#{remote.host}:#{remote.port}): #{err.message}")
352
- end
406
+ # The callback used when an auth-agent channel is requested by the server.
407
+ def auth_agent_channel(session, channel, packet)
408
+ info { "opening auth-agent channel" }
409
+ channel[:invisible] = true
353
410
 
354
- # The callback used when an auth-agent channel is requested by the server.
355
- def auth_agent_channel(session, channel, packet)
356
- info { "opening auth-agent channel" }
357
- channel[:invisible] = true
358
-
359
- begin
360
- agent = Authentication::Agent.connect(logger, session.options[:agent_socket_factory])
361
- if (agent.socket.is_a? ::IO)
362
- prepare_client(agent.socket, channel, :agent)
363
- else
364
- prepare_simple_client(agent.socket, channel, :agent)
411
+ begin
412
+ agent = Authentication::Agent.connect(logger, session.options[:agent_socket_factory])
413
+ if (agent.socket.is_a? ::IO)
414
+ prepare_client(agent.socket, channel, :agent)
415
+ else
416
+ prepare_simple_client(agent.socket, channel, :agent)
417
+ end
418
+ rescue Exception => e
419
+ error { "attempted to connect to agent but failed: #{e.class.name} (#{e.message})" }
420
+ raise Net::SSH::ChannelOpenFailed.new(2, "could not connect to authentication agent")
365
421
  end
366
- rescue Exception => e
367
- error { "attempted to connect to agent but failed: #{e.class.name} (#{e.message})" }
368
- raise Net::SSH::ChannelOpenFailed.new(2, "could not connect to authentication agent")
369
422
  end
370
423
  end
424
+ end
371
425
  end
372
-
373
- end; end; end
426
+ end