net-ssh 2.9.2 → 4.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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? }
|