net-ssh 5.0.0.beta1 → 5.0.0.beta2

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 (87) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/.rubocop_todo.yml +98 -258
  5. data/CHANGES.txt +8 -0
  6. data/Gemfile +1 -3
  7. data/Rakefile +37 -39
  8. data/lib/net/ssh.rb +26 -25
  9. data/lib/net/ssh/authentication/agent.rb +228 -225
  10. data/lib/net/ssh/authentication/certificate.rb +166 -164
  11. data/lib/net/ssh/authentication/constants.rb +17 -14
  12. data/lib/net/ssh/authentication/ed25519.rb +107 -104
  13. data/lib/net/ssh/authentication/ed25519_loader.rb +32 -28
  14. data/lib/net/ssh/authentication/key_manager.rb +5 -3
  15. data/lib/net/ssh/authentication/methods/abstract.rb +53 -47
  16. data/lib/net/ssh/authentication/methods/hostbased.rb +32 -33
  17. data/lib/net/ssh/authentication/methods/keyboard_interactive.rb +2 -4
  18. data/lib/net/ssh/authentication/methods/none.rb +10 -10
  19. data/lib/net/ssh/authentication/methods/password.rb +13 -13
  20. data/lib/net/ssh/authentication/methods/publickey.rb +54 -55
  21. data/lib/net/ssh/authentication/pageant.rb +468 -465
  22. data/lib/net/ssh/authentication/pub_key_fingerprint.rb +44 -0
  23. data/lib/net/ssh/authentication/session.rb +127 -123
  24. data/lib/net/ssh/buffer.rb +305 -303
  25. data/lib/net/ssh/buffered_io.rb +163 -162
  26. data/lib/net/ssh/config.rb +230 -227
  27. data/lib/net/ssh/connection/channel.rb +659 -654
  28. data/lib/net/ssh/connection/constants.rb +30 -26
  29. data/lib/net/ssh/connection/event_loop.rb +108 -104
  30. data/lib/net/ssh/connection/keepalive.rb +54 -50
  31. data/lib/net/ssh/connection/session.rb +677 -678
  32. data/lib/net/ssh/connection/term.rb +180 -176
  33. data/lib/net/ssh/errors.rb +101 -99
  34. data/lib/net/ssh/key_factory.rb +108 -108
  35. data/lib/net/ssh/known_hosts.rb +148 -154
  36. data/lib/net/ssh/loggable.rb +56 -54
  37. data/lib/net/ssh/packet.rb +82 -78
  38. data/lib/net/ssh/prompt.rb +55 -53
  39. data/lib/net/ssh/proxy/command.rb +103 -102
  40. data/lib/net/ssh/proxy/errors.rb +12 -8
  41. data/lib/net/ssh/proxy/http.rb +92 -91
  42. data/lib/net/ssh/proxy/https.rb +42 -39
  43. data/lib/net/ssh/proxy/jump.rb +50 -47
  44. data/lib/net/ssh/proxy/socks4.rb +0 -2
  45. data/lib/net/ssh/proxy/socks5.rb +11 -11
  46. data/lib/net/ssh/ruby_compat.rb +1 -0
  47. data/lib/net/ssh/service/forward.rb +364 -362
  48. data/lib/net/ssh/test.rb +85 -83
  49. data/lib/net/ssh/test/channel.rb +146 -142
  50. data/lib/net/ssh/test/extensions.rb +148 -146
  51. data/lib/net/ssh/test/kex.rb +35 -31
  52. data/lib/net/ssh/test/local_packet.rb +48 -44
  53. data/lib/net/ssh/test/packet.rb +87 -84
  54. data/lib/net/ssh/test/remote_packet.rb +35 -31
  55. data/lib/net/ssh/test/script.rb +173 -171
  56. data/lib/net/ssh/test/socket.rb +59 -55
  57. data/lib/net/ssh/transport/algorithms.rb +413 -412
  58. data/lib/net/ssh/transport/cipher_factory.rb +108 -105
  59. data/lib/net/ssh/transport/constants.rb +35 -31
  60. data/lib/net/ssh/transport/ctr.rb +1 -1
  61. data/lib/net/ssh/transport/hmac.rb +1 -1
  62. data/lib/net/ssh/transport/hmac/abstract.rb +67 -64
  63. data/lib/net/ssh/transport/hmac/sha2_256_96.rb +1 -1
  64. data/lib/net/ssh/transport/hmac/sha2_512_96.rb +1 -1
  65. data/lib/net/ssh/transport/identity_cipher.rb +55 -51
  66. data/lib/net/ssh/transport/kex.rb +2 -4
  67. data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha1.rb +47 -40
  68. data/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb +201 -197
  69. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb +53 -56
  70. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp256.rb +94 -87
  71. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp384.rb +17 -10
  72. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp521.rb +17 -10
  73. data/lib/net/ssh/transport/key_expander.rb +29 -25
  74. data/lib/net/ssh/transport/openssl.rb +17 -30
  75. data/lib/net/ssh/transport/packet_stream.rb +193 -192
  76. data/lib/net/ssh/transport/server_version.rb +64 -66
  77. data/lib/net/ssh/transport/session.rb +286 -284
  78. data/lib/net/ssh/transport/state.rb +198 -196
  79. data/lib/net/ssh/verifiers/lenient.rb +29 -25
  80. data/lib/net/ssh/verifiers/null.rb +13 -9
  81. data/lib/net/ssh/verifiers/secure.rb +45 -45
  82. data/lib/net/ssh/verifiers/strict.rb +20 -16
  83. data/lib/net/ssh/version.rb +55 -53
  84. data/net-ssh.gemspec +4 -4
  85. data/support/ssh_tunnel_bug.rb +2 -2
  86. metadata +25 -24
  87. metadata.gz.sig +0 -0
@@ -1,61 +1,63 @@
1
- module Net; module SSH
2
-
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
-
1
+ module Net
2
+ module SSH
3
+
4
+ # A simple module to make logging easier to deal with. It assumes that the
5
+ # logger instance (if not nil) quacks like a Logger object (in Ruby's
6
+ # standard library). Although used primarily internally by Net::SSH, it
7
+ # can easily be used to add Net::SSH-like logging to your own programs.
8
+ #
9
+ # class MyClass
10
+ # include Net::SSH::Loggable
11
+ # end
12
+ #
13
+ # Net::SSH.start(...) do |ssh|
14
+ # obj = MyClass.new
15
+ # obj.logger = ssh.logger
16
+ # ...
17
+ # end
18
+ module Loggable
19
+ # The logger instance that will be used to log messages. If nil, nothing
20
+ # will be logged.
21
+ attr_accessor :logger
22
+
23
+ # Displays the result of yielding if the log level is Logger::DEBUG or
24
+ # greater.
25
+ def debug
26
+ logger.add(Logger::DEBUG, nil, facility) { yield } if logger && logger.debug?
27
+ end
28
+
29
+ # Displays the result of yielding if the log level is Logger::INFO or
30
+ # greater.
31
+ def info
32
+ logger.add(Logger::INFO, nil, facility) { yield } if logger && logger.info?
33
+ end
34
+
35
+ # Displays the result of yielding if the log level is Logger::WARN or
36
+ # greater. (Called lwarn to avoid shadowing with Kernel#warn.)
37
+ def lwarn
38
+ logger.add(Logger::WARN, nil, facility) { yield } if logger && logger.warn?
39
+ end
40
+
41
+ # Displays the result of yielding if the log level is Logger:ERROR or
42
+ # greater.
43
+ def error
44
+ logger.add(Logger::ERROR, nil, facility) { yield } if logger && logger.error?
45
+ end
46
+
47
+ # Displays the result of yielding if the log level is Logger::FATAL or
48
+ # greater.
49
+ def fatal
50
+ logger.add(Logger::FATAL, nil, facility) { yield } if logger && logger.fatal?
51
+ end
52
+
53
+ private
54
+
54
55
  # Sets the "facility" value, used for reporting where a log message
55
56
  # originates. It defaults to the name of class with the object_id
56
57
  # appended.
57
58
  def facility
58
59
  @facility ||= self.class.name.gsub(/::/, ".").gsub(/([a-z])([A-Z])/, "\\1_\\2").downcase + "[%x]" % object_id
59
60
  end
61
+ end
60
62
  end
61
- end; end
63
+ end
@@ -3,100 +3,104 @@ require 'net/ssh/transport/constants'
3
3
  require 'net/ssh/authentication/constants'
4
4
  require 'net/ssh/connection/constants'
5
5
 
6
- module Net; module SSH
6
+ module Net
7
+ module SSH
7
8
 
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.
9
+ # A specialization of Buffer that knows the format of certain common
10
+ # packet types. It auto-parses those packet types, and allows them to
11
+ # be accessed via the #[] accessor.
28
12
  #
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.
13
+ # data = some_channel_request_packet
14
+ # packet = Net::SSH::Packet.new(data)
32
15
  #
33
- # register DISCONNECT, [:reason_code, :long], [:description, :string], [:language, :string]
34
- def self.register(type, *pairs)
35
- @@types[type] = pairs
36
- end
16
+ # p packet.type #-> 98 (CHANNEL_REQUEST)
17
+ # p packet[:request]
18
+ # p packet[:want_reply]
19
+ #
20
+ # This is used exclusively internally by Net::SSH, and unless you're doing
21
+ # protocol-level manipulation or are extending Net::SSH in some way, you'll
22
+ # never need to use this class directly.
23
+ class Packet < Buffer
24
+ @@types = {}
37
25
 
38
- include Transport::Constants, Authentication::Constants, Connection::Constants
26
+ # Register a new packet type that should be recognized and auto-parsed by
27
+ # Net::SSH::Packet. Note that any packet type that is not preregistered
28
+ # will not be autoparsed.
29
+ #
30
+ # The +pairs+ parameter must be either empty, or an array of two-element
31
+ # tuples, where the first element of each tuple is the name of the field,
32
+ # and the second is the type.
33
+ #
34
+ # register DISCONNECT, [:reason_code, :long], [:description, :string], [:language, :string]
35
+ def self.register(type, *pairs)
36
+ @@types[type] = pairs
37
+ end
39
38
 
40
- #--
41
- # These are the recognized packet types. All other packet types will be
42
- # accepted, but not auto-parsed, requiring the client to parse the
43
- # fields using the methods provided by Net::SSH::Buffer.
44
- #++
39
+ include Connection::Constants
40
+ include Authentication::Constants
41
+ include Transport::Constants
45
42
 
46
- register DISCONNECT, [:reason_code, :long], [:description, :string], [:language, :string]
47
- register IGNORE, [:data, :string]
48
- register UNIMPLEMENTED, [:number, :long]
49
- register DEBUG, [:always_display, :bool], [:message, :string], [:language, :string]
50
- register SERVICE_ACCEPT, [:service_name, :string]
51
- register USERAUTH_BANNER, [:message, :string], [:language, :string]
52
- register USERAUTH_FAILURE, [:authentications, :string], [:partial_success, :bool]
53
- register GLOBAL_REQUEST, [:request_type, :string], [:want_reply, :bool], [:request_data, :buffer]
54
- register CHANNEL_OPEN, [:channel_type, :string], [:remote_id, :long], [:window_size, :long], [:packet_size, :long]
55
- register CHANNEL_OPEN_CONFIRMATION, [:local_id, :long], [:remote_id, :long], [:window_size, :long], [:packet_size, :long]
56
- register CHANNEL_OPEN_FAILURE, [:local_id, :long], [:reason_code, :long], [:description, :string], [:language, :string]
57
- register CHANNEL_WINDOW_ADJUST, [:local_id, :long], [:extra_bytes, :long]
58
- register CHANNEL_DATA, [:local_id, :long], [:data, :string]
59
- register CHANNEL_EXTENDED_DATA, [:local_id, :long], [:data_type, :long], [:data, :string]
60
- register CHANNEL_EOF, [:local_id, :long]
61
- register CHANNEL_CLOSE, [:local_id, :long]
62
- register CHANNEL_REQUEST, [:local_id, :long], [:request, :string], [:want_reply, :bool], [:request_data, :buffer]
63
- register CHANNEL_SUCCESS, [:local_id, :long]
64
- register CHANNEL_FAILURE, [:local_id, :long]
43
+ #--
44
+ # These are the recognized packet types. All other packet types will be
45
+ # accepted, but not auto-parsed, requiring the client to parse the
46
+ # fields using the methods provided by Net::SSH::Buffer.
47
+ #++
65
48
 
66
- # The (integer) type of this packet.
67
- attr_reader :type
49
+ register DISCONNECT, %i[reason_code long], %i[description string], %i[language string]
50
+ register IGNORE, %i[data string]
51
+ register UNIMPLEMENTED, %i[number long]
52
+ register DEBUG, %i[always_display bool], %i[message string], %i[language string]
53
+ register SERVICE_ACCEPT, %i[service_name string]
54
+ register USERAUTH_BANNER, %i[message string], %i[language string]
55
+ register USERAUTH_FAILURE, %i[authentications string], %i[partial_success bool]
56
+ register GLOBAL_REQUEST, %i[request_type string], %i[want_reply bool], %i[request_data buffer]
57
+ register CHANNEL_OPEN, %i[channel_type string], %i[remote_id long], %i[window_size long], %i[packet_size long]
58
+ register CHANNEL_OPEN_CONFIRMATION, %i[local_id long], %i[remote_id long], %i[window_size long], %i[packet_size long]
59
+ register CHANNEL_OPEN_FAILURE, %i[local_id long], %i[reason_code long], %i[description string], %i[language string]
60
+ register CHANNEL_WINDOW_ADJUST, %i[local_id long], %i[extra_bytes long]
61
+ register CHANNEL_DATA, %i[local_id long], %i[data string]
62
+ register CHANNEL_EXTENDED_DATA, %i[local_id long], %i[data_type long], %i[data string]
63
+ register CHANNEL_EOF, %i[local_id long]
64
+ register CHANNEL_CLOSE, %i[local_id long]
65
+ register CHANNEL_REQUEST, %i[local_id long], %i[request string], %i[want_reply bool], %i[request_data buffer]
66
+ register CHANNEL_SUCCESS, %i[local_id long]
67
+ register CHANNEL_FAILURE, %i[local_id long]
68
68
 
69
- # Create a new packet from the given payload. This will automatically
70
- # parse the packet if it is one that has been previously registered with
71
- # Packet.register; otherwise, the packet will need to be manually parsed
72
- # using the methods provided in the Net::SSH::Buffer superclass.
73
- def initialize(payload)
74
- @named_elements = {}
75
- super
76
- @type = read_byte
77
- instantiate!
78
- end
69
+ # The (integer) type of this packet.
70
+ attr_reader :type
79
71
 
80
- # Access one of the auto-parsed fields by name. Raises an error if no
81
- # element by the given name exists.
82
- def [](name)
83
- name = name.to_sym
84
- raise ArgumentError, "no such element #{name}" unless @named_elements.key?(name)
85
- @named_elements[name]
86
- end
72
+ # Create a new packet from the given payload. This will automatically
73
+ # parse the packet if it is one that has been previously registered with
74
+ # Packet.register; otherwise, the packet will need to be manually parsed
75
+ # using the methods provided in the Net::SSH::Buffer superclass.
76
+ def initialize(payload)
77
+ @named_elements = {}
78
+ super
79
+ @type = read_byte
80
+ instantiate!
81
+ end
87
82
 
88
- private
83
+ # Access one of the auto-parsed fields by name. Raises an error if no
84
+ # element by the given name exists.
85
+ def [](name)
86
+ name = name.to_sym
87
+ raise ArgumentError, "no such element #{name}" unless @named_elements.key?(name)
88
+ @named_elements[name]
89
+ end
90
+
91
+ private
89
92
 
90
93
  # Parse the packet's contents and assign the named elements, as described
91
94
  # by the registered format for the packet.
92
95
  def instantiate!
93
96
  (@@types[type] || []).each do |name, datatype|
94
97
  @named_elements[name.to_sym] = if datatype == :buffer
95
- remainder_as_buffer
96
- else
97
- send("read_#{datatype}")
98
- end
98
+ remainder_as_buffer
99
+ else
100
+ send("read_#{datatype}")
101
+ end
99
102
  end
100
103
  end
104
+ end
101
105
  end
102
- end; end
106
+ end
@@ -1,62 +1,64 @@
1
1
  require 'io/console'
2
2
 
3
- module Net; module SSH
3
+ module Net
4
+ module SSH
4
5
 
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?
6
+ # Default prompt implementation, called for asking password from user.
7
+ # It will never be instantiated directly, but will instead be created for
8
+ # you automatically.
9
+ #
10
+ # A custom prompt objects can implement caching, or different UI. The prompt
11
+ # object should implemnted a start method, which should return something implementing
12
+ # ask and success. Net::SSH uses it like:
13
+ #
14
+ # prompter = options[:password_prompt].start({type:'password'})
15
+ # while !ok && max_retries < 3
16
+ # user = prompter.ask("user: ", {}, true)
17
+ # password = prompter.ask("password: ", {}, false)
18
+ # ok = send(user, password)
19
+ # prompter.sucess if ok
20
+ # end
21
+ #
22
+ class Prompt
23
+ # factory
24
+ def self.default(options = {})
25
+ @default ||= new(options)
26
+ end
27
+
28
+ def initialize(options = {}); end
29
+
30
+ # default prompt object implementation. More sophisticated implemenetations
31
+ # might implement caching.
32
+ class Prompter
33
+ def initialize(info)
34
+ if info[:type] == 'keyboard-interactive'
35
+ $stdout.puts(info[:name]) unless info[:name].empty?
36
+ $stdout.puts(info[:instruction]) unless info[:instruction].empty?
37
+ end
38
+ end
39
+
40
+ # ask input from user, a prompter might ask for multiple inputs
41
+ # (like user and password) in a single session.
42
+ def ask(prompt, echo=true)
43
+ $stdout.print(prompt)
44
+ $stdout.flush
45
+ ret = $stdin.noecho(&:gets).chomp
46
+ $stdout.print("\n")
47
+ ret
36
48
  end
49
+
50
+ # success method will be called when the password was accepted
51
+ # It's a good time to save password asked to a cache.
52
+ def success; end
37
53
  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
54
+
55
+ # start password session. Multiple questions might be asked multiple times
56
+ # on the returned object. Info hash tries to uniquely identify the password
57
+ # session, so caching implementations can save passwords properly.
58
+ def start(info)
59
+ Prompter.new(info)
47
60
  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
61
  end
53
62
 
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
63
  end
61
-
62
- end; end
64
+ end
@@ -3,121 +3,122 @@ require 'rubygems'
3
3
  require 'net/ssh/proxy/errors'
4
4
  require 'net/ssh/ruby_compat'
5
5
 
6
- module Net; module SSH; module Proxy
6
+ module Net
7
+ module SSH
8
+ module Proxy
7
9
 
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
10
+ # An implementation of a command proxy. To use it, instantiate it,
11
+ # then pass the instantiated object via the :proxy key to
12
+ # Net::SSH.start:
13
+ #
14
+ # require 'net/ssh/proxy/command'
15
+ #
16
+ # proxy = Net::SSH::Proxy::Command.new('ssh relay nc %h %p')
17
+ # Net::SSH.start('host', 'user', :proxy => proxy) do |ssh|
18
+ # ...
19
+ # end
20
+ class Command
21
+ # The command line template
22
+ attr_reader :command_line_template
19
23
 
20
- # The command line template
21
- attr_reader :command_line_template
24
+ # The command line for the session
25
+ attr_reader :command_line
22
26
 
23
- # The command line for the session
24
- attr_reader :command_line
27
+ # Timeout in seconds in open, defaults to 60
28
+ attr_accessor :timeout
25
29
 
26
- # Timeout in seconds in open, defaults to 60
27
- attr_accessor :timeout
28
-
29
- # Create a new socket factory that tunnels via a command executed
30
- # with the user's shell, which is composed from the given command
31
- # template. In the command template, `%h' will be substituted by
32
- # the host name to connect and `%p' by the port.
33
- def initialize(command_line_template)
34
- @command_line_template = command_line_template
35
- @command_line = nil
36
- @timeout = 60
37
- end
30
+ # Create a new socket factory that tunnels via a command executed
31
+ # with the user's shell, which is composed from the given command
32
+ # template. In the command template, `%h' will be substituted by
33
+ # the host name to connect and `%p' by the port.
34
+ def initialize(command_line_template)
35
+ @command_line_template = command_line_template
36
+ @command_line = nil
37
+ @timeout = 60
38
+ end
38
39
 
39
- # Return a new socket connected to the given host and port via the
40
- # proxy that was requested when the socket factory was instantiated.
41
- def open(host, port, connection_options = nil)
42
- command_line = @command_line_template.gsub(/%(.)/) {
43
- case $1
44
- when 'h'
45
- host
46
- when 'p'
47
- port.to_s
48
- when 'r'
49
- remote_user = connection_options && connection_options[:remote_user]
50
- if remote_user
51
- remote_user
52
- else
53
- raise ArgumentError, "remote user name not available"
40
+ # Return a new socket connected to the given host and port via the
41
+ # proxy that was requested when the socket factory was instantiated.
42
+ def open(host, port, connection_options = nil)
43
+ command_line = @command_line_template.gsub(/%(.)/) {
44
+ case $1
45
+ when 'h'
46
+ host
47
+ when 'p'
48
+ port.to_s
49
+ when 'r'
50
+ remote_user = connection_options && connection_options[:remote_user]
51
+ if remote_user
52
+ remote_user
53
+ else
54
+ raise ArgumentError, "remote user name not available"
55
+ end
56
+ when '%'
57
+ '%'
58
+ else
59
+ raise ArgumentError, "unknown key: #{$1}"
60
+ end
61
+ }
62
+ begin
63
+ io = IO.popen(command_line, "r+")
64
+ begin
65
+ if result = IO.select([io], nil, [io], @timeout)
66
+ if result.last.any? || io.eof?
67
+ raise "command failed"
68
+ end
69
+ else
70
+ raise "command timed out"
71
+ end
72
+ rescue StandardError
73
+ close_on_error(io)
74
+ raise
75
+ end
76
+ rescue StandardError => e
77
+ raise ConnectError, "#{e}: #{command_line}"
54
78
  end
55
- when '%'
56
- '%'
57
- else
58
- raise ArgumentError, "unknown key: #{$1}"
59
- end
60
- }
61
- begin
62
- io = IO.popen(command_line, "r+")
63
- begin
64
- if result = IO.select([io], nil, [io], @timeout)
65
- if result.last.any? || io.eof?
66
- raise "command failed"
79
+ @command_line = command_line
80
+ if Gem.win_platform?
81
+ # read_nonblock and write_nonblock are not available on Windows
82
+ # pipe. Use sysread and syswrite as a replacement works.
83
+ def io.send(data, flag)
84
+ syswrite(data)
85
+ end
86
+
87
+ def io.recv(size)
88
+ sysread(size)
67
89
  end
68
90
  else
69
- raise "command timed out"
70
- end
71
- rescue
72
- close_on_error(io)
73
- raise
74
- end
75
- rescue => e
76
- raise ConnectError, "#{e}: #{command_line}"
77
- end
78
- @command_line = command_line
79
- if Gem.win_platform?
80
- # read_nonblock and write_nonblock are not available on Windows
81
- # pipe. Use sysread and syswrite as a replacement works.
82
- def io.send(data, flag)
83
- syswrite(data)
84
- end
91
+ def io.send(data, flag)
92
+ begin
93
+ result = write_nonblock(data)
94
+ rescue IO::WaitWritable, Errno::EINTR
95
+ IO.select(nil, [self])
96
+ retry
97
+ end
98
+ result
99
+ end
85
100
 
86
- def io.recv(size)
87
- sysread(size)
88
- end
89
- else
90
- def io.send(data, flag)
91
- begin
92
- result = write_nonblock(data)
93
- rescue IO::WaitWritable, Errno::EINTR
94
- IO.select(nil, [self])
95
- retry
101
+ def io.recv(size)
102
+ begin
103
+ result = read_nonblock(size)
104
+ rescue IO::WaitReadable, Errno::EINTR
105
+ timeout_in_seconds = 20
106
+ if IO.select([self], nil, [self], timeout_in_seconds) == nil
107
+ raise "Unexpected spurious read wakeup"
108
+ end
109
+ retry
110
+ end
111
+ result
112
+ end
96
113
  end
97
- result
114
+ io
98
115
  end
99
116
 
100
- def io.recv(size)
101
- begin
102
- result = read_nonblock(size)
103
- rescue IO::WaitReadable, Errno::EINTR
104
- timeout_in_seconds = 20
105
- if IO.select([self], nil, [self], timeout_in_seconds) == nil
106
- raise "Unexpected spurious read wakeup"
107
- end
108
- retry
109
- end
110
- result
117
+ def close_on_error(io)
118
+ Process.kill('TERM', io.pid)
119
+ Thread.new { io.close }
111
120
  end
112
121
  end
113
- io
114
- end
115
-
116
- def close_on_error(io)
117
- Process.kill('TERM', io.pid)
118
- Thread.new { io.close }
119
122
  end
120
-
121
123
  end
122
-
123
- end; end; end
124
+ end