net-ssh 2.9.2 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/.gitignore +6 -0
- data/.rubocop.yml +5 -0
- data/.rubocop_todo.yml +1129 -0
- data/.travis.yml +41 -5
- data/CHANGES.txt +133 -1
- data/Gemfile +13 -0
- data/Gemfile.norbnacl +10 -0
- data/Gemfile.norbnacl.lock +41 -0
- data/ISSUE_TEMPLATE.md +30 -0
- data/README.rdoc +26 -81
- data/Rakefile +63 -45
- data/appveyor.yml +51 -0
- data/lib/net/ssh/authentication/agent.rb +174 -14
- data/lib/net/ssh/authentication/ed25519.rb +137 -0
- data/lib/net/ssh/authentication/ed25519_loader.rb +21 -0
- data/lib/net/ssh/authentication/key_manager.rb +36 -30
- data/lib/net/ssh/authentication/methods/abstract.rb +4 -0
- data/lib/net/ssh/authentication/methods/keyboard_interactive.rb +16 -9
- data/lib/net/ssh/authentication/methods/password.rb +17 -4
- data/lib/net/ssh/authentication/pageant.rb +166 -45
- data/lib/net/ssh/authentication/session.rb +3 -2
- data/lib/net/ssh/buffer.rb +49 -10
- data/lib/net/ssh/buffered_io.rb +17 -12
- data/lib/net/ssh/config.rb +39 -8
- data/lib/net/ssh/connection/channel.rb +42 -20
- data/lib/net/ssh/connection/event_loop.rb +114 -0
- data/lib/net/ssh/connection/keepalive.rb +2 -2
- data/lib/net/ssh/connection/session.rb +120 -34
- data/lib/net/ssh/errors.rb +6 -6
- data/lib/net/ssh/key_factory.rb +49 -43
- data/lib/net/ssh/known_hosts.rb +49 -3
- data/lib/net/ssh/prompt.rb +47 -78
- data/lib/net/ssh/proxy/command.rb +31 -5
- data/lib/net/ssh/proxy/http.rb +15 -11
- data/lib/net/ssh/proxy/https.rb +49 -0
- data/lib/net/ssh/proxy/socks4.rb +2 -1
- data/lib/net/ssh/proxy/socks5.rb +3 -2
- data/lib/net/ssh/ruby_compat.rb +2 -29
- data/lib/net/ssh/service/forward.rb +2 -2
- data/lib/net/ssh/test/channel.rb +7 -0
- data/lib/net/ssh/test/extensions.rb +17 -0
- data/lib/net/ssh/test/kex.rb +4 -4
- data/lib/net/ssh/test/packet.rb +18 -2
- data/lib/net/ssh/test/script.rb +16 -2
- data/lib/net/ssh/test/socket.rb +1 -1
- data/lib/net/ssh/test.rb +5 -5
- data/lib/net/ssh/transport/algorithms.rb +92 -75
- data/lib/net/ssh/transport/cipher_factory.rb +19 -26
- data/lib/net/ssh/transport/ctr.rb +7 -9
- data/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb +20 -9
- data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb +5 -3
- data/lib/net/ssh/transport/kex/ecdh_sha2_nistp256.rb +1 -1
- data/lib/net/ssh/transport/key_expander.rb +1 -0
- data/lib/net/ssh/transport/openssl.rb +1 -1
- data/lib/net/ssh/transport/packet_stream.rb +11 -3
- data/lib/net/ssh/transport/server_version.rb +13 -6
- data/lib/net/ssh/transport/session.rb +20 -10
- data/lib/net/ssh/transport/state.rb +1 -1
- data/lib/net/ssh/verifiers/secure.rb +8 -10
- data/lib/net/ssh/version.rb +4 -4
- data/lib/net/ssh.rb +62 -14
- data/net-ssh-public_cert.pem +19 -18
- data/net-ssh.gemspec +34 -194
- data/support/arcfour_check.rb +1 -1
- data/support/ssh_tunnel_bug.rb +1 -1
- data.tar.gz.sig +0 -0
- metadata +125 -109
- metadata.gz.sig +0 -0
- data/Rudyfile +0 -96
- data/lib/net/ssh/authentication/agent/java_pageant.rb +0 -85
- data/lib/net/ssh/authentication/agent/socket.rb +0 -178
- data/setup.rb +0 -1585
- data/test/README.txt +0 -47
- data/test/authentication/methods/common.rb +0 -28
- data/test/authentication/methods/test_abstract.rb +0 -51
- data/test/authentication/methods/test_hostbased.rb +0 -114
- data/test/authentication/methods/test_keyboard_interactive.rb +0 -100
- data/test/authentication/methods/test_none.rb +0 -41
- data/test/authentication/methods/test_password.rb +0 -95
- data/test/authentication/methods/test_publickey.rb +0 -148
- data/test/authentication/test_agent.rb +0 -224
- data/test/authentication/test_key_manager.rb +0 -227
- data/test/authentication/test_session.rb +0 -107
- data/test/common.rb +0 -108
- data/test/configs/auth_off +0 -5
- data/test/configs/auth_on +0 -4
- data/test/configs/empty +0 -0
- data/test/configs/eqsign +0 -3
- data/test/configs/exact_match +0 -8
- data/test/configs/host_plus +0 -10
- data/test/configs/multihost +0 -4
- data/test/configs/negative_match +0 -6
- data/test/configs/nohost +0 -19
- data/test/configs/numeric_host +0 -4
- data/test/configs/send_env +0 -2
- data/test/configs/substitutes +0 -8
- data/test/configs/wild_cards +0 -14
- data/test/connection/test_channel.rb +0 -467
- data/test/connection/test_session.rb +0 -543
- data/test/known_hosts/github +0 -1
- data/test/manual/test_forward.rb +0 -285
- data/test/manual/test_pageant.rb +0 -37
- data/test/start/test_connection.rb +0 -53
- data/test/start/test_options.rb +0 -43
- data/test/start/test_transport.rb +0 -28
- data/test/test_all.rb +0 -11
- data/test/test_buffer.rb +0 -433
- data/test/test_buffered_io.rb +0 -63
- data/test/test_config.rb +0 -221
- data/test/test_key_factory.rb +0 -191
- data/test/test_known_hosts.rb +0 -13
- data/test/transport/hmac/test_md5.rb +0 -41
- data/test/transport/hmac/test_md5_96.rb +0 -27
- data/test/transport/hmac/test_none.rb +0 -34
- data/test/transport/hmac/test_ripemd160.rb +0 -36
- data/test/transport/hmac/test_sha1.rb +0 -36
- data/test/transport/hmac/test_sha1_96.rb +0 -27
- data/test/transport/hmac/test_sha2_256.rb +0 -37
- data/test/transport/hmac/test_sha2_256_96.rb +0 -27
- data/test/transport/hmac/test_sha2_512.rb +0 -37
- data/test/transport/hmac/test_sha2_512_96.rb +0 -27
- data/test/transport/kex/test_diffie_hellman_group14_sha1.rb +0 -13
- data/test/transport/kex/test_diffie_hellman_group1_sha1.rb +0 -146
- data/test/transport/kex/test_diffie_hellman_group_exchange_sha1.rb +0 -92
- data/test/transport/kex/test_diffie_hellman_group_exchange_sha256.rb +0 -34
- data/test/transport/kex/test_ecdh_sha2_nistp256.rb +0 -161
- data/test/transport/kex/test_ecdh_sha2_nistp384.rb +0 -38
- data/test/transport/kex/test_ecdh_sha2_nistp521.rb +0 -38
- data/test/transport/test_algorithms.rb +0 -324
- data/test/transport/test_cipher_factory.rb +0 -443
- data/test/transport/test_hmac.rb +0 -34
- data/test/transport/test_identity_cipher.rb +0 -40
- data/test/transport/test_packet_stream.rb +0 -1761
- data/test/transport/test_server_version.rb +0 -78
- data/test/transport/test_session.rb +0 -331
- data/test/transport/test_state.rb +0 -181
data/lib/net/ssh/key_factory.rb
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
require 'net/ssh/transport/openssl'
|
|
2
2
|
require 'net/ssh/prompt'
|
|
3
3
|
|
|
4
|
+
require 'net/ssh/authentication/ed25519_loader'
|
|
5
|
+
|
|
4
6
|
module Net; module SSH
|
|
5
7
|
|
|
6
8
|
# A factory class for returning new Key classes. It is used for obtaining
|
|
@@ -21,11 +23,10 @@ module Net; module SSH
|
|
|
21
23
|
}
|
|
22
24
|
if defined?(OpenSSL::PKey::EC)
|
|
23
25
|
MAP["ecdsa"] = OpenSSL::PKey::EC
|
|
26
|
+
MAP["ed25519"] = Net::SSH::Authentication::ED25519::PrivKey if defined? Net::SSH::Authentication::ED25519
|
|
24
27
|
end
|
|
25
28
|
|
|
26
29
|
class <<self
|
|
27
|
-
include Prompt
|
|
28
|
-
|
|
29
30
|
# Fetch an OpenSSL key instance by its SSH name. It will be a new,
|
|
30
31
|
# empty key of the given type.
|
|
31
32
|
def get(name)
|
|
@@ -36,61 +37,43 @@ module Net; module SSH
|
|
|
36
37
|
# whether the file describes an RSA or DSA key, and will load it
|
|
37
38
|
# appropriately. The new key is returned. If the key itself is
|
|
38
39
|
# encrypted (requiring a passphrase to use), the user will be
|
|
39
|
-
# prompted to enter their password unless passphrase works.
|
|
40
|
-
def load_private_key(filename, passphrase=nil, ask_passphrase=true)
|
|
40
|
+
# prompted to enter their password unless passphrase works.
|
|
41
|
+
def load_private_key(filename, passphrase=nil, ask_passphrase=true, prompt=Prompt.default)
|
|
41
42
|
data = File.read(File.expand_path(filename))
|
|
42
|
-
load_data_private_key(data, passphrase, ask_passphrase, filename)
|
|
43
|
+
load_data_private_key(data, passphrase, ask_passphrase, filename, prompt)
|
|
43
44
|
end
|
|
44
45
|
|
|
45
46
|
# Loads a private key. It will correctly determine
|
|
46
47
|
# whether the file describes an RSA or DSA key, and will load it
|
|
47
48
|
# appropriately. The new key is returned. If the key itself is
|
|
48
49
|
# encrypted (requiring a passphrase to use), the user will be
|
|
49
|
-
# prompted to enter their password unless passphrase works.
|
|
50
|
-
def load_data_private_key(data, passphrase=nil, ask_passphrase=true, filename="")
|
|
51
|
-
|
|
52
|
-
pkey_read = true
|
|
53
|
-
error_class = ArgumentError
|
|
54
|
-
else
|
|
55
|
-
pkey_read = false
|
|
56
|
-
if data.match(/-----BEGIN DSA PRIVATE KEY-----/)
|
|
57
|
-
key_type = OpenSSL::PKey::DSA
|
|
58
|
-
error_class = OpenSSL::PKey::DSAError
|
|
59
|
-
elsif data.match(/-----BEGIN RSA PRIVATE KEY-----/)
|
|
60
|
-
key_type = OpenSSL::PKey::RSA
|
|
61
|
-
error_class = OpenSSL::PKey::RSAError
|
|
62
|
-
elsif data.match(/-----BEGIN EC PRIVATE KEY-----/) && defined?(OpenSSL::PKey::EC)
|
|
63
|
-
key_type = OpenSSL::PKey::EC
|
|
64
|
-
error_class = OpenSSL::PKey::ECError
|
|
65
|
-
elsif data.match(/-----BEGIN (.+) PRIVATE KEY-----/)
|
|
66
|
-
raise OpenSSL::PKey::PKeyError, "not a supported key type '#{$1}'"
|
|
67
|
-
else
|
|
68
|
-
raise OpenSSL::PKey::PKeyError, "not a private key (#{filename})"
|
|
69
|
-
end
|
|
70
|
-
end
|
|
50
|
+
# prompted to enter their password unless passphrase works.
|
|
51
|
+
def load_data_private_key(data, passphrase=nil, ask_passphrase=true, filename="", prompt=Prompt.default)
|
|
52
|
+
key_read, error_classes = classify_key(data, filename)
|
|
71
53
|
|
|
72
54
|
encrypted_key = data.match(/ENCRYPTED/)
|
|
73
55
|
tries = 0
|
|
74
56
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
57
|
+
prompter = nil
|
|
58
|
+
result =
|
|
59
|
+
begin
|
|
60
|
+
key_read[data, passphrase || 'invalid']
|
|
61
|
+
rescue *error_classes
|
|
62
|
+
if encrypted_key && ask_passphrase
|
|
63
|
+
tries += 1
|
|
64
|
+
if tries <= 3
|
|
65
|
+
prompter ||= prompt.start(type: 'private_key', filename: filename, sha: Digest::SHA256.digest(data))
|
|
66
|
+
passphrase = prompter.ask("Enter passphrase for #{filename}:", false)
|
|
67
|
+
retry
|
|
68
|
+
else
|
|
69
|
+
raise
|
|
70
|
+
end
|
|
87
71
|
else
|
|
88
72
|
raise
|
|
89
73
|
end
|
|
90
|
-
else
|
|
91
|
-
raise
|
|
92
74
|
end
|
|
93
|
-
|
|
75
|
+
prompter.success if prompter
|
|
76
|
+
result
|
|
94
77
|
end
|
|
95
78
|
|
|
96
79
|
# Loads a public key from a file. It will correctly determine whether
|
|
@@ -110,7 +93,7 @@ module Net; module SSH
|
|
|
110
93
|
blob = nil
|
|
111
94
|
begin
|
|
112
95
|
blob = fields.shift
|
|
113
|
-
end while !blob.nil? && !/^(ssh-(rsa|dss)|ecdsa-sha2-nistp\d+)$/.match(blob)
|
|
96
|
+
end while !blob.nil? && !/^(ssh-(rsa|dss|ed25519)|ecdsa-sha2-nistp\d+)$/.match(blob)
|
|
114
97
|
blob = fields.shift
|
|
115
98
|
|
|
116
99
|
raise Net::SSH::Exception, "public key at #{filename} is not valid" if blob.nil?
|
|
@@ -119,6 +102,29 @@ module Net; module SSH
|
|
|
119
102
|
reader = Net::SSH::Buffer.new(blob)
|
|
120
103
|
reader.read_key or raise OpenSSL::PKey::PKeyError, "not a public key #{filename.inspect}"
|
|
121
104
|
end
|
|
105
|
+
|
|
106
|
+
private
|
|
107
|
+
|
|
108
|
+
# Determine whether the file describes an RSA or DSA key, and return how load it
|
|
109
|
+
# appropriately.
|
|
110
|
+
def classify_key(data, filename)
|
|
111
|
+
if data.match(/-----BEGIN OPENSSH PRIVATE KEY-----/)
|
|
112
|
+
Net::SSH::Authentication::ED25519Loader.raiseUnlessLoaded("OpenSSH keys only supported if ED25519 is available")
|
|
113
|
+
return ->(key_data, passphrase) { Net::SSH::Authentication::ED25519::PrivKey.read(key_data, passphrase) }, [ArgumentError]
|
|
114
|
+
elsif OpenSSL::PKey.respond_to?(:read)
|
|
115
|
+
return ->(key_data, passphrase) { OpenSSL::PKey.read(key_data, passphrase) }, [ArgumentError, OpenSSL::PKey::PKeyError]
|
|
116
|
+
elsif data.match(/-----BEGIN DSA PRIVATE KEY-----/)
|
|
117
|
+
return ->(key_data, passphrase) { OpenSSL::PKey::DSA.new(key_data, passphrase) }, [OpenSSL::PKey::DSAError]
|
|
118
|
+
elsif data.match(/-----BEGIN RSA PRIVATE KEY-----/)
|
|
119
|
+
return ->(key_data, passphrase) { OpenSSL::PKey::RSA.new(key_data, passphrase) }, [OpenSSL::PKey::RSAError]
|
|
120
|
+
elsif data.match(/-----BEGIN EC PRIVATE KEY-----/) && defined?(OpenSSL::PKey::EC)
|
|
121
|
+
return ->(key_data, passphrase) { OpenSSL::PKey::EC.new(key_data, passphrase) }, [OpenSSL::PKey::ECError]
|
|
122
|
+
elsif data.match(/-----BEGIN (.+) PRIVATE KEY-----/)
|
|
123
|
+
raise OpenSSL::PKey::PKeyError, "not a supported key type '#{$1}'"
|
|
124
|
+
else
|
|
125
|
+
raise OpenSSL::PKey::PKeyError, "not a private key (#{filename})"
|
|
126
|
+
end
|
|
127
|
+
end
|
|
122
128
|
end
|
|
123
129
|
|
|
124
130
|
end
|
data/lib/net/ssh/known_hosts.rb
CHANGED
|
@@ -1,8 +1,37 @@
|
|
|
1
1
|
require 'strscan'
|
|
2
|
+
require 'openssl'
|
|
3
|
+
require 'base64'
|
|
2
4
|
require 'net/ssh/buffer'
|
|
3
5
|
|
|
4
6
|
module Net; module SSH
|
|
5
7
|
|
|
8
|
+
# Represents the result of a search in known hosts
|
|
9
|
+
# see search_for
|
|
10
|
+
class HostKeys
|
|
11
|
+
include Enumerable
|
|
12
|
+
attr_reader :host
|
|
13
|
+
|
|
14
|
+
def initialize(host_keys, host, known_hosts, options = {})
|
|
15
|
+
@host_keys = host_keys
|
|
16
|
+
@host = host
|
|
17
|
+
@known_hosts = known_hosts
|
|
18
|
+
@options = options
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def add_host_key(key)
|
|
22
|
+
@known_hosts.add(@host, key, @options)
|
|
23
|
+
@host_keys.push(key)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def each(&block)
|
|
27
|
+
@host_keys.each(&block)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def empty?
|
|
31
|
+
@host_keys.empty?
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
6
35
|
# Searches an OpenSSH-style known-host file for a given host, and returns all
|
|
7
36
|
# matching keys. This is used to implement host-key verification, as well as
|
|
8
37
|
# to determine what key a user prefers to use for a given host.
|
|
@@ -24,9 +53,9 @@ module Net; module SSH
|
|
|
24
53
|
class <<self
|
|
25
54
|
|
|
26
55
|
# Searches all known host files (see KnownHosts.hostfiles) for all keys
|
|
27
|
-
# of the given host. Returns an
|
|
56
|
+
# of the given host. Returns an enumerable of keys found.
|
|
28
57
|
def search_for(host, options={})
|
|
29
|
-
search_in(hostfiles(options), host)
|
|
58
|
+
HostKeys.new(search_in(hostfiles(options), host), host, self, options)
|
|
30
59
|
end
|
|
31
60
|
|
|
32
61
|
# Search for all known keys for the given host, in every file given in
|
|
@@ -111,7 +140,9 @@ module Net; module SSH
|
|
|
111
140
|
next if scanner.match?(/$|#/)
|
|
112
141
|
|
|
113
142
|
hostlist = scanner.scan(/\S+/).split(/,/)
|
|
114
|
-
|
|
143
|
+
found = entries.all? { |entry| hostlist.include?(entry) } ||
|
|
144
|
+
known_host_hash?(hostlist, entries, scanner)
|
|
145
|
+
next unless found
|
|
115
146
|
|
|
116
147
|
scanner.skip(/\s*/)
|
|
117
148
|
type = scanner.scan(/\S+/)
|
|
@@ -127,6 +158,21 @@ module Net; module SSH
|
|
|
127
158
|
keys
|
|
128
159
|
end
|
|
129
160
|
|
|
161
|
+
# Indicates whether one of the entries matches an hostname that has been
|
|
162
|
+
# stored as a HMAC-SHA1 hash in the known hosts.
|
|
163
|
+
def known_host_hash?(hostlist, entries, scanner)
|
|
164
|
+
if hostlist.size == 1 && hostlist.first =~ /\A\|1(\|.+){2}\z/
|
|
165
|
+
chunks = hostlist.first.split(/\|/)
|
|
166
|
+
salt = Base64.decode64(chunks[2])
|
|
167
|
+
digest = OpenSSL::Digest.new('sha1')
|
|
168
|
+
entries.each do |entry|
|
|
169
|
+
hmac = OpenSSL::HMAC.digest(digest, salt, entry)
|
|
170
|
+
return true if Base64.encode64(hmac).chomp == chunks[3]
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
false
|
|
174
|
+
end
|
|
175
|
+
|
|
130
176
|
# Tries to append an entry to the current source file for the given host
|
|
131
177
|
# and key. If it is unable to (because the file is not writable, for
|
|
132
178
|
# instance), an exception will be raised.
|
data/lib/net/ssh/prompt.rb
CHANGED
|
@@ -1,93 +1,62 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
# A basic prompt module that can be mixed into other objects. If HighLine is
|
|
4
|
-
# installed, it will be used to display prompts and read input from the
|
|
5
|
-
# user. Otherwise, the termios library will be used. If neither HighLine
|
|
6
|
-
# nor termios is installed, a simple prompt that echos text in the clear
|
|
7
|
-
# will be used.
|
|
1
|
+
require 'io/console'
|
|
8
2
|
|
|
9
|
-
|
|
3
|
+
module Net; module SSH
|
|
10
4
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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)
|
|
20
25
|
end
|
|
21
26
|
|
|
22
|
-
|
|
23
|
-
module Termios
|
|
24
|
-
# Displays the prompt to $stdout. If +echo+ is false, the Termios
|
|
25
|
-
# library will be used to disable keystroke echoing for the duration of
|
|
26
|
-
# this method.
|
|
27
|
-
def prompt(prompt, echo=true)
|
|
28
|
-
$stdout.print(prompt)
|
|
29
|
-
$stdout.flush
|
|
27
|
+
def initialize(options = {}); end
|
|
30
28
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
$stdout.puts
|
|
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?
|
|
37
36
|
end
|
|
38
37
|
end
|
|
39
38
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def set_echo(enable)
|
|
44
|
-
term = ::Termios.getattr($stdin)
|
|
45
|
-
|
|
46
|
-
if enable
|
|
47
|
-
term.c_lflag |= (::Termios::ECHO | ::Termios::ICANON)
|
|
48
|
-
else
|
|
49
|
-
term.c_lflag &= ~::Termios::ECHO
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
::Termios.setattr($stdin, ::Termios::TCSANOW, term)
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
# Defines the prompt method to use when neither Highline nor Termios are
|
|
57
|
-
# installed.
|
|
58
|
-
module Clear
|
|
59
|
-
# Displays the prompt to $stdout and pulls the response from $stdin.
|
|
60
|
-
# Text is always echoed in the clear, regardless of the +echo+ setting.
|
|
61
|
-
# The first time a prompt is given and +echo+ is false, a warning will
|
|
62
|
-
# be written to $stderr recommending that either Highline or Termios
|
|
63
|
-
# be installed.
|
|
64
|
-
def prompt(prompt, echo=true)
|
|
65
|
-
@seen_warning ||= false
|
|
66
|
-
if !echo && !@seen_warning
|
|
67
|
-
$stderr.puts "Text will be echoed in the clear. Please install the HighLine or Termios libraries to suppress echoed text."
|
|
68
|
-
@seen_warning = true
|
|
69
|
-
end
|
|
70
|
-
|
|
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)
|
|
71
42
|
$stdout.print(prompt)
|
|
72
43
|
$stdout.flush
|
|
73
|
-
$stdin.gets.chomp
|
|
44
|
+
ret = $stdin.noecho(&:gets).chomp
|
|
45
|
+
$stdout.print("\n")
|
|
46
|
+
ret
|
|
74
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
|
|
75
52
|
end
|
|
76
|
-
end
|
|
77
53
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
PromptMethods::Highline
|
|
84
|
-
rescue LoadError
|
|
85
|
-
begin
|
|
86
|
-
require 'termios'
|
|
87
|
-
PromptMethods::Termios
|
|
88
|
-
rescue LoadError
|
|
89
|
-
PromptMethods::Clear
|
|
90
|
-
end
|
|
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)
|
|
91
59
|
end
|
|
60
|
+
end
|
|
92
61
|
|
|
93
|
-
end; end
|
|
62
|
+
end; end
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
require 'socket'
|
|
2
|
+
require 'rubygems'
|
|
2
3
|
require 'net/ssh/proxy/errors'
|
|
3
4
|
require 'net/ssh/ruby_compat'
|
|
4
5
|
|
|
@@ -66,13 +67,38 @@ module Net; module SSH; module Proxy
|
|
|
66
67
|
raise ConnectError, "#{e}: #{command_line}"
|
|
67
68
|
end
|
|
68
69
|
@command_line = command_line
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
if Gem.win_platform?
|
|
71
|
+
# read_nonblock and write_nonblock are not available on Windows
|
|
72
|
+
# pipe. Use sysread and syswrite as a replacement works.
|
|
73
|
+
def io.send(data, flag)
|
|
74
|
+
syswrite(data)
|
|
72
75
|
end
|
|
73
76
|
|
|
74
|
-
def recv(size)
|
|
75
|
-
|
|
77
|
+
def io.recv(size)
|
|
78
|
+
sysread(size)
|
|
79
|
+
end
|
|
80
|
+
else
|
|
81
|
+
def io.send(data, flag)
|
|
82
|
+
begin
|
|
83
|
+
result = write_nonblock(data)
|
|
84
|
+
rescue IO::WaitWritable, Errno::EINTR
|
|
85
|
+
IO.select(nil, [self])
|
|
86
|
+
retry
|
|
87
|
+
end
|
|
88
|
+
result
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def io.recv(size)
|
|
92
|
+
begin
|
|
93
|
+
result = read_nonblock(size)
|
|
94
|
+
rescue IO::WaitReadable, Errno::EINTR
|
|
95
|
+
timeout_in_seconds = 20
|
|
96
|
+
if IO.select([self], nil, [self], timeout_in_seconds) == nil
|
|
97
|
+
raise "Unexpected spurious read wakeup"
|
|
98
|
+
end
|
|
99
|
+
retry
|
|
100
|
+
end
|
|
101
|
+
result
|
|
76
102
|
end
|
|
77
103
|
end
|
|
78
104
|
io
|
data/lib/net/ssh/proxy/http.rb
CHANGED
|
@@ -8,7 +8,7 @@ module Net; module SSH; module Proxy
|
|
|
8
8
|
#
|
|
9
9
|
# require 'net/ssh/proxy/http'
|
|
10
10
|
#
|
|
11
|
-
# proxy = Net::SSH::Proxy::HTTP.new('
|
|
11
|
+
# proxy = Net::SSH::Proxy::HTTP.new('proxy_host', proxy_port)
|
|
12
12
|
# Net::SSH.start('host', 'user', :proxy => proxy) do |ssh|
|
|
13
13
|
# ...
|
|
14
14
|
# end
|
|
@@ -16,7 +16,7 @@ module Net; module SSH; module Proxy
|
|
|
16
16
|
# If the proxy requires authentication, you can pass :user and :password
|
|
17
17
|
# to the proxy's constructor:
|
|
18
18
|
#
|
|
19
|
-
# proxy = Net::SSH::Proxy::HTTP.new('
|
|
19
|
+
# proxy = Net::SSH::Proxy::HTTP.new('proxy_host', proxy_port,
|
|
20
20
|
# :user => "user", :password => "password")
|
|
21
21
|
#
|
|
22
22
|
# Note that HTTP digest authentication is not supported; Basic only at
|
|
@@ -48,8 +48,8 @@ module Net; module SSH; module Proxy
|
|
|
48
48
|
|
|
49
49
|
# Return a new socket connected to the given host and port via the
|
|
50
50
|
# proxy that was requested when the socket factory was instantiated.
|
|
51
|
-
def open(host, port, connection_options
|
|
52
|
-
socket =
|
|
51
|
+
def open(host, port, connection_options)
|
|
52
|
+
socket = establish_connection(connection_options[:timeout])
|
|
53
53
|
socket.write "CONNECT #{host}:#{port} HTTP/1.0\r\n"
|
|
54
54
|
|
|
55
55
|
if options[:user]
|
|
@@ -67,7 +67,12 @@ module Net; module SSH; module Proxy
|
|
|
67
67
|
raise ConnectError, resp.inspect
|
|
68
68
|
end
|
|
69
69
|
|
|
70
|
-
|
|
70
|
+
protected
|
|
71
|
+
|
|
72
|
+
def establish_connection(connect_timeout)
|
|
73
|
+
Socket.tcp(proxy_host, proxy_port, nil, nil,
|
|
74
|
+
connect_timeout: connect_timeout)
|
|
75
|
+
end
|
|
71
76
|
|
|
72
77
|
def parse_response(socket)
|
|
73
78
|
version, code, reason = socket.gets.chomp.split(/ /, 3)
|
|
@@ -82,13 +87,12 @@ module Net; module SSH; module Proxy
|
|
|
82
87
|
body = socket.read(headers["Content-Length"].to_i)
|
|
83
88
|
end
|
|
84
89
|
|
|
85
|
-
return { :
|
|
86
|
-
:
|
|
87
|
-
:
|
|
88
|
-
:
|
|
89
|
-
:
|
|
90
|
+
return { version: version,
|
|
91
|
+
code: code.to_i,
|
|
92
|
+
reason: reason,
|
|
93
|
+
headers: headers,
|
|
94
|
+
body: body }
|
|
90
95
|
end
|
|
91
|
-
|
|
92
96
|
end
|
|
93
97
|
|
|
94
98
|
end; end; end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
require 'socket'
|
|
2
|
+
require 'openssl'
|
|
3
|
+
require 'net/ssh/proxy/errors'
|
|
4
|
+
require 'net/ssh/proxy/http'
|
|
5
|
+
|
|
6
|
+
module Net; module SSH; module Proxy
|
|
7
|
+
|
|
8
|
+
# A specialization of the HTTP proxy which encrypts the whole connection
|
|
9
|
+
# using OpenSSL. This has the advantage that proxy authentication
|
|
10
|
+
# information is not sent in plaintext.
|
|
11
|
+
class HTTPS < HTTP
|
|
12
|
+
|
|
13
|
+
# Create a new socket factory that tunnels via the given host and
|
|
14
|
+
# port. The +options+ parameter is a hash of additional settings that
|
|
15
|
+
# can be used to tweak this proxy connection. In addition to the options
|
|
16
|
+
# taken by Net::SSH::Proxy::HTTP it supports:
|
|
17
|
+
#
|
|
18
|
+
# * :ssl_context => the SSL configuration to use for the connection
|
|
19
|
+
def initialize(proxy_host, proxy_port=80, options={})
|
|
20
|
+
@ssl_context = options.delete(:ssl_context) ||
|
|
21
|
+
OpenSSL::SSL::SSLContext.new
|
|
22
|
+
super(proxy_host, proxy_port, options)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
protected
|
|
26
|
+
|
|
27
|
+
# Shim to make OpenSSL::SSL::SSLSocket behave like a regular TCPSocket
|
|
28
|
+
# for all intents and purposes of Net::SSH::BufferedIo
|
|
29
|
+
module SSLSocketCompatibility
|
|
30
|
+
def self.extended(object) #:nodoc:
|
|
31
|
+
object.define_singleton_method(:recv, object.method(:sysread))
|
|
32
|
+
object.sync_close = true
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def send(data, _opts)
|
|
36
|
+
syswrite(data)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def establish_connection(connect_timeout)
|
|
41
|
+
plain_socket = super(connect_timeout)
|
|
42
|
+
OpenSSL::SSL::SSLSocket.new(plain_socket, @ssl_context).tap do |socket|
|
|
43
|
+
socket.extend(SSLSocketCompatibility)
|
|
44
|
+
socket.connect
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
end; end; end
|
data/lib/net/ssh/proxy/socks4.rb
CHANGED
|
@@ -48,7 +48,8 @@ module Net
|
|
|
48
48
|
# Return a new socket connected to the given host and port via the
|
|
49
49
|
# proxy that was requested when the socket factory was instantiated.
|
|
50
50
|
def open(host, port, connection_options)
|
|
51
|
-
socket =
|
|
51
|
+
socket = Socket.tcp(proxy_host, proxy_port, nil, nil,
|
|
52
|
+
connect_timeout: connection_options[:timeout])
|
|
52
53
|
ip_addr = IPAddr.new(Resolv.getaddress(host))
|
|
53
54
|
|
|
54
55
|
packet = [VERSION, CONNECT, port.to_i, ip_addr.to_i, options[:user]].pack("CCnNZ*")
|
data/lib/net/ssh/proxy/socks5.rb
CHANGED
|
@@ -62,8 +62,9 @@ module Net
|
|
|
62
62
|
|
|
63
63
|
# Return a new socket connected to the given host and port via the
|
|
64
64
|
# proxy that was requested when the socket factory was instantiated.
|
|
65
|
-
def open(host, port, connection_options
|
|
66
|
-
socket =
|
|
65
|
+
def open(host, port, connection_options)
|
|
66
|
+
socket = Socket.tcp(proxy_host, proxy_port, nil, nil,
|
|
67
|
+
connect_timeout: connection_options[:timeout])
|
|
67
68
|
|
|
68
69
|
methods = [METHOD_NO_AUTH]
|
|
69
70
|
methods << METHOD_PASSWD if options[:user]
|
data/lib/net/ssh/ruby_compat.rb
CHANGED
|
@@ -9,11 +9,6 @@ class String
|
|
|
9
9
|
self[index] = c
|
|
10
10
|
end
|
|
11
11
|
end
|
|
12
|
-
if RUBY_VERSION < "1.8.7"
|
|
13
|
-
def bytesize
|
|
14
|
-
self.size
|
|
15
|
-
end
|
|
16
|
-
end
|
|
17
12
|
end
|
|
18
13
|
|
|
19
14
|
module Net; module SSH
|
|
@@ -21,31 +16,9 @@ module Net; module SSH
|
|
|
21
16
|
# This class contains miscellaneous patches and workarounds
|
|
22
17
|
# for different ruby implementations.
|
|
23
18
|
class Compat
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
# See: http://net-ssh.lighthouseapp.com/projects/36253/tickets/1-ioselect-threading-bug-in-ruby-18
|
|
27
|
-
# The root issue is documented here: http://redmine.ruby-lang.org/issues/show/1993
|
|
28
|
-
if RUBY_VERSION >= '1.9' || RUBY_PLATFORM == 'java'
|
|
29
|
-
def self.io_select(*params)
|
|
30
|
-
IO.select(*params)
|
|
31
|
-
end
|
|
32
|
-
else
|
|
33
|
-
SELECT_MUTEX = Mutex.new
|
|
34
|
-
def self.io_select(*params)
|
|
35
|
-
# It should be safe to wrap calls in a mutex when the timeout is 0
|
|
36
|
-
# (that is, the call is not supposed to block).
|
|
37
|
-
# We leave blocking calls unprotected to avoid causing deadlocks.
|
|
38
|
-
# This should still catch the main case for Capistrano users.
|
|
39
|
-
if params[3] == 0
|
|
40
|
-
SELECT_MUTEX.synchronize do
|
|
41
|
-
IO.select(*params)
|
|
42
|
-
end
|
|
43
|
-
else
|
|
44
|
-
IO.select(*params)
|
|
45
|
-
end
|
|
46
|
-
end
|
|
19
|
+
def self.io_select(*params)
|
|
20
|
+
IO.select(*params)
|
|
47
21
|
end
|
|
48
|
-
|
|
49
22
|
end
|
|
50
23
|
|
|
51
24
|
end; end
|
|
@@ -91,6 +91,7 @@ module Net; module SSH; module Service
|
|
|
91
91
|
|
|
92
92
|
channel.on_open_failed do |ch, code, description|
|
|
93
93
|
channel.error { "could not establish direct channel: #{description} (#{code})" }
|
|
94
|
+
session.stop_listening_to(channel[:socket])
|
|
94
95
|
channel[:socket].close
|
|
95
96
|
end
|
|
96
97
|
end
|
|
@@ -273,7 +274,6 @@ module Net; module SSH; module Service
|
|
|
273
274
|
ch[:socket].enqueue(data)
|
|
274
275
|
end
|
|
275
276
|
|
|
276
|
-
# Handles server close on the sending side by Miklós Fazekas
|
|
277
277
|
channel.on_eof do |ch|
|
|
278
278
|
debug { "eof #{type} on #{type} forwarded channel" }
|
|
279
279
|
begin
|
|
@@ -357,7 +357,7 @@ module Net; module SSH; module Service
|
|
|
357
357
|
channel[:invisible] = true
|
|
358
358
|
|
|
359
359
|
begin
|
|
360
|
-
agent = Authentication::Agent.connect(logger)
|
|
360
|
+
agent = Authentication::Agent.connect(logger, session.options[:agent_socket_factory])
|
|
361
361
|
if (agent.socket.is_a? ::IO)
|
|
362
362
|
prepare_client(agent.socket, channel, :agent)
|
|
363
363
|
else
|
data/lib/net/ssh/test/channel.rb
CHANGED
|
@@ -98,6 +98,13 @@ module Net; module SSH; module Test
|
|
|
98
98
|
script.sends_channel_close(self)
|
|
99
99
|
end
|
|
100
100
|
|
|
101
|
+
# Scripts the sending of a "request pty" request packet across the channel.
|
|
102
|
+
#
|
|
103
|
+
# channel.sends_request_pty
|
|
104
|
+
def sends_request_pty
|
|
105
|
+
script.sends_channel_request_pty(self)
|
|
106
|
+
end
|
|
107
|
+
|
|
101
108
|
# Scripts the reception of a channel data packet from the remote end.
|
|
102
109
|
#
|
|
103
110
|
# channel.gets_data "bar"
|
|
@@ -113,6 +113,22 @@ module Net; module SSH; module Test
|
|
|
113
113
|
base.extend(ClassMethods)
|
|
114
114
|
end
|
|
115
115
|
|
|
116
|
+
@extension_enabled = false
|
|
117
|
+
|
|
118
|
+
def self.with_test_extension(&block)
|
|
119
|
+
orig_value = @extension_enabled
|
|
120
|
+
@extension_enabled = true
|
|
121
|
+
begin
|
|
122
|
+
yield
|
|
123
|
+
ensure
|
|
124
|
+
@extension_enabled = orig_value
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def self.extension_enabled?
|
|
129
|
+
@extension_enabled
|
|
130
|
+
end
|
|
131
|
+
|
|
116
132
|
module ClassMethods
|
|
117
133
|
def self.extended(obj) #:nodoc:
|
|
118
134
|
class <<obj
|
|
@@ -125,6 +141,7 @@ module Net; module SSH; module Test
|
|
|
125
141
|
# writers, and errors arrays are either nil, or contain only objects
|
|
126
142
|
# that mix in Net::SSH::Test::Extensions::BufferedIo.
|
|
127
143
|
def select_for_test(readers=nil, writers=nil, errors=nil, wait=nil)
|
|
144
|
+
return select_for_real(readers, writers, errors, wait) unless Net::SSH::Test::Extensions::IO.extension_enabled?
|
|
128
145
|
ready_readers = Array(readers).select { |r| r.select_for_read? }
|
|
129
146
|
ready_writers = Array(writers).select { |r| r.select_for_write? }
|
|
130
147
|
ready_errors = Array(errors).select { |r| r.select_for_error? }
|