net-ssh 4.2.0 → 7.0.1

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