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.
Files changed (111) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/ci.yml +93 -0
  3. data/.gitignore +13 -0
  4. data/.rubocop.yml +21 -0
  5. data/.rubocop_todo.yml +1074 -0
  6. data/.travis.yml +51 -0
  7. data/CHANGES.txt +698 -0
  8. data/Gemfile +13 -0
  9. data/Gemfile.noed25519 +12 -0
  10. data/ISSUE_TEMPLATE.md +30 -0
  11. data/LICENSE.txt +19 -0
  12. data/Manifest +132 -0
  13. data/README.md +287 -0
  14. data/Rakefile +105 -0
  15. data/THANKS.txt +110 -0
  16. data/appveyor.yml +58 -0
  17. data/lib/net/ssh/authentication/agent.rb +284 -0
  18. data/lib/net/ssh/authentication/certificate.rb +183 -0
  19. data/lib/net/ssh/authentication/constants.rb +20 -0
  20. data/lib/net/ssh/authentication/ed25519.rb +185 -0
  21. data/lib/net/ssh/authentication/ed25519_loader.rb +31 -0
  22. data/lib/net/ssh/authentication/key_manager.rb +297 -0
  23. data/lib/net/ssh/authentication/methods/abstract.rb +69 -0
  24. data/lib/net/ssh/authentication/methods/hostbased.rb +72 -0
  25. data/lib/net/ssh/authentication/methods/keyboard_interactive.rb +77 -0
  26. data/lib/net/ssh/authentication/methods/none.rb +34 -0
  27. data/lib/net/ssh/authentication/methods/password.rb +80 -0
  28. data/lib/net/ssh/authentication/methods/publickey.rb +95 -0
  29. data/lib/net/ssh/authentication/pageant.rb +497 -0
  30. data/lib/net/ssh/authentication/pub_key_fingerprint.rb +43 -0
  31. data/lib/net/ssh/authentication/session.rb +163 -0
  32. data/lib/net/ssh/buffer.rb +434 -0
  33. data/lib/net/ssh/buffered_io.rb +202 -0
  34. data/lib/net/ssh/config.rb +406 -0
  35. data/lib/net/ssh/connection/channel.rb +695 -0
  36. data/lib/net/ssh/connection/constants.rb +33 -0
  37. data/lib/net/ssh/connection/event_loop.rb +123 -0
  38. data/lib/net/ssh/connection/keepalive.rb +59 -0
  39. data/lib/net/ssh/connection/session.rb +712 -0
  40. data/lib/net/ssh/connection/term.rb +180 -0
  41. data/lib/net/ssh/errors.rb +106 -0
  42. data/lib/net/ssh/key_factory.rb +218 -0
  43. data/lib/net/ssh/known_hosts.rb +264 -0
  44. data/lib/net/ssh/loggable.rb +62 -0
  45. data/lib/net/ssh/packet.rb +106 -0
  46. data/lib/net/ssh/prompt.rb +62 -0
  47. data/lib/net/ssh/proxy/command.rb +123 -0
  48. data/lib/net/ssh/proxy/errors.rb +16 -0
  49. data/lib/net/ssh/proxy/http.rb +98 -0
  50. data/lib/net/ssh/proxy/https.rb +50 -0
  51. data/lib/net/ssh/proxy/jump.rb +54 -0
  52. data/lib/net/ssh/proxy/socks4.rb +67 -0
  53. data/lib/net/ssh/proxy/socks5.rb +140 -0
  54. data/lib/net/ssh/service/forward.rb +426 -0
  55. data/lib/net/ssh/test/channel.rb +147 -0
  56. data/lib/net/ssh/test/extensions.rb +173 -0
  57. data/lib/net/ssh/test/kex.rb +46 -0
  58. data/lib/net/ssh/test/local_packet.rb +53 -0
  59. data/lib/net/ssh/test/packet.rb +101 -0
  60. data/lib/net/ssh/test/remote_packet.rb +40 -0
  61. data/lib/net/ssh/test/script.rb +180 -0
  62. data/lib/net/ssh/test/socket.rb +65 -0
  63. data/lib/net/ssh/test.rb +94 -0
  64. data/lib/net/ssh/transport/algorithms.rb +502 -0
  65. data/lib/net/ssh/transport/cipher_factory.rb +103 -0
  66. data/lib/net/ssh/transport/constants.rb +40 -0
  67. data/lib/net/ssh/transport/ctr.rb +115 -0
  68. data/lib/net/ssh/transport/hmac/abstract.rb +97 -0
  69. data/lib/net/ssh/transport/hmac/md5.rb +10 -0
  70. data/lib/net/ssh/transport/hmac/md5_96.rb +9 -0
  71. data/lib/net/ssh/transport/hmac/none.rb +13 -0
  72. data/lib/net/ssh/transport/hmac/ripemd160.rb +11 -0
  73. data/lib/net/ssh/transport/hmac/sha1.rb +11 -0
  74. data/lib/net/ssh/transport/hmac/sha1_96.rb +9 -0
  75. data/lib/net/ssh/transport/hmac/sha2_256.rb +11 -0
  76. data/lib/net/ssh/transport/hmac/sha2_256_96.rb +9 -0
  77. data/lib/net/ssh/transport/hmac/sha2_256_etm.rb +12 -0
  78. data/lib/net/ssh/transport/hmac/sha2_512.rb +11 -0
  79. data/lib/net/ssh/transport/hmac/sha2_512_96.rb +9 -0
  80. data/lib/net/ssh/transport/hmac/sha2_512_etm.rb +12 -0
  81. data/lib/net/ssh/transport/hmac.rb +47 -0
  82. data/lib/net/ssh/transport/identity_cipher.rb +57 -0
  83. data/lib/net/ssh/transport/kex/abstract.rb +130 -0
  84. data/lib/net/ssh/transport/kex/abstract5656.rb +72 -0
  85. data/lib/net/ssh/transport/kex/curve25519_sha256.rb +39 -0
  86. data/lib/net/ssh/transport/kex/curve25519_sha256_loader.rb +30 -0
  87. data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha1.rb +37 -0
  88. data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha256.rb +11 -0
  89. data/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb +122 -0
  90. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb +72 -0
  91. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha256.rb +11 -0
  92. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp256.rb +39 -0
  93. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp384.rb +21 -0
  94. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp521.rb +21 -0
  95. data/lib/net/ssh/transport/kex.rb +31 -0
  96. data/lib/net/ssh/transport/key_expander.rb +30 -0
  97. data/lib/net/ssh/transport/openssl.rb +253 -0
  98. data/lib/net/ssh/transport/packet_stream.rb +280 -0
  99. data/lib/net/ssh/transport/server_version.rb +77 -0
  100. data/lib/net/ssh/transport/session.rb +354 -0
  101. data/lib/net/ssh/transport/state.rb +208 -0
  102. data/lib/net/ssh/verifiers/accept_new.rb +33 -0
  103. data/lib/net/ssh/verifiers/accept_new_or_local_tunnel.rb +33 -0
  104. data/lib/net/ssh/verifiers/always.rb +58 -0
  105. data/lib/net/ssh/verifiers/never.rb +19 -0
  106. data/lib/net/ssh/version.rb +68 -0
  107. data/lib/net/ssh.rb +330 -0
  108. data/net-ssh-public_cert.pem +20 -0
  109. data/net-ssh.gemspec +44 -0
  110. data/support/ssh_tunnel_bug.rb +65 -0
  111. 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