net-ssh-backports 6.3.0.backports
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.
- checksums.yaml +7 -0
- data/.github/workflows/ci.yml +93 -0
- data/.gitignore +13 -0
- data/.rubocop.yml +21 -0
- data/.rubocop_todo.yml +1074 -0
- data/.travis.yml +51 -0
- data/CHANGES.txt +698 -0
- data/Gemfile +13 -0
- data/Gemfile.noed25519 +12 -0
- data/ISSUE_TEMPLATE.md +30 -0
- data/LICENSE.txt +19 -0
- data/Manifest +132 -0
- data/README.md +287 -0
- data/Rakefile +105 -0
- data/THANKS.txt +110 -0
- data/appveyor.yml +58 -0
- data/lib/net/ssh/authentication/agent.rb +284 -0
- data/lib/net/ssh/authentication/certificate.rb +183 -0
- data/lib/net/ssh/authentication/constants.rb +20 -0
- data/lib/net/ssh/authentication/ed25519.rb +185 -0
- data/lib/net/ssh/authentication/ed25519_loader.rb +31 -0
- data/lib/net/ssh/authentication/key_manager.rb +297 -0
- data/lib/net/ssh/authentication/methods/abstract.rb +69 -0
- data/lib/net/ssh/authentication/methods/hostbased.rb +72 -0
- data/lib/net/ssh/authentication/methods/keyboard_interactive.rb +77 -0
- data/lib/net/ssh/authentication/methods/none.rb +34 -0
- data/lib/net/ssh/authentication/methods/password.rb +80 -0
- data/lib/net/ssh/authentication/methods/publickey.rb +95 -0
- data/lib/net/ssh/authentication/pageant.rb +497 -0
- data/lib/net/ssh/authentication/pub_key_fingerprint.rb +43 -0
- data/lib/net/ssh/authentication/session.rb +163 -0
- data/lib/net/ssh/buffer.rb +434 -0
- data/lib/net/ssh/buffered_io.rb +202 -0
- data/lib/net/ssh/config.rb +406 -0
- data/lib/net/ssh/connection/channel.rb +695 -0
- data/lib/net/ssh/connection/constants.rb +33 -0
- data/lib/net/ssh/connection/event_loop.rb +123 -0
- data/lib/net/ssh/connection/keepalive.rb +59 -0
- data/lib/net/ssh/connection/session.rb +712 -0
- data/lib/net/ssh/connection/term.rb +180 -0
- data/lib/net/ssh/errors.rb +106 -0
- data/lib/net/ssh/key_factory.rb +218 -0
- data/lib/net/ssh/known_hosts.rb +264 -0
- data/lib/net/ssh/loggable.rb +62 -0
- data/lib/net/ssh/packet.rb +106 -0
- data/lib/net/ssh/prompt.rb +62 -0
- data/lib/net/ssh/proxy/command.rb +123 -0
- data/lib/net/ssh/proxy/errors.rb +16 -0
- data/lib/net/ssh/proxy/http.rb +98 -0
- data/lib/net/ssh/proxy/https.rb +50 -0
- data/lib/net/ssh/proxy/jump.rb +54 -0
- data/lib/net/ssh/proxy/socks4.rb +67 -0
- data/lib/net/ssh/proxy/socks5.rb +140 -0
- data/lib/net/ssh/service/forward.rb +426 -0
- data/lib/net/ssh/test/channel.rb +147 -0
- data/lib/net/ssh/test/extensions.rb +173 -0
- data/lib/net/ssh/test/kex.rb +46 -0
- data/lib/net/ssh/test/local_packet.rb +53 -0
- data/lib/net/ssh/test/packet.rb +101 -0
- data/lib/net/ssh/test/remote_packet.rb +40 -0
- data/lib/net/ssh/test/script.rb +180 -0
- data/lib/net/ssh/test/socket.rb +65 -0
- data/lib/net/ssh/test.rb +94 -0
- data/lib/net/ssh/transport/algorithms.rb +502 -0
- data/lib/net/ssh/transport/cipher_factory.rb +103 -0
- data/lib/net/ssh/transport/constants.rb +40 -0
- data/lib/net/ssh/transport/ctr.rb +115 -0
- data/lib/net/ssh/transport/hmac/abstract.rb +97 -0
- data/lib/net/ssh/transport/hmac/md5.rb +10 -0
- data/lib/net/ssh/transport/hmac/md5_96.rb +9 -0
- data/lib/net/ssh/transport/hmac/none.rb +13 -0
- data/lib/net/ssh/transport/hmac/ripemd160.rb +11 -0
- data/lib/net/ssh/transport/hmac/sha1.rb +11 -0
- data/lib/net/ssh/transport/hmac/sha1_96.rb +9 -0
- data/lib/net/ssh/transport/hmac/sha2_256.rb +11 -0
- data/lib/net/ssh/transport/hmac/sha2_256_96.rb +9 -0
- data/lib/net/ssh/transport/hmac/sha2_256_etm.rb +12 -0
- data/lib/net/ssh/transport/hmac/sha2_512.rb +11 -0
- data/lib/net/ssh/transport/hmac/sha2_512_96.rb +9 -0
- data/lib/net/ssh/transport/hmac/sha2_512_etm.rb +12 -0
- data/lib/net/ssh/transport/hmac.rb +47 -0
- data/lib/net/ssh/transport/identity_cipher.rb +57 -0
- data/lib/net/ssh/transport/kex/abstract.rb +130 -0
- data/lib/net/ssh/transport/kex/abstract5656.rb +72 -0
- data/lib/net/ssh/transport/kex/curve25519_sha256.rb +39 -0
- data/lib/net/ssh/transport/kex/curve25519_sha256_loader.rb +30 -0
- data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha1.rb +37 -0
- data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha256.rb +11 -0
- data/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb +122 -0
- data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb +72 -0
- data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha256.rb +11 -0
- data/lib/net/ssh/transport/kex/ecdh_sha2_nistp256.rb +39 -0
- data/lib/net/ssh/transport/kex/ecdh_sha2_nistp384.rb +21 -0
- data/lib/net/ssh/transport/kex/ecdh_sha2_nistp521.rb +21 -0
- data/lib/net/ssh/transport/kex.rb +31 -0
- data/lib/net/ssh/transport/key_expander.rb +30 -0
- data/lib/net/ssh/transport/openssl.rb +253 -0
- data/lib/net/ssh/transport/packet_stream.rb +280 -0
- data/lib/net/ssh/transport/server_version.rb +77 -0
- data/lib/net/ssh/transport/session.rb +354 -0
- data/lib/net/ssh/transport/state.rb +208 -0
- data/lib/net/ssh/verifiers/accept_new.rb +33 -0
- data/lib/net/ssh/verifiers/accept_new_or_local_tunnel.rb +33 -0
- data/lib/net/ssh/verifiers/always.rb +58 -0
- data/lib/net/ssh/verifiers/never.rb +19 -0
- data/lib/net/ssh/version.rb +68 -0
- data/lib/net/ssh.rb +330 -0
- data/net-ssh-public_cert.pem +20 -0
- data/net-ssh.gemspec +44 -0
- data/support/ssh_tunnel_bug.rb +65 -0
- metadata +271 -0
@@ -0,0 +1,264 @@
|
|
1
|
+
require 'strscan'
|
2
|
+
require 'openssl'
|
3
|
+
require 'base64'
|
4
|
+
require 'net/ssh/buffer'
|
5
|
+
require 'net/ssh/authentication/ed25519_loader'
|
6
|
+
|
7
|
+
module Net
|
8
|
+
module SSH
|
9
|
+
module HostKeyEntries
|
10
|
+
# regular public key entry
|
11
|
+
class PubKey < Delegator
|
12
|
+
def initialize(key, comment: nil) # rubocop:disable Lint/MissingSuper
|
13
|
+
@key = key
|
14
|
+
@comment = comment
|
15
|
+
end
|
16
|
+
|
17
|
+
def ssh_type
|
18
|
+
@key.ssh_type
|
19
|
+
end
|
20
|
+
|
21
|
+
def ssh_types
|
22
|
+
[ssh_type]
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_blob
|
26
|
+
@key.to_blob
|
27
|
+
end
|
28
|
+
|
29
|
+
def __getobj__
|
30
|
+
Kernel.warn("Calling Net::SSH::Buffer methods on HostKeyEntries PubKey is deprecated")
|
31
|
+
@key
|
32
|
+
end
|
33
|
+
|
34
|
+
def matches_key?(server_key)
|
35
|
+
@key.ssh_type == server_key.ssh_type && @key.to_blob == server_key.to_blob
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# @cert-authority entry
|
40
|
+
class CertAuthority
|
41
|
+
def ssh_types
|
42
|
+
%w[
|
43
|
+
ecdsa-sha2-nistp256-cert-v01@openssh.com
|
44
|
+
ecdsa-sha2-nistp384-cert-v01@openssh.com
|
45
|
+
ecdsa-sha2-nistp521-cert-v01@openssh.com
|
46
|
+
ssh-ed25519-cert-v01@openssh.com
|
47
|
+
ssh-rsa-cert-v01@openssh.com
|
48
|
+
ssh-rsa-cert-v00@openssh.com
|
49
|
+
]
|
50
|
+
end
|
51
|
+
|
52
|
+
def initialize(key, comment: nil)
|
53
|
+
@key = key
|
54
|
+
@comment = comment
|
55
|
+
end
|
56
|
+
|
57
|
+
def matches_key?(server_key)
|
58
|
+
if ssh_types.include?(server_key.ssh_type)
|
59
|
+
server_key.signature_valid? && (server_key.signature_key.to_blob == @key.to_blob)
|
60
|
+
else
|
61
|
+
false
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Represents the result of a search in known hosts
|
68
|
+
# see search_for
|
69
|
+
class HostKeys
|
70
|
+
include Enumerable
|
71
|
+
attr_reader :host
|
72
|
+
|
73
|
+
def initialize(host_keys, host, known_hosts, options = {})
|
74
|
+
@host_keys = host_keys
|
75
|
+
@host = host
|
76
|
+
@known_hosts = known_hosts
|
77
|
+
@options = options
|
78
|
+
end
|
79
|
+
|
80
|
+
def add_host_key(key)
|
81
|
+
@known_hosts.add(@host, key, @options)
|
82
|
+
@host_keys.push(key)
|
83
|
+
end
|
84
|
+
|
85
|
+
def each(&block)
|
86
|
+
@host_keys.each(&block)
|
87
|
+
end
|
88
|
+
|
89
|
+
def empty?
|
90
|
+
@host_keys.empty?
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# Searches an OpenSSH-style known-host file for a given host, and returns all
|
95
|
+
# matching keys. This is used to implement host-key verification, as well as
|
96
|
+
# to determine what key a user prefers to use for a given host.
|
97
|
+
#
|
98
|
+
# This is used internally by Net::SSH, and will never need to be used directly
|
99
|
+
# by consumers of the library.
|
100
|
+
class KnownHosts
|
101
|
+
SUPPORTED_TYPE = %w[ssh-rsa ssh-dss
|
102
|
+
ecdsa-sha2-nistp256
|
103
|
+
ecdsa-sha2-nistp384
|
104
|
+
ecdsa-sha2-nistp521]
|
105
|
+
|
106
|
+
SUPPORTED_TYPE.push('ssh-ed25519') if Net::SSH::Authentication::ED25519Loader::LOADED
|
107
|
+
|
108
|
+
class <<self
|
109
|
+
# Searches all known host files (see KnownHosts.hostfiles) for all keys
|
110
|
+
# of the given host. Returns an enumerable of keys found.
|
111
|
+
def search_for(host, options={})
|
112
|
+
HostKeys.new(search_in(hostfiles(options), host, options), host, self, options)
|
113
|
+
end
|
114
|
+
|
115
|
+
# Search for all known keys for the given host, in every file given in
|
116
|
+
# the +files+ array. Returns the list of keys.
|
117
|
+
def search_in(files, host, options = {})
|
118
|
+
files.flat_map { |file| KnownHosts.new(file).keys_for(host, options) }
|
119
|
+
end
|
120
|
+
|
121
|
+
# Looks in the given +options+ hash for the :user_known_hosts_file and
|
122
|
+
# :global_known_hosts_file keys, and returns an array of all known
|
123
|
+
# hosts files. If the :user_known_hosts_file key is not set, the
|
124
|
+
# default is returned (~/.ssh/known_hosts and ~/.ssh/known_hosts2). If
|
125
|
+
# :global_known_hosts_file is not set, the default is used
|
126
|
+
# (/etc/ssh/ssh_known_hosts and /etc/ssh/ssh_known_hosts2).
|
127
|
+
#
|
128
|
+
# If you only want the user known host files, you can pass :user as
|
129
|
+
# the second option.
|
130
|
+
def hostfiles(options, which=:all)
|
131
|
+
files = []
|
132
|
+
|
133
|
+
files += Array(options[:user_known_hosts_file] || %w[~/.ssh/known_hosts ~/.ssh/known_hosts2]) if which == :all || which == :user
|
134
|
+
|
135
|
+
if which == :all || which == :global
|
136
|
+
files += Array(options[:global_known_hosts_file] || %w[/etc/ssh/ssh_known_hosts /etc/ssh/ssh_known_hosts2])
|
137
|
+
end
|
138
|
+
|
139
|
+
return files
|
140
|
+
end
|
141
|
+
|
142
|
+
# Looks in all user known host files (see KnownHosts.hostfiles) and tries to
|
143
|
+
# add an entry for the given host and key to the first file it is able
|
144
|
+
# to.
|
145
|
+
def add(host, key, options={})
|
146
|
+
hostfiles(options, :user).each do |file|
|
147
|
+
KnownHosts.new(file).add(host, key)
|
148
|
+
return
|
149
|
+
rescue SystemCallError
|
150
|
+
# try the next hostfile
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
# The host-key file name that this KnownHosts instance will use to search
|
156
|
+
# for keys.
|
157
|
+
attr_reader :source
|
158
|
+
|
159
|
+
# Instantiate a new KnownHosts instance that will search the given known-hosts
|
160
|
+
# file. The path is expanded file File.expand_path.
|
161
|
+
def initialize(source)
|
162
|
+
@source = File.expand_path(source)
|
163
|
+
end
|
164
|
+
|
165
|
+
# Returns an array of all keys that are known to be associatd with the
|
166
|
+
# given host. The +host+ parameter is either the domain name or ip address
|
167
|
+
# of the host, or both (comma-separated). Additionally, if a non-standard
|
168
|
+
# port is being used, it may be specified by putting the host (or ip, or
|
169
|
+
# both) in square brackets, and appending the port outside the brackets
|
170
|
+
# after a colon. Possible formats for +host+, then, are;
|
171
|
+
#
|
172
|
+
# "net.ssh.test"
|
173
|
+
# "1.2.3.4"
|
174
|
+
# "net.ssh.test,1.2.3.4"
|
175
|
+
# "[net.ssh.test]:5555"
|
176
|
+
# "[1,2,3,4]:5555"
|
177
|
+
# "[net.ssh.test]:5555,[1.2.3.4]:5555
|
178
|
+
def keys_for(host, options = {})
|
179
|
+
keys = []
|
180
|
+
return keys unless File.readable?(source)
|
181
|
+
|
182
|
+
entries = host.split(/,/)
|
183
|
+
host_name = entries[0]
|
184
|
+
host_ip = entries[1]
|
185
|
+
|
186
|
+
File.open(source) do |file|
|
187
|
+
file.each_line do |line|
|
188
|
+
if line.start_with?('@')
|
189
|
+
marker, hosts, type, key_content, comment = line.split(' ')
|
190
|
+
else
|
191
|
+
marker = nil
|
192
|
+
hosts, type, key_content, comment = line.split(' ')
|
193
|
+
end
|
194
|
+
|
195
|
+
# Skip empty line or one that is commented
|
196
|
+
next if hosts.nil? || hosts.start_with?('#')
|
197
|
+
|
198
|
+
hostlist = hosts.split(',')
|
199
|
+
|
200
|
+
next unless SUPPORTED_TYPE.include?(type)
|
201
|
+
|
202
|
+
found = hostlist.any? { |pattern| match(host_name, pattern) } || known_host_hash?(hostlist, entries)
|
203
|
+
next unless found
|
204
|
+
|
205
|
+
found = hostlist.include?(host_ip) if options[:check_host_ip] && entries.size > 1 && hostlist.size > 1
|
206
|
+
next unless found
|
207
|
+
|
208
|
+
blob = key_content.unpack("m*").first
|
209
|
+
raw_key = Net::SSH::Buffer.new(blob).read_key
|
210
|
+
|
211
|
+
keys <<
|
212
|
+
if marker == "@cert-authority"
|
213
|
+
HostKeyEntries::CertAuthority.new(raw_key, comment: comment)
|
214
|
+
else
|
215
|
+
HostKeyEntries::PubKey.new(raw_key, comment: comment)
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
keys
|
221
|
+
end
|
222
|
+
|
223
|
+
def match(host, pattern)
|
224
|
+
if pattern.include?('*') || pattern.include?('?')
|
225
|
+
# see man 8 sshd for pattern details
|
226
|
+
pattern_regexp = pattern.split('*', -1).map do |x|
|
227
|
+
x.split('?', -1).map do |y|
|
228
|
+
Regexp.escape(y)
|
229
|
+
end.join('.')
|
230
|
+
end.join('.*')
|
231
|
+
|
232
|
+
host =~ Regexp.new("\\A#{pattern_regexp}\\z")
|
233
|
+
else
|
234
|
+
host == pattern
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
# Indicates whether one of the entries matches an hostname that has been
|
239
|
+
# stored as a HMAC-SHA1 hash in the known hosts.
|
240
|
+
def known_host_hash?(hostlist, entries)
|
241
|
+
if hostlist.size == 1 && hostlist.first =~ /\A\|1(\|.+){2}\z/
|
242
|
+
chunks = hostlist.first.split(/\|/)
|
243
|
+
salt = Base64.decode64(chunks[2])
|
244
|
+
digest = OpenSSL::Digest.new('sha1')
|
245
|
+
entries.each do |entry|
|
246
|
+
hmac = OpenSSL::HMAC.digest(digest, salt, entry)
|
247
|
+
return true if Base64.encode64(hmac).chomp == chunks[3]
|
248
|
+
end
|
249
|
+
end
|
250
|
+
false
|
251
|
+
end
|
252
|
+
|
253
|
+
# Tries to append an entry to the current source file for the given host
|
254
|
+
# and key. If it is unable to (because the file is not writable, for
|
255
|
+
# instance), an exception will be raised.
|
256
|
+
def add(host, key)
|
257
|
+
File.open(source, "a") do |file|
|
258
|
+
blob = [Net::SSH::Buffer.from(:key, key).to_s].pack("m*").gsub(/\s/, "")
|
259
|
+
file.puts "#{host} #{key.ssh_type} #{blob}"
|
260
|
+
end
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Net
|
2
|
+
module SSH
|
3
|
+
# A simple module to make logging easier to deal with. It assumes that the
|
4
|
+
# logger instance (if not nil) quacks like a Logger object (in Ruby's
|
5
|
+
# standard library). Although used primarily internally by Net::SSH, it
|
6
|
+
# can easily be used to add Net::SSH-like logging to your own programs.
|
7
|
+
#
|
8
|
+
# class MyClass
|
9
|
+
# include Net::SSH::Loggable
|
10
|
+
# end
|
11
|
+
#
|
12
|
+
# Net::SSH.start(...) do |ssh|
|
13
|
+
# obj = MyClass.new
|
14
|
+
# obj.logger = ssh.logger
|
15
|
+
# ...
|
16
|
+
# end
|
17
|
+
module Loggable
|
18
|
+
# The logger instance that will be used to log messages. If nil, nothing
|
19
|
+
# will be logged.
|
20
|
+
attr_accessor :logger
|
21
|
+
|
22
|
+
# Displays the result of yielding if the log level is Logger::DEBUG or
|
23
|
+
# greater.
|
24
|
+
def debug
|
25
|
+
logger.add(Logger::DEBUG, nil, facility) { yield } if logger && logger.debug?
|
26
|
+
end
|
27
|
+
|
28
|
+
# Displays the result of yielding if the log level is Logger::INFO or
|
29
|
+
# greater.
|
30
|
+
def info
|
31
|
+
logger.add(Logger::INFO, nil, facility) { yield } if logger && logger.info?
|
32
|
+
end
|
33
|
+
|
34
|
+
# Displays the result of yielding if the log level is Logger::WARN or
|
35
|
+
# greater. (Called lwarn to avoid shadowing with Kernel#warn.)
|
36
|
+
def lwarn
|
37
|
+
logger.add(Logger::WARN, nil, facility) { yield } if logger && logger.warn?
|
38
|
+
end
|
39
|
+
|
40
|
+
# Displays the result of yielding if the log level is Logger:ERROR or
|
41
|
+
# greater.
|
42
|
+
def error
|
43
|
+
logger.add(Logger::ERROR, nil, facility) { yield } if logger && logger.error?
|
44
|
+
end
|
45
|
+
|
46
|
+
# Displays the result of yielding if the log level is Logger::FATAL or
|
47
|
+
# greater.
|
48
|
+
def fatal
|
49
|
+
logger.add(Logger::FATAL, nil, facility) { yield } if logger && logger.fatal?
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
# Sets the "facility" value, used for reporting where a log message
|
55
|
+
# originates. It defaults to the name of class with the object_id
|
56
|
+
# appended.
|
57
|
+
def facility
|
58
|
+
@facility ||= self.class.to_s.gsub(/::/, ".").gsub(/([a-z])([A-Z])/, "\\1_\\2").downcase + "[%x]" % object_id
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'net/ssh/buffer'
|
2
|
+
require 'net/ssh/transport/constants'
|
3
|
+
require 'net/ssh/authentication/constants'
|
4
|
+
require 'net/ssh/connection/constants'
|
5
|
+
|
6
|
+
module Net
|
7
|
+
module SSH
|
8
|
+
# A specialization of Buffer that knows the format of certain common
|
9
|
+
# packet types. It auto-parses those packet types, and allows them to
|
10
|
+
# be accessed via the #[] accessor.
|
11
|
+
#
|
12
|
+
# data = some_channel_request_packet
|
13
|
+
# packet = Net::SSH::Packet.new(data)
|
14
|
+
#
|
15
|
+
# p packet.type #-> 98 (CHANNEL_REQUEST)
|
16
|
+
# p packet[:request]
|
17
|
+
# p packet[:want_reply]
|
18
|
+
#
|
19
|
+
# This is used exclusively internally by Net::SSH, and unless you're doing
|
20
|
+
# protocol-level manipulation or are extending Net::SSH in some way, you'll
|
21
|
+
# never need to use this class directly.
|
22
|
+
class Packet < Buffer
|
23
|
+
@@types = {}
|
24
|
+
|
25
|
+
# Register a new packet type that should be recognized and auto-parsed by
|
26
|
+
# Net::SSH::Packet. Note that any packet type that is not preregistered
|
27
|
+
# will not be autoparsed.
|
28
|
+
#
|
29
|
+
# The +pairs+ parameter must be either empty, or an array of two-element
|
30
|
+
# tuples, where the first element of each tuple is the name of the field,
|
31
|
+
# and the second is the type.
|
32
|
+
#
|
33
|
+
# register DISCONNECT, [:reason_code, :long], [:description, :string], [:language, :string]
|
34
|
+
def self.register(type, *pairs)
|
35
|
+
@@types[type] = pairs
|
36
|
+
end
|
37
|
+
|
38
|
+
include Connection::Constants
|
39
|
+
include Authentication::Constants
|
40
|
+
include Transport::Constants
|
41
|
+
|
42
|
+
#--
|
43
|
+
# These are the recognized packet types. All other packet types will be
|
44
|
+
# accepted, but not auto-parsed, requiring the client to parse the
|
45
|
+
# fields using the methods provided by Net::SSH::Buffer.
|
46
|
+
#++
|
47
|
+
|
48
|
+
register DISCONNECT, %i[reason_code long], %i[description string], %i[language string]
|
49
|
+
register IGNORE, %i[data string]
|
50
|
+
register UNIMPLEMENTED, %i[number long]
|
51
|
+
register DEBUG, %i[always_display bool], %i[message string], %i[language string]
|
52
|
+
register SERVICE_ACCEPT, %i[service_name string]
|
53
|
+
register USERAUTH_BANNER, %i[message string], %i[language string]
|
54
|
+
register USERAUTH_FAILURE, %i[authentications string], %i[partial_success bool]
|
55
|
+
register GLOBAL_REQUEST, %i[request_type string], %i[want_reply bool], %i[request_data buffer]
|
56
|
+
register CHANNEL_OPEN, %i[channel_type string], %i[remote_id long], %i[window_size long], %i[packet_size long]
|
57
|
+
register CHANNEL_OPEN_CONFIRMATION, %i[local_id long], %i[remote_id long], %i[window_size long], %i[packet_size long]
|
58
|
+
register CHANNEL_OPEN_FAILURE, %i[local_id long], %i[reason_code long], %i[description string], %i[language string]
|
59
|
+
register CHANNEL_WINDOW_ADJUST, %i[local_id long], %i[extra_bytes long]
|
60
|
+
register CHANNEL_DATA, %i[local_id long], %i[data string]
|
61
|
+
register CHANNEL_EXTENDED_DATA, %i[local_id long], %i[data_type long], %i[data string]
|
62
|
+
register CHANNEL_EOF, %i[local_id long]
|
63
|
+
register CHANNEL_CLOSE, %i[local_id long]
|
64
|
+
register CHANNEL_REQUEST, %i[local_id long], %i[request string], %i[want_reply bool], %i[request_data buffer]
|
65
|
+
register CHANNEL_SUCCESS, %i[local_id long]
|
66
|
+
register CHANNEL_FAILURE, %i[local_id long]
|
67
|
+
|
68
|
+
# The (integer) type of this packet.
|
69
|
+
attr_reader :type
|
70
|
+
|
71
|
+
# Create a new packet from the given payload. This will automatically
|
72
|
+
# parse the packet if it is one that has been previously registered with
|
73
|
+
# Packet.register; otherwise, the packet will need to be manually parsed
|
74
|
+
# using the methods provided in the Net::SSH::Buffer superclass.
|
75
|
+
def initialize(payload)
|
76
|
+
@named_elements = {}
|
77
|
+
super
|
78
|
+
@type = read_byte
|
79
|
+
instantiate!
|
80
|
+
end
|
81
|
+
|
82
|
+
# Access one of the auto-parsed fields by name. Raises an error if no
|
83
|
+
# element by the given name exists.
|
84
|
+
def [](name)
|
85
|
+
name = name.to_sym
|
86
|
+
raise ArgumentError, "no such element #{name}" unless @named_elements.key?(name)
|
87
|
+
|
88
|
+
@named_elements[name]
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
# Parse the packet's contents and assign the named elements, as described
|
94
|
+
# by the registered format for the packet.
|
95
|
+
def instantiate!
|
96
|
+
(@@types[type] || []).each do |name, datatype|
|
97
|
+
@named_elements[name.to_sym] = if datatype == :buffer
|
98
|
+
remainder_as_buffer
|
99
|
+
else
|
100
|
+
send("read_#{datatype}")
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,62 @@
|
|
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)
|
25
|
+
end
|
26
|
+
|
27
|
+
def initialize(options = {}); end
|
28
|
+
|
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?
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
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
|
47
|
+
end
|
48
|
+
|
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
|
52
|
+
end
|
53
|
+
|
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)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'rubygems'
|
3
|
+
require 'net/ssh/proxy/errors'
|
4
|
+
|
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
|
21
|
+
|
22
|
+
# The command line for the session
|
23
|
+
attr_reader :command_line
|
24
|
+
|
25
|
+
# Timeout in seconds in open, defaults to 60
|
26
|
+
attr_accessor :timeout
|
27
|
+
|
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
|
37
|
+
|
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
|
84
|
+
|
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
|
112
|
+
end
|
113
|
+
io
|
114
|
+
end
|
115
|
+
|
116
|
+
def close_on_error(io)
|
117
|
+
Process.kill('TERM', io.pid)
|
118
|
+
Thread.new { io.close }
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'net/ssh/errors'
|
2
|
+
|
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
|
8
|
+
|
9
|
+
# Used for reporting proxy connection errors.
|
10
|
+
class ConnectError < Error; end
|
11
|
+
|
12
|
+
# Used when the server doesn't recognize the user's credentials.
|
13
|
+
class UnauthorizedError < Error; end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|